From e89e2145162cb70077a6a46f1a890ac101f3f19f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 18 Apr 2026 22:46:49 +0100 Subject: [PATCH] test: share oauth test helpers --- .../oauth-lock-timeout-classification.test.ts | 41 ++++---------- .../auth-profiles/oauth-refresh-queue.test.ts | 43 ++++----------- src/agents/auth-profiles/oauth-test-utils.ts | 54 +++++++++++++++++++ .../oauth.adopt-identity.test.ts | 39 ++++---------- .../oauth.concurrent-agents.test.ts | 45 +++------------- .../oauth.mirror-refresh.test.ts | 51 ++++-------------- ...auth.openai-codex-refresh-fallback.test.ts | 31 +---------- 7 files changed, 104 insertions(+), 200 deletions(-) create mode 100644 src/agents/auth-profiles/oauth-test-utils.ts diff --git a/src/agents/auth-profiles/oauth-lock-timeout-classification.test.ts b/src/agents/auth-profiles/oauth-lock-timeout-classification.test.ts index bd265653d27..04f6ee44c5c 100644 --- a/src/agents/auth-profiles/oauth-lock-timeout-classification.test.ts +++ b/src/agents/auth-profiles/oauth-lock-timeout-classification.test.ts @@ -4,19 +4,18 @@ import path from "node:path"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { FILE_LOCK_TIMEOUT_ERROR_CODE, type FileLockTimeoutError } from "../../infra/file-lock.js"; import { captureEnv } from "../../test-utils/env.js"; +import { + OAUTH_AGENT_ENV_KEYS, + createExpiredOauthStore, + resolveApiKeyForProfileInTest, +} from "./oauth-test-utils.js"; import { resolveAuthStorePath, resolveOAuthRefreshLockPath } from "./paths.js"; import { clearRuntimeAuthProfileStoreSnapshots, saveAuthProfileStore } from "./store.js"; -import type { AuthProfileStore, OAuthCredential } from "./types.js"; +import type { OAuthCredential } from "./types.js"; let resolveApiKeyForProfile: typeof import("./oauth.js").resolveApiKeyForProfile; let resetOAuthRefreshQueuesForTest: typeof import("./oauth.js").resetOAuthRefreshQueuesForTest; -function resolveApiKeyForProfileInTest( - params: Omit[0], "cfg">, -) { - return resolveApiKeyForProfile({ cfg: {}, ...params }); -} - const { withFileLockMock } = vi.hoisted(() => ({ withFileLockMock: vi.fn( async (_filePath: string, _options: unknown, run: () => Promise) => await run(), @@ -78,24 +77,6 @@ vi.mock("./external-cli-sync.js", () => ({ existing !== incoming, })); -function createExpiredOauthStore(params: { - profileId: string; - provider: string; -}): AuthProfileStore { - return { - version: 1, - profiles: { - [params.profileId]: { - type: "oauth", - provider: params.provider, - access: "stale-access", - refresh: "stale-refresh", - expires: Date.now() - 60_000, - } satisfies OAuthCredential, - }, - }; -} - function createLockTimeoutError(lockPath: string): FileLockTimeoutError { return Object.assign(new Error(`file lock timeout for ${lockPath.slice(0, -5)}`), { code: FILE_LOCK_TIMEOUT_ERROR_CODE as typeof FILE_LOCK_TIMEOUT_ERROR_CODE, @@ -104,11 +85,7 @@ function createLockTimeoutError(lockPath: string): FileLockTimeoutError { } describe("OAuth refresh lock timeout classification", () => { - const envSnapshot = captureEnv([ - "OPENCLAW_STATE_DIR", - "OPENCLAW_AGENT_DIR", - "PI_CODING_AGENT_DIR", - ]); + const envSnapshot = captureEnv(OAUTH_AGENT_ENV_KEYS); let tempRoot = ""; let agentDir = ""; let caseIndex = 0; @@ -155,7 +132,7 @@ describe("OAuth refresh lock timeout classification", () => { }); try { - await resolveApiKeyForProfileInTest({ + await resolveApiKeyForProfileInTest(resolveApiKeyForProfile, { store, profileId, agentDir, @@ -194,7 +171,7 @@ describe("OAuth refresh lock timeout classification", () => { }); try { - await resolveApiKeyForProfileInTest({ + await resolveApiKeyForProfileInTest(resolveApiKeyForProfile, { store, profileId, agentDir, diff --git a/src/agents/auth-profiles/oauth-refresh-queue.test.ts b/src/agents/auth-profiles/oauth-refresh-queue.test.ts index 7c219874f38..182f4d6df91 100644 --- a/src/agents/auth-profiles/oauth-refresh-queue.test.ts +++ b/src/agents/auth-profiles/oauth-refresh-queue.test.ts @@ -4,19 +4,18 @@ import path from "node:path"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { resetFileLockStateForTest } from "../../infra/file-lock.js"; import { captureEnv } from "../../test-utils/env.js"; +import { + OAUTH_AGENT_ENV_KEYS, + createExpiredOauthStore, + resolveApiKeyForProfileInTest, +} from "./oauth-test-utils.js"; import { resolveApiKeyForProfile, resetOAuthRefreshQueuesForTest } from "./oauth.js"; import { clearRuntimeAuthProfileStoreSnapshots, ensureAuthProfileStore, saveAuthProfileStore, } from "./store.js"; -import type { AuthProfileStore, OAuthCredential } from "./types.js"; - -function resolveApiKeyForProfileInTest( - params: Omit[0], "cfg">, -) { - return resolveApiKeyForProfile({ cfg: {}, ...params }); -} +import type { OAuthCredential } from "./types.js"; const { refreshProviderOAuthCredentialWithPluginMock, @@ -81,30 +80,8 @@ vi.mock("./external-cli-sync.js", () => ({ existing !== incoming, })); -function createExpiredOauthStore(params: { - profileId: string; - provider: string; -}): AuthProfileStore { - return { - version: 1, - profiles: { - [params.profileId]: { - type: "oauth", - provider: params.provider, - access: "stale-access", - refresh: "stale-refresh", - expires: Date.now() - 60_000, - } satisfies OAuthCredential, - }, - }; -} - describe("OAuth refresh in-process queue", () => { - const envSnapshot = captureEnv([ - "OPENCLAW_STATE_DIR", - "OPENCLAW_AGENT_DIR", - "PI_CODING_AGENT_DIR", - ]); + const envSnapshot = captureEnv(OAUTH_AGENT_ENV_KEYS); let tempRoot = ""; let agentDir = ""; let caseIndex = 0; @@ -163,12 +140,12 @@ describe("OAuth refresh in-process queue", () => { }); const [first, second] = await Promise.all([ - resolveApiKeyForProfileInTest({ + resolveApiKeyForProfileInTest(resolveApiKeyForProfile, { store: ensureAuthProfileStore(agentDir), profileId, agentDir, }).catch((e) => e), - resolveApiKeyForProfileInTest({ + resolveApiKeyForProfileInTest(resolveApiKeyForProfile, { store: ensureAuthProfileStore(agentDir), profileId, agentDir, @@ -230,7 +207,7 @@ describe("OAuth refresh in-process queue", () => { const results = await Promise.all( Array.from({ length: 10 }, () => - resolveApiKeyForProfileInTest({ + resolveApiKeyForProfileInTest(resolveApiKeyForProfile, { store: ensureAuthProfileStore(agentDir), profileId, agentDir, diff --git a/src/agents/auth-profiles/oauth-test-utils.ts b/src/agents/auth-profiles/oauth-test-utils.ts new file mode 100644 index 00000000000..55204bb6d99 --- /dev/null +++ b/src/agents/auth-profiles/oauth-test-utils.ts @@ -0,0 +1,54 @@ +import type { resolveApiKeyForProfile } from "./oauth.js"; +import type { AuthProfileStore, OAuthCredential } from "./types.js"; + +export const OAUTH_AGENT_ENV_KEYS = [ + "OPENCLAW_STATE_DIR", + "OPENCLAW_AGENT_DIR", + "PI_CODING_AGENT_DIR", +]; + +export function resolveApiKeyForProfileInTest( + resolver: typeof resolveApiKeyForProfile, + params: Omit[0], "cfg">, +) { + return resolver({ cfg: {}, ...params }); +} + +export function oauthCred(params: { + provider: string; + access: string; + refresh: string; + expires: number; + accountId?: string; + email?: string; +}): OAuthCredential { + return { type: "oauth", ...params }; +} + +export function storeWith(profileId: string, cred: OAuthCredential): AuthProfileStore { + return { version: 1, profiles: { [profileId]: cred } }; +} + +export function createExpiredOauthStore(params: { + profileId: string; + provider: string; + access?: string; + refresh?: string; + accountId?: string; + email?: string; +}): AuthProfileStore { + return { + version: 1, + profiles: { + [params.profileId]: { + type: "oauth", + provider: params.provider, + access: params.access ?? "cached-access-token", + refresh: params.refresh ?? "refresh-token", + expires: Date.now() - 60_000, + accountId: params.accountId, + email: params.email, + } satisfies OAuthCredential, + }, + }; +} diff --git a/src/agents/auth-profiles/oauth.adopt-identity.test.ts b/src/agents/auth-profiles/oauth.adopt-identity.test.ts index 8919a782f31..744a4110c27 100644 --- a/src/agents/auth-profiles/oauth.adopt-identity.test.ts +++ b/src/agents/auth-profiles/oauth.adopt-identity.test.ts @@ -4,6 +4,12 @@ import path from "node:path"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { resetFileLockStateForTest } from "../../infra/file-lock.js"; import { captureEnv } from "../../test-utils/env.js"; +import { + OAUTH_AGENT_ENV_KEYS, + oauthCred, + resolveApiKeyForProfileInTest, + storeWith, +} from "./oauth-test-utils.js"; import { resolveApiKeyForProfile, resetOAuthRefreshQueuesForTest } from "./oauth.js"; import { clearRuntimeAuthProfileStoreSnapshots, @@ -17,12 +23,6 @@ import type { AuthProfileStore, OAuthCredential } from "./types.js"; // sub-agent store. Unit tests cover policy variants; this suite proves each // production branch refuses a mismatched accountId. -function resolveApiKeyForProfileInTest( - params: Omit[0], "cfg">, -) { - return resolveApiKeyForProfile({ cfg: {}, ...params }); -} - const { refreshProviderOAuthCredentialWithPluginMock, formatProviderAuthProfileApiKeyWithPluginMock, @@ -86,27 +86,8 @@ vi.mock("./external-cli-sync.js", () => ({ existing !== incoming, })); -function oauthCred(params: { - provider: string; - access: string; - refresh: string; - expires: number; - accountId?: string; - email?: string; -}): OAuthCredential { - return { type: "oauth", ...params }; -} - -function storeWith(profileId: string, cred: OAuthCredential): AuthProfileStore { - return { version: 1, profiles: { [profileId]: cred } }; -} - describe("OAuth credential adoption is identity-gated", () => { - const envSnapshot = captureEnv([ - "OPENCLAW_STATE_DIR", - "OPENCLAW_AGENT_DIR", - "PI_CODING_AGENT_DIR", - ]); + const envSnapshot = captureEnv(OAUTH_AGENT_ENV_KEYS); let tempRoot = ""; let caseIndex = 0; let mainAgentDir = ""; @@ -183,7 +164,7 @@ describe("OAuth credential adoption is identity-gated", () => { mainAgentDir, ); - const result = await resolveApiKeyForProfileInTest({ + const result = await resolveApiKeyForProfileInTest(resolveApiKeyForProfile, { store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, @@ -252,7 +233,7 @@ describe("OAuth credential adoption is identity-gated", () => { }) as never, ); - const result = await resolveApiKeyForProfileInTest({ + const result = await resolveApiKeyForProfileInTest(resolveApiKeyForProfile, { store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, @@ -333,7 +314,7 @@ describe("OAuth credential adoption is identity-gated", () => { }); await expect( - resolveApiKeyForProfileInTest({ + resolveApiKeyForProfileInTest(resolveApiKeyForProfile, { store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, diff --git a/src/agents/auth-profiles/oauth.concurrent-agents.test.ts b/src/agents/auth-profiles/oauth.concurrent-agents.test.ts index e64daa49e1e..bd1cd882aef 100644 --- a/src/agents/auth-profiles/oauth.concurrent-agents.test.ts +++ b/src/agents/auth-profiles/oauth.concurrent-agents.test.ts @@ -4,12 +4,17 @@ import path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { resetFileLockStateForTest } from "../../infra/file-lock.js"; import { captureEnv } from "../../test-utils/env.js"; +import { + OAUTH_AGENT_ENV_KEYS, + createExpiredOauthStore, + resolveApiKeyForProfileInTest, +} from "./oauth-test-utils.js"; import { clearRuntimeAuthProfileStoreSnapshots, ensureAuthProfileStore, saveAuthProfileStore, } from "./store.js"; -import type { AuthProfileStore, OAuthCredential } from "./types.js"; +import type { OAuthCredential } from "./types.js"; let resolveApiKeyForProfile: typeof import("./oauth.js").resolveApiKeyForProfile; let resetOAuthRefreshQueuesForTest: typeof import("./oauth.js").resetOAuthRefreshQueuesForTest; @@ -18,12 +23,6 @@ async function loadOAuthModuleForTest() { ({ resolveApiKeyForProfile, resetOAuthRefreshQueuesForTest } = await import("./oauth.js")); } -function resolveApiKeyForProfileInTest( - params: Omit[0], "cfg">, -) { - return resolveApiKeyForProfile({ cfg: {}, ...params }); -} - const { refreshProviderOAuthCredentialWithPluginMock, formatProviderAuthProfileApiKeyWithPluginMock, @@ -80,36 +79,8 @@ vi.mock("./external-cli-sync.js", () => ({ existing !== incoming, })); -function createExpiredOauthStore(params: { - profileId: string; - provider: string; - access?: string; - refresh?: string; - accountId?: string; - email?: string; -}): AuthProfileStore { - return { - version: 1, - profiles: { - [params.profileId]: { - type: "oauth", - provider: params.provider, - access: params.access ?? "cached-access-token", - refresh: params.refresh ?? "refresh-token", - expires: Date.now() - 60_000, - accountId: params.accountId, - email: params.email, - } satisfies OAuthCredential, - }, - }; -} - describe("resolveApiKeyForProfile cross-agent refresh coordination (#26322)", () => { - const envSnapshot = captureEnv([ - "OPENCLAW_STATE_DIR", - "OPENCLAW_AGENT_DIR", - "PI_CODING_AGENT_DIR", - ]); + const envSnapshot = captureEnv(OAUTH_AGENT_ENV_KEYS); let tempRoot = ""; let mainAgentDir = ""; @@ -183,7 +154,7 @@ describe("resolveApiKeyForProfile cross-agent refresh coordination (#26322)", () // performed; the remaining agents adopt the resulting fresh credentials. const results = await Promise.all( subAgents.map((agentDir) => - resolveApiKeyForProfileInTest({ + resolveApiKeyForProfileInTest(resolveApiKeyForProfile, { store: ensureAuthProfileStore(agentDir), profileId, agentDir, diff --git a/src/agents/auth-profiles/oauth.mirror-refresh.test.ts b/src/agents/auth-profiles/oauth.mirror-refresh.test.ts index 27e3b5fd42d..d28638dacb7 100644 --- a/src/agents/auth-profiles/oauth.mirror-refresh.test.ts +++ b/src/agents/auth-profiles/oauth.mirror-refresh.test.ts @@ -5,6 +5,11 @@ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } import { resetFileLockStateForTest } from "../../infra/file-lock.js"; import { captureEnv } from "../../test-utils/env.js"; import { __testing as externalAuthTesting } from "./external-auth.js"; +import { + OAUTH_AGENT_ENV_KEYS, + createExpiredOauthStore, + resolveApiKeyForProfileInTest, +} from "./oauth-test-utils.js"; import { resolveApiKeyForProfile, resetOAuthRefreshQueuesForTest } from "./oauth.js"; import { clearRuntimeAuthProfileStoreSnapshots, @@ -13,12 +18,6 @@ import { } from "./store.js"; import type { AuthProfileStore, OAuthCredential } from "./types.js"; -function resolveApiKeyForProfileInTest( - params: Omit[0], "cfg">, -) { - return resolveApiKeyForProfile({ cfg: {}, ...params }); -} - const { refreshProviderOAuthCredentialWithPluginMock, formatProviderAuthProfileApiKeyWithPluginMock, @@ -85,36 +84,8 @@ vi.mock("./external-cli-sync.js", () => ({ existing !== incoming, })); -function createExpiredOauthStore(params: { - profileId: string; - provider: string; - access?: string; - refresh?: string; - accountId?: string; - email?: string; -}): AuthProfileStore { - return { - version: 1, - profiles: { - [params.profileId]: { - type: "oauth", - provider: params.provider, - access: params.access ?? "cached-access-token", - refresh: params.refresh ?? "refresh-token", - expires: Date.now() - 60_000, - accountId: params.accountId, - email: params.email, - } satisfies OAuthCredential, - }, - }; -} - describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => { - const envSnapshot = captureEnv([ - "OPENCLAW_STATE_DIR", - "OPENCLAW_AGENT_DIR", - "PI_CODING_AGENT_DIR", - ]); + const envSnapshot = captureEnv(OAUTH_AGENT_ENV_KEYS); let tempRoot = ""; let caseIndex = 0; let mainAgentDir = ""; @@ -178,7 +149,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => }) as never, ); - const result = await resolveApiKeyForProfileInTest({ + const result = await resolveApiKeyForProfileInTest(resolveApiKeyForProfile, { store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, @@ -222,7 +193,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => // Main-agent refresh uses undefined agentDir; the mirror path is a no-op // (local == main). Just make sure the main store still reflects the refresh // and no double-write happens. - const result = await resolveApiKeyForProfileInTest({ + const result = await resolveApiKeyForProfileInTest(resolveApiKeyForProfile, { store: ensureAuthProfileStore(undefined), profileId, agentDir: undefined, @@ -274,7 +245,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => // Refresh mock intentionally left as default-undefined — it should not // be called, the pre-refresh adopt wins. - const result = await resolveApiKeyForProfileInTest({ + const result = await resolveApiKeyForProfileInTest(resolveApiKeyForProfile, { store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, @@ -337,7 +308,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => throw new Error("upstream 503 service unavailable"); }); - const result = await resolveApiKeyForProfileInTest({ + const result = await resolveApiKeyForProfileInTest(resolveApiKeyForProfile, { store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, @@ -380,7 +351,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => }) as never, ); - const result = await resolveApiKeyForProfileInTest({ + const result = await resolveApiKeyForProfileInTest(resolveApiKeyForProfile, { store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, 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 d4c352bf129..47606896fe6 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 @@ -4,6 +4,7 @@ import path from "node:path"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { resetFileLockStateForTest } from "../../infra/file-lock.js"; import { captureEnv } from "../../test-utils/env.js"; +import { OAUTH_AGENT_ENV_KEYS, createExpiredOauthStore } from "./oauth-test-utils.js"; import { clearRuntimeAuthProfileStoreSnapshots, ensureAuthProfileStore, @@ -77,36 +78,8 @@ async function readPersistedStore(agentDir: string): Promise { ) as AuthProfileStore; } -function createExpiredOauthStore(params: { - profileId: string; - provider: string; - access?: string; - refresh?: string; - accountId?: string; - email?: string; -}): AuthProfileStore { - return { - version: 1, - profiles: { - [params.profileId]: { - type: "oauth", - provider: params.provider, - access: params.access ?? "cached-access-token", - refresh: params.refresh ?? "refresh-token", - expires: Date.now() - 60_000, - accountId: params.accountId, - email: params.email, - }, - }, - }; -} - describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { - const envSnapshot = captureEnv([ - "OPENCLAW_STATE_DIR", - "OPENCLAW_AGENT_DIR", - "PI_CODING_AGENT_DIR", - ]); + const envSnapshot = captureEnv(OAUTH_AGENT_ENV_KEYS); let tempRoot = ""; let agentDir = ""; let caseIndex = 0;