From 77d0e0309c30a6cf9635b56c29f34859ae63531f Mon Sep 17 00:00:00 2001 From: ndycode Date: Wed, 4 Mar 2026 10:06:05 +0800 Subject: [PATCH 1/3] test: add boundary regression tests for core flows - add storage identity fallback test for malformed .git metadata - add OAuth poll abort test for deterministic close behavior - add prompt refresh dedupe test for concurrent stale cache reads Co-authored-by: Codex --- test/codex-prompts.test.ts | 54 ++++++++++++++++++++++++++++++++++++++ test/paths.test.ts | 16 +++++++++++ test/server.unit.test.ts | 25 ++++++++++++++++++ 3 files changed, 95 insertions(+) diff --git a/test/codex-prompts.test.ts b/test/codex-prompts.test.ts index 17131b8f5..7245acef9 100644 --- a/test/codex-prompts.test.ts +++ b/test/codex-prompts.test.ts @@ -213,6 +213,60 @@ describe("Codex Prompts Module", () => { const second = await getCodexInstructions("gpt-5.1-codex"); expect(second).toBe("new version content"); }); + + it("deduplicates background refresh for concurrent stale calls", async () => { + const oldTimestamp = Date.now() - 20 * 60 * 1000; + let resolvePromptText: ((value: string) => void) | null = null; + const promptText = new Promise((resolve) => { + resolvePromptText = resolve; + }); + + mockedReadFile.mockImplementation((filePath) => { + if (typeof filePath === "string" && filePath.includes("-meta.json")) { + return Promise.resolve( + JSON.stringify({ + etag: "old-etag", + tag: "rust-v0.40.0", + lastChecked: oldTimestamp, + url: "https://example.com", + }), + ); + } + return Promise.resolve("stale disk content"); + }); + mockFetch.mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve({ tag_name: "rust-v0.50.0" }), + }); + mockFetch.mockImplementationOnce(() => + Promise.resolve({ + ok: true, + text: () => promptText, + headers: { get: () => "new-etag" }, + }), + ); + mockedMkdir.mockResolvedValue(undefined); + mockedWriteFile.mockResolvedValue(undefined); + + const [first, second] = await Promise.all([ + getCodexInstructions("gpt-5.1-codex"), + getCodexInstructions("gpt-5.1-codex"), + ]); + + expect(first).toBe("stale disk content"); + expect(second).toBe("stale disk content"); + await vi.waitFor(() => { + expect(mockFetch).toHaveBeenCalledTimes(2); + }); + + resolvePromptText?.("fresh deduped content"); + await vi.waitFor(() => { + expect(mockedWriteFile).toHaveBeenCalled(); + }); + + const refreshed = await getCodexInstructions("gpt-5.1-codex"); + expect(refreshed).toBe("fresh deduped content"); + }); }); describe("GitHub HTML fallback", () => { diff --git a/test/paths.test.ts b/test/paths.test.ts index d289bf2cd..36ed7ad5d 100644 --- a/test/paths.test.ts +++ b/test/paths.test.ts @@ -654,6 +654,22 @@ describe("Storage Paths Module", () => { expect(resolved).toBe(projectRoot); }); + it("falls back when .git entry exists but is neither file nor directory", () => { + const projectRoot = "/repo/weird"; + const gitEntry = path.join(projectRoot, ".git"); + + mockedExistsSync.mockImplementation((candidate) => candidate === gitEntry); + mockedStatSync.mockImplementation((candidate) => { + expect(candidate).toBe(gitEntry); + return buildMockStat({ isDirectory: false, isFile: false }); + }); + + const resolved = resolveProjectStorageIdentityRoot(projectRoot); + + expect(resolved).toBe(projectRoot); + expect(mockedReadFileSync).not.toHaveBeenCalled(); + }); + it("keeps project root when .git file does not point to worktrees", () => { const projectRoot = "/repo/submodule"; const gitEntry = path.join(projectRoot, ".git"); diff --git a/test/server.unit.test.ts b/test/server.unit.test.ts index 560f0a35e..28aeb89e8 100644 --- a/test/server.unit.test.ts +++ b/test/server.unit.test.ts @@ -300,6 +300,31 @@ describe('OAuth Server Unit Tests', () => { expect(code).toEqual({ code: 'the-code' }); }); + it('waitForCode returns null when close aborts active poll loop', async () => { + vi.useFakeTimers(); + try { + (mockServer.listen as ReturnType).mockImplementation( + (_port: number, _host: string, callback: () => void) => { + callback(); + return mockServer; + } + ); + (mockServer.on as ReturnType).mockReturnValue(mockServer); + (mockServer.close as ReturnType).mockImplementation(() => undefined); + + const result = await startLocalOAuthServer({ state: 'test-state' }); + const codePromise = result.waitForCode('test-state'); + result.close(); + + await vi.advanceTimersByTimeAsync(200); + + await expect(codePromise).resolves.toBeNull(); + expect(logWarn).not.toHaveBeenCalledWith('OAuth poll timeout after 5 minutes'); + } finally { + vi.useRealTimers(); + } + }); + it('should return null after 5 minute timeout', async () => { vi.useFakeTimers(); From 898d70805fef573106f3b855213bb664d52a3f7f Mon Sep 17 00:00:00 2001 From: ndycode Date: Wed, 4 Mar 2026 11:05:58 +0800 Subject: [PATCH 2/3] test: tighten prompt refresh dedupe regression - replace instantaneous fetch-count check with stable post-refresh assertion window - verify fetch count remains fixed after refresh persistence completes Co-authored-by: Codex --- test/codex-prompts.test.ts | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/test/codex-prompts.test.ts b/test/codex-prompts.test.ts index 7245acef9..a208d7dcc 100644 --- a/test/codex-prompts.test.ts +++ b/test/codex-prompts.test.ts @@ -255,14 +255,22 @@ describe("Codex Prompts Module", () => { expect(first).toBe("stale disk content"); expect(second).toBe("stale disk content"); - await vi.waitFor(() => { - expect(mockFetch).toHaveBeenCalledTimes(2); - }); + for (let i = 0; i < 25 && mockFetch.mock.calls.length < 2; i += 1) { + await Promise.resolve(); + } + expect(mockFetch).toHaveBeenCalledTimes(2); resolvePromptText?.("fresh deduped content"); - await vi.waitFor(() => { - expect(mockedWriteFile).toHaveBeenCalled(); - }); + for (let i = 0; i < 25 && mockedWriteFile.mock.calls.length === 0; i += 1) { + await Promise.resolve(); + } + expect(mockedWriteFile).toHaveBeenCalled(); + const settledFetchCalls = mockFetch.mock.calls.length; + for (let i = 0; i < 25; i += 1) { + await Promise.resolve(); + } + expect(settledFetchCalls).toBe(2); + expect(mockFetch).toHaveBeenCalledTimes(settledFetchCalls); const refreshed = await getCodexInstructions("gpt-5.1-codex"); expect(refreshed).toBe("fresh deduped content"); From e06e5e52670336e7d1e5c49d30d219536ab1dae4 Mon Sep 17 00:00:00 2001 From: ndycode Date: Wed, 4 Mar 2026 11:40:38 +0800 Subject: [PATCH 3/3] test: use waitFor for prompt dedupe stability assertions - replace microtask spin-loops with vi.waitFor polling - keep final no-extra-fetch stability assertion after refresh Co-authored-by: Codex --- test/codex-prompts.test.ts | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/test/codex-prompts.test.ts b/test/codex-prompts.test.ts index a208d7dcc..8e72bac9a 100644 --- a/test/codex-prompts.test.ts +++ b/test/codex-prompts.test.ts @@ -255,25 +255,22 @@ describe("Codex Prompts Module", () => { expect(first).toBe("stale disk content"); expect(second).toBe("stale disk content"); - for (let i = 0; i < 25 && mockFetch.mock.calls.length < 2; i += 1) { - await Promise.resolve(); - } - expect(mockFetch).toHaveBeenCalledTimes(2); + await vi.waitFor(() => { + expect(mockFetch).toHaveBeenCalledTimes(2); + }); resolvePromptText?.("fresh deduped content"); - for (let i = 0; i < 25 && mockedWriteFile.mock.calls.length === 0; i += 1) { - await Promise.resolve(); - } - expect(mockedWriteFile).toHaveBeenCalled(); + await vi.waitFor(() => { + expect(mockedWriteFile).toHaveBeenCalled(); + }); const settledFetchCalls = mockFetch.mock.calls.length; - for (let i = 0; i < 25; i += 1) { - await Promise.resolve(); - } expect(settledFetchCalls).toBe(2); - expect(mockFetch).toHaveBeenCalledTimes(settledFetchCalls); const refreshed = await getCodexInstructions("gpt-5.1-codex"); expect(refreshed).toBe("fresh deduped content"); + await vi.waitFor(() => { + expect(mockFetch).toHaveBeenCalledTimes(settledFetchCalls); + }); }); });