Skip to content

Commit 1768d95

Browse files
Restore node:fs after the Codex instructions unit mock (#530)
The instructions test replaced node:fs for the whole process and never put it back. CI loads ./src before ./tests, so every later suite that touches the real filesystem saw the in-memory fake — missing settings files, ENOENT fixture reads, and chmod on paths mkdir never created. Spread the real module into the mock and restore it in afterAll, matching the pattern already used by the session state tests.
1 parent d9d516a commit 1768d95

1 file changed

Lines changed: 75 additions & 61 deletions

File tree

Lines changed: 75 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,119 +1,133 @@
1-
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
1+
import { afterAll, afterEach, beforeEach, describe, expect, mock, test } from "bun:test"
22

33
// Reused by both the TUI and exec boot paths (src/tui/runner.ts,
44
// src/exec/runner.ts) to refresh the pinned Codex instructions before first
55
// Codex inference. This exercises the shared refresh/fallback logic directly,
66
// with disk I/O faked so tests never touch the real ~/.corbits cache.
7+
//
8+
// Shallow-copy + afterAll restore is required: Bun mutates the live module
9+
// namespace when mock.module runs, and CI loads ./src before ./tests — a leaked
10+
// node:fs mock turns later suites into ENOENT / missing-settings failures.
711

8-
let fakeDisk = new Map<string, string>();
12+
const realFs = { ...(await import("node:fs")) }
13+
14+
let fakeDisk = new Map<string, string>()
915

1016
mock.module("node:fs", () => ({
17+
...realFs,
1118
readFileSync: (path: string) => {
12-
const contents = fakeDisk.get(path);
19+
const contents = fakeDisk.get(path)
1320
if (contents === undefined) {
14-
const err = new Error("ENOENT") as NodeJS.ErrnoException;
15-
err.code = "ENOENT";
16-
throw err;
21+
const err = new Error("ENOENT") as NodeJS.ErrnoException
22+
err.code = "ENOENT"
23+
throw err
1724
}
18-
return contents;
25+
return contents
1926
},
2027
writeFileSync: (path: string, contents: string) => {
21-
fakeDisk.set(path, contents);
28+
fakeDisk.set(path, contents)
2229
},
2330
mkdirSync: () => undefined,
24-
}));
31+
}))
32+
33+
afterAll(() => {
34+
mock.module("node:fs", () => realFs)
35+
})
2536

2637
const { refreshCodexInstructions, codexInstructions, codexInstructionsHash } = await import(
27-
"./instructions.js"
28-
);
29-
const { GPT_5_CODEX_PROMPT } = await import("./prompts/gpt-5-codex.js");
38+
"./instructions.js",
39+
)
40+
const { GPT_5_CODEX_PROMPT } = await import("./prompts/gpt-5-codex.js")
3041

31-
const VALID_PROMPT = `You are Codex${"x".repeat(1200)}`;
42+
const VALID_PROMPT = `You are Codex${"x".repeat(1200)}`
3243

3344
function mockFetchSequence(tag: string, promptResponse: () => Response): typeof fetch {
3445
return (async (input: RequestInfo | URL) => {
35-
const url = String(input);
46+
const url = String(input)
3647
if (url.includes("releases/latest")) {
37-
return new Response(JSON.stringify({ tag_name: tag }), { status: 200 });
48+
return new Response(JSON.stringify({ tag_name: tag }), { status: 200 })
3849
}
39-
return promptResponse();
40-
}) as typeof fetch;
50+
return promptResponse()
51+
}) as typeof fetch
4152
}
4253

4354
describe("refreshCodexInstructions", () => {
44-
const originalFetch = global.fetch;
55+
const originalFetch = global.fetch
4556

4657
beforeEach(() => {
47-
fakeDisk = new Map();
48-
});
58+
fakeDisk = new Map()
59+
})
4960

5061
afterEach(() => {
51-
global.fetch = originalFetch;
52-
});
62+
global.fetch = originalFetch
63+
})
5364

5465
test("bundled copy is used before any refresh", () => {
55-
expect(codexInstructions()).toBe(GPT_5_CODEX_PROMPT);
56-
});
66+
expect(codexInstructions()).toBe(GPT_5_CODEX_PROMPT)
67+
})
5768

5869
test("updates in-memory instructions on a successful fetch", async () => {
59-
global.fetch = mockFetchSequence("rust-v1.2.3", () => new Response(VALID_PROMPT, { status: 200 }));
70+
global.fetch = mockFetchSequence("rust-v1.2.3", () => new Response(VALID_PROMPT, { status: 200 }))
6071

61-
await refreshCodexInstructions();
62-
expect(codexInstructions()).toBe(VALID_PROMPT);
63-
});
72+
await refreshCodexInstructions()
73+
expect(codexInstructions()).toBe(VALID_PROMPT)
74+
})
6475

6576
test("falls back without throwing the run when the release lookup network call fails", async () => {
66-
const before = codexInstructions();
67-
global.fetch = (() => Promise.reject(new Error("network down"))) as unknown as typeof fetch;
77+
const before = codexInstructions()
78+
global.fetch = (() => Promise.reject(new Error("network down"))) as unknown as typeof fetch
6879

6980
// The function itself rejects; callers (TUI/exec boot) catch this and
7081
// keep running on cache/bundled instructions — see src/exec/runner.ts and
7182
// src/tui/runner.ts refresh call sites.
72-
await expect(refreshCodexInstructions()).rejects.toThrow("network down");
73-
expect(codexInstructions()).toBe(before);
74-
});
83+
await expect(refreshCodexInstructions()).rejects.toThrow("network down")
84+
expect(codexInstructions()).toBe(before)
85+
})
7586

7687
test("falls back without throwing when the prompt fetch returns a non-200", async () => {
77-
const before = codexInstructions();
78-
global.fetch = mockFetchSequence("rust-v1.2.3", () => new Response("not found", { status: 404 }));
88+
const before = codexInstructions()
89+
global.fetch = mockFetchSequence("rust-v1.2.3", () => new Response("not found", { status: 404 }))
7990

80-
await expect(refreshCodexInstructions()).rejects.toThrow(/HTTP 404/);
81-
expect(codexInstructions()).toBe(before);
82-
});
91+
await expect(refreshCodexInstructions()).rejects.toThrow(/HTTP 404/)
92+
expect(codexInstructions()).toBe(before)
93+
})
8394

8495
test("rejects a 200 response whose body is not a valid Codex prompt (CDN error page)", async () => {
85-
const before = codexInstructions();
86-
global.fetch = mockFetchSequence("rust-v1.2.3", () => new Response("<html>oops</html>", { status: 200 }));
96+
const before = codexInstructions()
97+
global.fetch = mockFetchSequence(
98+
"rust-v1.2.3",
99+
() => new Response("<html>oops</html>", { status: 200 }),
100+
)
87101

88-
await expect(refreshCodexInstructions()).rejects.toThrow(/unexpected body/);
89-
expect(codexInstructions()).toBe(before);
90-
});
102+
await expect(refreshCodexInstructions()).rejects.toThrow(/unexpected body/)
103+
expect(codexInstructions()).toBe(before)
104+
})
91105

92106
test("rejects within the timeout when a fetch never resolves (hung connection)", async () => {
93-
const before = codexInstructions();
107+
const before = codexInstructions()
94108
global.fetch = ((_input: RequestInfo | URL, init?: RequestInit) => {
95109
return new Promise((_resolve, reject) => {
96-
const signal = init?.signal;
110+
const signal = init?.signal
97111
if (signal) {
98-
signal.addEventListener("abort", () => reject(signal.reason as Error));
112+
signal.addEventListener("abort", () => reject(signal.reason as Error))
99113
}
100-
});
101-
}) as unknown as typeof fetch;
114+
})
115+
}) as unknown as typeof fetch
102116

103-
const started = Date.now();
104-
await expect(refreshCodexInstructions()).rejects.toBeTruthy();
105-
expect(Date.now() - started).toBeLessThan(15_000);
106-
expect(codexInstructions()).toBe(before);
107-
}, 20_000);
117+
const started = Date.now()
118+
await expect(refreshCodexInstructions()).rejects.toBeTruthy()
119+
expect(Date.now() - started).toBeLessThan(15_000)
120+
expect(codexInstructions()).toBe(before)
121+
}, 20_000)
108122

109123
test("codexInstructionsHash reflects the currently resolved instructions text", async () => {
110-
const hashBefore = codexInstructionsHash();
111-
expect(hashBefore).toMatch(/^[0-9a-f]{12}$/);
124+
const hashBefore = codexInstructionsHash()
125+
expect(hashBefore).toMatch(/^[0-9a-f]{12}$/)
112126

113-
const otherPrompt = `You are Codex${"y".repeat(1200)}`;
114-
global.fetch = mockFetchSequence("rust-v1.2.4", () => new Response(otherPrompt, { status: 200 }));
115-
await refreshCodexInstructions();
127+
const otherPrompt = `You are Codex${"y".repeat(1200)}`
128+
global.fetch = mockFetchSequence("rust-v1.2.4", () => new Response(otherPrompt, { status: 200 }))
129+
await refreshCodexInstructions()
116130

117-
expect(codexInstructionsHash()).not.toBe(hashBefore);
118-
});
119-
});
131+
expect(codexInstructionsHash()).not.toBe(hashBefore)
132+
})
133+
})

0 commit comments

Comments
 (0)