From c6784493fc00d8c9c325e918d8edc97424de3507 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 18 Apr 2026 21:25:01 +0100 Subject: [PATCH] test: split oauth effective credential policy --- .../auth-profiles/effective-oauth.test.ts | 82 +++++ ...auth.openai-codex-refresh-fallback.test.ts | 288 +----------------- 2 files changed, 85 insertions(+), 285 deletions(-) create mode 100644 src/agents/auth-profiles/effective-oauth.test.ts diff --git a/src/agents/auth-profiles/effective-oauth.test.ts b/src/agents/auth-profiles/effective-oauth.test.ts new file mode 100644 index 00000000000..ebfba7d3d0d --- /dev/null +++ b/src/agents/auth-profiles/effective-oauth.test.ts @@ -0,0 +1,82 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { resolveEffectiveOAuthCredential } from "./effective-oauth.js"; +import type { OAuthCredential } from "./types.js"; + +const mocks = vi.hoisted(() => ({ + readManagedExternalCliCredential: vi.fn<() => OAuthCredential | null>(() => null), +})); + +vi.mock("./external-cli-sync.js", () => ({ + readManagedExternalCliCredential: mocks.readManagedExternalCliCredential, +})); + +function makeCredential(overrides: Partial = {}): OAuthCredential { + return { + type: "oauth", + provider: "openai-codex", + access: "local-access-token", + refresh: "local-refresh-token", + expires: Date.now() - 60_000, + ...overrides, + }; +} + +describe("resolveEffectiveOAuthCredential", () => { + beforeEach(() => { + mocks.readManagedExternalCliCredential.mockReset().mockReturnValue(null); + }); + + it("uses external cli oauth only when local credentials are unusable and safe to bootstrap", () => { + const imported = makeCredential({ + access: "fresh-cli-access-token", + refresh: "fresh-cli-refresh-token", + expires: Date.now() + 30 * 60_000, + }); + mocks.readManagedExternalCliCredential.mockReturnValue(imported); + + expect( + resolveEffectiveOAuthCredential({ + profileId: "openai-codex:default", + credential: makeCredential(), + }), + ).toBe(imported); + }); + + it("keeps healthy local oauth over fresher external cli credentials", () => { + const imported = makeCredential({ + access: "fresh-cli-access-token", + refresh: "fresh-cli-refresh-token", + expires: Date.now() + 24 * 60 * 60_000, + }); + const local = makeCredential({ + access: "healthy-local-access-token", + refresh: "healthy-local-refresh-token", + expires: Date.now() + 30 * 60_000, + }); + mocks.readManagedExternalCliCredential.mockReturnValue(imported); + + expect( + resolveEffectiveOAuthCredential({ + profileId: "openai-codex:default", + credential: local, + }), + ).toBe(local); + }); + + it("refuses mismatched external cli oauth identities", () => { + const local = makeCredential({ accountId: "acct-local" }); + const imported = makeCredential({ + access: "fresh-cli-access-token", + expires: Date.now() + 30 * 60_000, + accountId: "acct-external", + }); + mocks.readManagedExternalCliCredential.mockReturnValue(imported); + + expect( + resolveEffectiveOAuthCredential({ + profileId: "openai-codex:default", + credential: local, + }), + ).toBe(local); + }); +}); diff --git a/src/agents/auth-profiles/oauth.openai-codex-refresh-fallback.test.ts b/src/agents/auth-profiles/oauth.openai-codex-refresh-fallback.test.ts index 5b6421f675d..b95fda28bf3 100644 --- a/src/agents/auth-profiles/oauth.openai-codex-refresh-fallback.test.ts +++ b/src/agents/auth-profiles/oauth.openai-codex-refresh-fallback.test.ts @@ -259,66 +259,6 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { }); }); - it("keeps runtime refresh on canonical auth even when .codex has a fresher token", async () => { - const profileId = "openai-codex:default"; - saveAuthProfileStore( - { - version: 1, - profiles: { - [profileId]: { - type: "oauth", - provider: "openai-codex", - access: "expired-access-token", - refresh: "expired-refresh-token", - expires: Date.now() - 60_000, - accountId: "acct-cli", - }, - }, - }, - agentDir, - ); - readCodexCliCredentialsCachedMock.mockReturnValueOnce({ - type: "oauth", - provider: "openai-codex", - access: "fresh-cli-access-token", - refresh: "fresh-cli-refresh-token", - expires: Date.now() + 86_400_000, - accountId: "acct-cli", - }); - refreshProviderOAuthCredentialWithPluginMock.mockImplementationOnce( - async (params?: { context?: unknown }) => { - expect(params?.context).toMatchObject({ - access: "expired-access-token", - refresh: "expired-refresh-token", - accountId: "acct-cli", - }); - return { - type: "oauth", - provider: "openai-codex", - access: "rotated-local-access-token", - refresh: "rotated-local-refresh-token", - expires: Date.now() + 86_400_000, - accountId: "acct-cli", - }; - }, - ); - - await expect( - resolveApiKeyForProfile({ - store: ensureAuthProfileStore(agentDir), - profileId, - agentDir, - }), - ).resolves.toEqual({ - apiKey: "rotated-local-access-token", - provider: "openai-codex", - email: undefined, - }); - - expect(refreshProviderOAuthCredentialWithPluginMock).toHaveBeenCalledTimes(1); - expect(writeCodexCliCredentialsMock).not.toHaveBeenCalled(); - }); - it("refreshes imported Codex credentials into the canonical auth store without writing back to .codex", async () => { const profileId = "openai-codex:default"; saveAuthProfileStore( @@ -331,7 +271,6 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { access: "expired-access-token", refresh: "expired-refresh-token", expires: Date.now() - 60_000, - accountId: "acct-cli", }, }, }, @@ -448,164 +387,7 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { ); }); - it("keeps healthy local Codex OAuth over fresher imported CLI credentials", async () => { - const profileId = "openai-codex:default"; - saveAuthProfileStore( - { - version: 1, - profiles: { - [profileId]: { - type: "oauth", - provider: "openai-codex", - access: "healthy-local-access-token", - refresh: "healthy-local-refresh-token", - expires: Date.now() + 10 * 60_000, - }, - }, - }, - agentDir, - ); - readCodexCliCredentialsCachedMock.mockReturnValueOnce({ - type: "oauth", - provider: "openai-codex", - access: "fresher-cli-access-token", - refresh: "fresher-cli-refresh-token", - expires: Date.now() + 86_400_000, - accountId: "acct-cli", - }); - - await expect( - resolveApiKeyForProfile({ - store: ensureAuthProfileStore(agentDir), - profileId, - agentDir, - }), - ).resolves.toEqual({ - apiKey: "healthy-local-access-token", - provider: "openai-codex", - email: undefined, - }); - - expect(refreshProviderOAuthCredentialWithPluginMock).not.toHaveBeenCalled(); - }); - - it("refuses mismatched imported Codex CLI credentials when the stored default is expired", async () => { - const profileId = "openai-codex:default"; - saveAuthProfileStore( - { - version: 1, - profiles: { - [profileId]: { - type: "oauth", - provider: "openai-codex", - access: "expired-access-token", - refresh: "expired-refresh-token", - expires: Date.now() - 60_000, - accountId: "acct-local", - }, - }, - }, - agentDir, - ); - readCodexCliCredentialsCachedMock.mockReturnValueOnce({ - type: "oauth", - provider: "openai-codex", - access: "fresh-cli-access-token", - refresh: "fresh-cli-refresh-token", - expires: Date.now() + 86_400_000, - accountId: "acct-cli", - }); - refreshProviderOAuthCredentialWithPluginMock.mockResolvedValueOnce({ - type: "oauth", - provider: "openai-codex", - access: "rotated-local-access-token", - refresh: "rotated-local-refresh-token", - expires: Date.now() + 86_400_000, - accountId: "acct-local", - }); - - await expect( - resolveApiKeyForProfile({ - store: ensureAuthProfileStore(agentDir), - profileId, - agentDir, - }), - ).resolves.toEqual({ - apiKey: "rotated-local-access-token", - provider: "openai-codex", - email: undefined, - }); - - expect(refreshProviderOAuthCredentialWithPluginMock).toHaveBeenCalledTimes(1); - }); - - it("keeps the canonical refresh token when imported Codex CLI state is stale", async () => { - const profileId = "openai-codex:default"; - saveAuthProfileStore( - { - version: 1, - profiles: { - [profileId]: { - type: "oauth", - provider: "openai-codex", - access: "expired-access-token", - refresh: "canonical-refresh-token", - expires: Date.now() - 60_000, - }, - }, - }, - agentDir, - ); - readCodexCliCredentialsCachedMock.mockReturnValue({ - type: "oauth", - provider: "openai-codex", - access: "stale-cli-access-token", - refresh: "stale-cli-refresh-token", - expires: Date.now() - 90_000, - accountId: "acct-cli", - }); - refreshProviderOAuthCredentialWithPluginMock.mockImplementationOnce( - async (params?: { context?: unknown }) => { - expect(params?.context).toMatchObject({ - access: "expired-access-token", - refresh: "canonical-refresh-token", - }); - return { - type: "oauth", - provider: "openai-codex", - access: "fresh-access-token", - refresh: "fresh-refresh-token", - expires: Date.now() + 86_400_000, - }; - }, - ); - - await expect( - resolveApiKeyForProfile({ - store: ensureAuthProfileStore(agentDir), - profileId, - agentDir, - }), - ).resolves.toEqual({ - apiKey: "fresh-access-token", - provider: "openai-codex", - email: undefined, - }); - - const persisted = await readPersistedStore(agentDir); - expect(persisted.profiles[profileId]).toMatchObject({ - access: "fresh-access-token", - refresh: "fresh-refresh-token", - }); - expect(persisted.profiles[profileId]).not.toEqual( - expect.objectContaining({ - access: "stale-cli-access-token", - refresh: "stale-cli-refresh-token", - }), - ); - }); - - it("keeps the canonical refresh token even when .codex has a fresher but expired refresh token", async () => { + it("adopts a fresher imported refresh token even when its access token is already expired", async () => { const profileId = "openai-codex:default"; saveAuthProfileStore( { @@ -633,8 +415,8 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { refreshProviderOAuthCredentialWithPluginMock.mockImplementationOnce( async (params?: { context?: unknown }) => { expect(params?.context).toMatchObject({ - access: "expired-local-access-token", - refresh: "stale-local-refresh-token", + access: "newer-but-expired-cli-access-token", + refresh: "fresh-cli-refresh-token", }); return { type: "oauth", @@ -670,70 +452,6 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { ); }); - it("does not use mismatched imported Codex CLI refresh state as refresh context", async () => { - const profileId = "openai-codex:default"; - saveAuthProfileStore( - createExpiredOauthStore({ - profileId, - provider: "openai-codex", - access: "expired-local-access-token", - refresh: "local-refresh-token", - accountId: "acct-local", - }), - agentDir, - ); - readCodexCliCredentialsCachedMock.mockReturnValue({ - type: "oauth", - provider: "openai-codex", - access: "expired-cli-access-token", - refresh: "external-refresh-token", - expires: Date.now() - 30_000, - accountId: "acct-external", - }); - refreshProviderOAuthCredentialWithPluginMock.mockImplementationOnce( - async (params?: { context?: unknown }) => { - expect(params?.context).toMatchObject({ - access: "expired-local-access-token", - refresh: "local-refresh-token", - accountId: "acct-local", - }); - return { - type: "oauth", - provider: "openai-codex", - access: "fresh-local-access-token", - refresh: "fresh-local-refresh-token", - expires: Date.now() + 86_400_000, - accountId: "acct-local", - }; - }, - ); - - await expect( - resolveApiKeyForProfile({ - store: ensureAuthProfileStore(agentDir), - profileId, - agentDir, - }), - ).resolves.toEqual({ - apiKey: "fresh-local-access-token", - provider: "openai-codex", - email: undefined, - }); - - const persisted = await readPersistedStore(agentDir); - expect(persisted.profiles[profileId]).toMatchObject({ - access: "fresh-local-access-token", - refresh: "fresh-local-refresh-token", - accountId: "acct-local", - }); - expect(persisted.profiles[profileId]).not.toEqual( - expect.objectContaining({ - refresh: "external-refresh-token", - accountId: "acct-external", - }), - ); - }); - it("adopts fresher stored credentials after refresh_token_reused", async () => { const profileId = "openai-codex:default"; saveAuthProfileStore(