From 8a0f4d9f5fa73755ea24f420b56ad106990e4ab9 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 22 Aug 2026 16:50:57 -0700 Subject: [PATCH] Restore node:fs after the Codex instructions unit mock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/auth/codex/instructions.test.ts | 136 +++++++++++++++------------- 1 file changed, 75 insertions(+), 61 deletions(-) diff --git a/src/auth/codex/instructions.test.ts b/src/auth/codex/instructions.test.ts index 63a798b79..95fcdc0d8 100644 --- a/src/auth/codex/instructions.test.ts +++ b/src/auth/codex/instructions.test.ts @@ -1,119 +1,133 @@ -import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test"; +import { afterAll, afterEach, beforeEach, describe, expect, mock, test } from "bun:test" // Reused by both the TUI and exec boot paths (src/tui/runner.ts, // src/exec/runner.ts) to refresh the pinned Codex instructions before first // Codex inference. This exercises the shared refresh/fallback logic directly, // with disk I/O faked so tests never touch the real ~/.corbits cache. +// +// Shallow-copy + afterAll restore is required: Bun mutates the live module +// namespace when mock.module runs, and CI loads ./src before ./tests — a leaked +// node:fs mock turns later suites into ENOENT / missing-settings failures. -let fakeDisk = new Map(); +const realFs = { ...(await import("node:fs")) } + +let fakeDisk = new Map() mock.module("node:fs", () => ({ + ...realFs, readFileSync: (path: string) => { - const contents = fakeDisk.get(path); + const contents = fakeDisk.get(path) if (contents === undefined) { - const err = new Error("ENOENT") as NodeJS.ErrnoException; - err.code = "ENOENT"; - throw err; + const err = new Error("ENOENT") as NodeJS.ErrnoException + err.code = "ENOENT" + throw err } - return contents; + return contents }, writeFileSync: (path: string, contents: string) => { - fakeDisk.set(path, contents); + fakeDisk.set(path, contents) }, mkdirSync: () => undefined, -})); +})) + +afterAll(() => { + mock.module("node:fs", () => realFs) +}) const { refreshCodexInstructions, codexInstructions, codexInstructionsHash } = await import( - "./instructions.js" -); -const { GPT_5_CODEX_PROMPT } = await import("./prompts/gpt-5-codex.js"); + "./instructions.js", +) +const { GPT_5_CODEX_PROMPT } = await import("./prompts/gpt-5-codex.js") -const VALID_PROMPT = `You are Codex${"x".repeat(1200)}`; +const VALID_PROMPT = `You are Codex${"x".repeat(1200)}` function mockFetchSequence(tag: string, promptResponse: () => Response): typeof fetch { return (async (input: RequestInfo | URL) => { - const url = String(input); + const url = String(input) if (url.includes("releases/latest")) { - return new Response(JSON.stringify({ tag_name: tag }), { status: 200 }); + return new Response(JSON.stringify({ tag_name: tag }), { status: 200 }) } - return promptResponse(); - }) as typeof fetch; + return promptResponse() + }) as typeof fetch } describe("refreshCodexInstructions", () => { - const originalFetch = global.fetch; + const originalFetch = global.fetch beforeEach(() => { - fakeDisk = new Map(); - }); + fakeDisk = new Map() + }) afterEach(() => { - global.fetch = originalFetch; - }); + global.fetch = originalFetch + }) test("bundled copy is used before any refresh", () => { - expect(codexInstructions()).toBe(GPT_5_CODEX_PROMPT); - }); + expect(codexInstructions()).toBe(GPT_5_CODEX_PROMPT) + }) test("updates in-memory instructions on a successful fetch", async () => { - global.fetch = mockFetchSequence("rust-v1.2.3", () => new Response(VALID_PROMPT, { status: 200 })); + global.fetch = mockFetchSequence("rust-v1.2.3", () => new Response(VALID_PROMPT, { status: 200 })) - await refreshCodexInstructions(); - expect(codexInstructions()).toBe(VALID_PROMPT); - }); + await refreshCodexInstructions() + expect(codexInstructions()).toBe(VALID_PROMPT) + }) test("falls back without throwing the run when the release lookup network call fails", async () => { - const before = codexInstructions(); - global.fetch = (() => Promise.reject(new Error("network down"))) as unknown as typeof fetch; + const before = codexInstructions() + global.fetch = (() => Promise.reject(new Error("network down"))) as unknown as typeof fetch // The function itself rejects; callers (TUI/exec boot) catch this and // keep running on cache/bundled instructions — see src/exec/runner.ts and // src/tui/runner.ts refresh call sites. - await expect(refreshCodexInstructions()).rejects.toThrow("network down"); - expect(codexInstructions()).toBe(before); - }); + await expect(refreshCodexInstructions()).rejects.toThrow("network down") + expect(codexInstructions()).toBe(before) + }) test("falls back without throwing when the prompt fetch returns a non-200", async () => { - const before = codexInstructions(); - global.fetch = mockFetchSequence("rust-v1.2.3", () => new Response("not found", { status: 404 })); + const before = codexInstructions() + global.fetch = mockFetchSequence("rust-v1.2.3", () => new Response("not found", { status: 404 })) - await expect(refreshCodexInstructions()).rejects.toThrow(/HTTP 404/); - expect(codexInstructions()).toBe(before); - }); + await expect(refreshCodexInstructions()).rejects.toThrow(/HTTP 404/) + expect(codexInstructions()).toBe(before) + }) test("rejects a 200 response whose body is not a valid Codex prompt (CDN error page)", async () => { - const before = codexInstructions(); - global.fetch = mockFetchSequence("rust-v1.2.3", () => new Response("oops", { status: 200 })); + const before = codexInstructions() + global.fetch = mockFetchSequence( + "rust-v1.2.3", + () => new Response("oops", { status: 200 }), + ) - await expect(refreshCodexInstructions()).rejects.toThrow(/unexpected body/); - expect(codexInstructions()).toBe(before); - }); + await expect(refreshCodexInstructions()).rejects.toThrow(/unexpected body/) + expect(codexInstructions()).toBe(before) + }) test("rejects within the timeout when a fetch never resolves (hung connection)", async () => { - const before = codexInstructions(); + const before = codexInstructions() global.fetch = ((_input: RequestInfo | URL, init?: RequestInit) => { return new Promise((_resolve, reject) => { - const signal = init?.signal; + const signal = init?.signal if (signal) { - signal.addEventListener("abort", () => reject(signal.reason as Error)); + signal.addEventListener("abort", () => reject(signal.reason as Error)) } - }); - }) as unknown as typeof fetch; + }) + }) as unknown as typeof fetch - const started = Date.now(); - await expect(refreshCodexInstructions()).rejects.toBeTruthy(); - expect(Date.now() - started).toBeLessThan(15_000); - expect(codexInstructions()).toBe(before); - }, 20_000); + const started = Date.now() + await expect(refreshCodexInstructions()).rejects.toBeTruthy() + expect(Date.now() - started).toBeLessThan(15_000) + expect(codexInstructions()).toBe(before) + }, 20_000) test("codexInstructionsHash reflects the currently resolved instructions text", async () => { - const hashBefore = codexInstructionsHash(); - expect(hashBefore).toMatch(/^[0-9a-f]{12}$/); + const hashBefore = codexInstructionsHash() + expect(hashBefore).toMatch(/^[0-9a-f]{12}$/) - const otherPrompt = `You are Codex${"y".repeat(1200)}`; - global.fetch = mockFetchSequence("rust-v1.2.4", () => new Response(otherPrompt, { status: 200 })); - await refreshCodexInstructions(); + const otherPrompt = `You are Codex${"y".repeat(1200)}` + global.fetch = mockFetchSequence("rust-v1.2.4", () => new Response(otherPrompt, { status: 200 })) + await refreshCodexInstructions() - expect(codexInstructionsHash()).not.toBe(hashBefore); - }); -}); + expect(codexInstructionsHash()).not.toBe(hashBefore) + }) +})