Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions test/codex-prompts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,65 @@ 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<string>((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 settledFetchCalls = mockFetch.mock.calls.length;
expect(settledFetchCalls).toBe(2);

const refreshed = await getCodexInstructions("gpt-5.1-codex");
expect(refreshed).toBe("fresh deduped content");
await vi.waitFor(() => {
expect(mockFetch).toHaveBeenCalledTimes(settledFetchCalls);
});
});
});

describe("GitHub HTML fallback", () => {
Expand Down
16 changes: 16 additions & 0 deletions test/paths.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
25 changes: 25 additions & 0 deletions test/server.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof vi.fn>).mockImplementation(
(_port: number, _host: string, callback: () => void) => {
callback();
return mockServer;
}
);
(mockServer.on as ReturnType<typeof vi.fn>).mockReturnValue(mockServer);
(mockServer.close as ReturnType<typeof vi.fn>).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();

Expand Down