diff --git a/src/plugins/provider-openai-codex-oauth.test.ts b/src/plugins/provider-openai-codex-oauth.test.ts index 81c79c0b63f..d1c6bcef55e 100644 --- a/src/plugins/provider-openai-codex-oauth.test.ts +++ b/src/plugins/provider-openai-codex-oauth.test.ts @@ -25,6 +25,13 @@ vi.mock("./provider-openai-codex-oauth-tls.js", () => ({ import { loginOpenAICodexOAuth } from "./provider-openai-codex-oauth.js"; +const CODEX_AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize?state=abc"; + +type CodexLoginOptions = { + onAuth: (event: { url: string }) => Promise; + onManualCodeInput?: () => Promise; +}; + function createPrompter() { const spin = { update: vi.fn(), stop: vi.fn() }; const prompter: Pick = { @@ -45,6 +52,22 @@ function createRuntime(): RuntimeEnv { }; } +function createCodexCredentials(extra: Record = {}) { + return { + provider: "openai-codex" as const, + access: "access-token", + refresh: "refresh-token", + expires: Date.now() + 60_000, + email: "user@example.com", + ...extra, + }; +} + +async function startCodexAuth(opts: CodexLoginOptions) { + await opts.onAuth({ url: CODEX_AUTHORIZE_URL }); + expect(opts.onManualCodeInput).toBeTypeOf("function"); +} + async function runCodexOAuth(params: { isRemote: boolean; openUrl?: (url: string) => Promise; @@ -68,13 +91,7 @@ describe("loginOpenAICodexOAuth", () => { }); it("returns credentials on successful oauth login", async () => { - const creds = { - provider: "openai-codex" as const, - access: "access-token", - refresh: "refresh-token", - expires: Date.now() + 60_000, - email: "user@example.com", - }; + const creds = createCodexCredentials(); mocks.loginOpenAICodex.mockResolvedValue(creds); const { result, spin, runtime } = await runCodexOAuth({ isRemote: false }); @@ -89,13 +106,7 @@ describe("loginOpenAICodexOAuth", () => { }); it("passes through Pi-provided authorize URLs without mutation", async () => { - const creds = { - provider: "openai-codex" as const, - access: "access-token", - refresh: "refresh-token", - expires: Date.now() + 60_000, - email: "user@example.com", - }; + const creds = createCodexCredentials(); mocks.loginOpenAICodex.mockImplementation( async (opts: { onAuth: (event: { url: string }) => Promise }) => { await opts.onAuth({ @@ -117,18 +128,10 @@ describe("loginOpenAICodexOAuth", () => { }); it("preserves authorize urls that omit scope", async () => { - const creds = { - provider: "openai-codex" as const, - access: "access-token", - refresh: "refresh-token", - expires: Date.now() + 60_000, - email: "user@example.com", - }; + const creds = createCodexCredentials(); mocks.loginOpenAICodex.mockImplementation( async (opts: { onAuth: (event: { url: string }) => Promise }) => { - await opts.onAuth({ - url: "https://auth.openai.com/oauth/authorize?state=abc", - }); + await opts.onAuth({ url: CODEX_AUTHORIZE_URL }); return creds; }, ); @@ -136,17 +139,11 @@ describe("loginOpenAICodexOAuth", () => { const openUrl = vi.fn(async () => {}); await runCodexOAuth({ isRemote: false, openUrl }); - expect(openUrl).toHaveBeenCalledWith("https://auth.openai.com/oauth/authorize?state=abc"); + expect(openUrl).toHaveBeenCalledWith(CODEX_AUTHORIZE_URL); }); it("preserves slash-terminated authorize paths too", async () => { - const creds = { - provider: "openai-codex" as const, - access: "access-token", - refresh: "refresh-token", - expires: Date.now() + 60_000, - email: "user@example.com", - }; + const creds = createCodexCredentials(); mocks.loginOpenAICodex.mockImplementation( async (opts: { onAuth: (event: { url: string }) => Promise }) => { await opts.onAuth({ @@ -185,26 +182,12 @@ describe("loginOpenAICodexOAuth", () => { }); it("passes manual code input hook for remote oauth flows", async () => { - const creds = { - provider: "openai-codex" as const, - access: "access-token", - refresh: "refresh-token", - expires: Date.now() + 60_000, - email: "user@example.com", - }; - mocks.loginOpenAICodex.mockImplementation( - async (opts: { - onAuth: (event: { url: string }) => Promise; - onManualCodeInput?: () => Promise; - }) => { - await opts.onAuth({ - url: "https://auth.openai.com/oauth/authorize?state=abc", - }); - expect(opts.onManualCodeInput).toBeTypeOf("function"); - await expect(opts.onManualCodeInput?.()).resolves.toContain("code=test"); - return creds; - }, - ); + const creds = createCodexCredentials(); + mocks.loginOpenAICodex.mockImplementation(async (opts: CodexLoginOptions) => { + await startCodexAuth(opts); + await expect(opts.onManualCodeInput?.()).resolves.toContain("code=test"); + return creds; + }); const { result, prompter } = await runCodexOAuth({ isRemote: true }); @@ -219,32 +202,17 @@ describe("loginOpenAICodexOAuth", () => { vi.useFakeTimers(); const { prompter } = createPrompter(); const runtime = createRuntime(); - mocks.loginOpenAICodex.mockImplementation( - async (opts: { - onAuth: (event: { url: string }) => Promise; - onManualCodeInput?: () => Promise; - }) => { - await opts.onAuth({ - url: "https://auth.openai.com/oauth/authorize?state=abc", - }); - expect(opts.onManualCodeInput).toBeTypeOf("function"); - const manualPromise = opts.onManualCodeInput?.(); - await vi.advanceTimersByTimeAsync(14_000); - expect(manualPromise).toBeDefined(); - expect(prompter.text).not.toHaveBeenCalled(); - await vi.advanceTimersByTimeAsync(1_000); - expect(prompter.text).not.toHaveBeenCalled(); - await vi.advanceTimersByTimeAsync(1_000); - return { - provider: "openai-codex" as const, - access: "access-token", - refresh: "refresh-token", - expires: Date.now() + 60_000, - email: "user@example.com", - manualCode: await manualPromise, - }; - }, - ); + mocks.loginOpenAICodex.mockImplementation(async (opts: CodexLoginOptions) => { + await startCodexAuth(opts); + const manualPromise = opts.onManualCodeInput?.(); + await vi.advanceTimersByTimeAsync(14_000); + expect(manualPromise).toBeDefined(); + expect(prompter.text).not.toHaveBeenCalled(); + await vi.advanceTimersByTimeAsync(1_000); + expect(prompter.text).not.toHaveBeenCalled(); + await vi.advanceTimersByTimeAsync(1_000); + return createCodexCredentials({ manualCode: await manualPromise }); + }); await expect( loginOpenAICodexOAuth({ @@ -270,25 +238,11 @@ describe("loginOpenAICodexOAuth", () => { it("clears the local manual fallback timer when browser callback settles first", async () => { vi.useFakeTimers(); - mocks.loginOpenAICodex.mockImplementation( - async (opts: { - onAuth: (event: { url: string }) => Promise; - onManualCodeInput?: () => Promise; - }) => { - await opts.onAuth({ - url: "https://auth.openai.com/oauth/authorize?state=abc", - }); - expect(opts.onManualCodeInput).toBeTypeOf("function"); - void opts.onManualCodeInput?.(); - return { - provider: "openai-codex" as const, - access: "access-token", - refresh: "refresh-token", - expires: Date.now() + 60_000, - email: "user@example.com", - }; - }, - ); + mocks.loginOpenAICodex.mockImplementation(async (opts: CodexLoginOptions) => { + await startCodexAuth(opts); + void opts.onManualCodeInput?.(); + return createCodexCredentials(); + }); await expect(runCodexOAuth({ isRemote: false })).resolves.toMatchObject({ result: expect.objectContaining({ @@ -302,13 +256,7 @@ describe("loginOpenAICodexOAuth", () => { }); it("continues OAuth flow on non-certificate preflight failures", async () => { - const creds = { - provider: "openai-codex" as const, - access: "access-token", - refresh: "refresh-token", - expires: Date.now() + 60_000, - email: "user@example.com", - }; + const creds = createCodexCredentials(); mocks.runOpenAIOAuthTlsPreflight.mockResolvedValue({ ok: false, kind: "network", @@ -332,13 +280,7 @@ describe("loginOpenAICodexOAuth", () => { message: "unable to get local issuer certificate", }); mocks.formatOpenAIOAuthTlsPreflightFix.mockReturnValue("Run brew postinstall openssl@3"); - const creds = { - provider: "openai-codex" as const, - access: "access-token", - refresh: "refresh-token", - expires: Date.now() + 60_000, - email: "user@example.com", - }; + const creds = createCodexCredentials(); mocks.loginOpenAICodex.mockResolvedValue(creds); const { prompter } = createPrompter(); @@ -368,14 +310,7 @@ describe("loginOpenAICodexOAuth", () => { async (opts: { onManualCodeInput?: () => Promise }) => { expect(opts.onManualCodeInput).toBeTypeOf("function"); const manualCode = await opts.onManualCodeInput?.(); - return { - provider: "openai-codex" as const, - access: "access-token", - refresh: "refresh-token", - expires: Date.now() + 60_000, - email: "user@example.com", - manualCode, - }; + return createCodexCredentials({ manualCode }); }, ); @@ -403,25 +338,12 @@ describe("loginOpenAICodexOAuth", () => { vi.useFakeTimers(); const { prompter } = createPrompter(); const runtime = createRuntime(); - mocks.loginOpenAICodex.mockImplementation( - async (opts: { - onAuth: (event: { url: string }) => Promise; - onManualCodeInput?: () => Promise; - }) => { - await opts.onAuth({ - url: "https://auth.openai.com/oauth/authorize?state=abc", - }); - void opts.onManualCodeInput?.(); - await vi.advanceTimersByTimeAsync(15_500); - return { - provider: "openai-codex" as const, - access: "access-token", - refresh: "refresh-token", - expires: Date.now() + 60_000, - email: "user@example.com", - }; - }, - ); + mocks.loginOpenAICodex.mockImplementation(async (opts: CodexLoginOptions) => { + await startCodexAuth(opts); + void opts.onManualCodeInput?.(); + await vi.advanceTimersByTimeAsync(15_500); + return createCodexCredentials(); + }); await expect( loginOpenAICodexOAuth({