From 89d3117ad0de2b085dd3f0612f4f4349214ca932 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 17 Apr 2026 18:05:01 +0100 Subject: [PATCH] test: narrow auth profile runtime mocks --- .../oauth.adopt-identity.test.ts | 52 +++++++++++++++---- .../oauth.concurrent-20-agents.test.ts | 29 +++++++++-- .../oauth.mirror-refresh.test.ts | 48 ++++++++++++----- src/agents/auth-profiles/order.test.ts | 5 ++ 4 files changed, 107 insertions(+), 27 deletions(-) diff --git a/src/agents/auth-profiles/oauth.adopt-identity.test.ts b/src/agents/auth-profiles/oauth.adopt-identity.test.ts index a07980940f4..75eb8273faf 100644 --- a/src/agents/auth-profiles/oauth.adopt-identity.test.ts +++ b/src/agents/auth-profiles/oauth.adopt-identity.test.ts @@ -25,6 +25,12 @@ async function loadOAuthModuleForTest() { ({ resolveApiKeyForProfile, resetOAuthRefreshQueuesForTest } = await import("./oauth.js")); } +function resolveApiKeyForProfileInTest( + params: Omit[0], "cfg">, +) { + return resolveApiKeyForProfile({ cfg: {}, ...params }); +} + const { refreshProviderOAuthCredentialWithPluginMock, formatProviderAuthProfileApiKeyWithPluginMock, @@ -42,12 +48,32 @@ vi.mock("../cli-credentials.js", () => ({ writeCodexCliCredentials: () => true, })); +vi.mock("@mariozechner/pi-ai/oauth", () => ({ + getOAuthApiKey: vi.fn(async () => null), + getOAuthProviders: () => [{ id: "openai-codex" }, { id: "anthropic" }], +})); + vi.mock("../../plugins/provider-runtime.runtime.js", () => ({ formatProviderAuthProfileApiKeyWithPlugin: (params: { context?: { access?: string } }) => formatProviderAuthProfileApiKeyWithPluginMock() ?? params?.context?.access, refreshProviderOAuthCredentialWithPlugin: refreshProviderOAuthCredentialWithPluginMock, })); +vi.mock("../../infra/file-lock.js", () => ({ + resetFileLockStateForTest: () => undefined, + withFileLock: async (_filePath: string, _options: unknown, run: () => Promise) => run(), +})); + +vi.mock("../../plugin-sdk/file-lock.js", () => ({ + resetFileLockStateForTest: () => undefined, + withFileLock: async (_filePath: string, _options: unknown, run: () => Promise) => run(), +})); + +vi.mock("./external-auth.js", () => ({ + overlayExternalAuthProfiles: (store: T) => store, + shouldPersistExternalAuthProfile: () => true, +})); + vi.mock("./doctor.js", () => ({ formatAuthDoctorHint: async () => undefined, })); @@ -74,7 +100,11 @@ function storeWith(profileId: string, cred: OAuthCredential): AuthProfileStore { } describe("OAuth credential adoption is identity-gated", () => { - const envSnapshot = captureEnv(["OPENCLAW_STATE_DIR"]); + const envSnapshot = captureEnv([ + "OPENCLAW_STATE_DIR", + "OPENCLAW_AGENT_DIR", + "PI_CODING_AGENT_DIR", + ]); let tempRoot = ""; let mainAgentDir = ""; @@ -88,6 +118,8 @@ describe("OAuth credential adoption is identity-gated", () => { tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-oauth-adopt-identity-")); process.env.OPENCLAW_STATE_DIR = tempRoot; mainAgentDir = path.join(tempRoot, "agents", "main", "agent"); + process.env.OPENCLAW_AGENT_DIR = mainAgentDir; + process.env.PI_CODING_AGENT_DIR = mainAgentDir; await fs.mkdir(mainAgentDir, { recursive: true }); await loadOAuthModuleForTest(); resetOAuthRefreshQueuesForTest(); @@ -143,7 +175,7 @@ describe("OAuth credential adoption is identity-gated", () => { mainAgentDir, ); - const result = await resolveApiKeyForProfile({ + const result = await resolveApiKeyForProfileInTest({ store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, @@ -212,7 +244,7 @@ describe("OAuth credential adoption is identity-gated", () => { }) as never, ); - const result = await resolveApiKeyForProfile({ + const result = await resolveApiKeyForProfileInTest({ store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, @@ -274,7 +306,7 @@ describe("OAuth credential adoption is identity-gated", () => { mainAgentDir, ); - const result = await resolveApiKeyForProfile({ + const result = await resolveApiKeyForProfileInTest({ store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, @@ -331,7 +363,7 @@ describe("OAuth credential adoption is identity-gated", () => { // Plugin refresh must NOT be called — sub should adopt main's fresh // cred rather than performing its own refresh. - const result = await resolveApiKeyForProfile({ + const result = await resolveApiKeyForProfileInTest({ store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, @@ -380,7 +412,7 @@ describe("OAuth credential adoption is identity-gated", () => { mainAgentDir, ); - const result = await resolveApiKeyForProfile({ + const result = await resolveApiKeyForProfileInTest({ store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, @@ -444,7 +476,7 @@ describe("OAuth credential adoption is identity-gated", () => { }) as never, ); - const result = await resolveApiKeyForProfile({ + const result = await resolveApiKeyForProfileInTest({ store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, @@ -511,7 +543,7 @@ describe("OAuth credential adoption is identity-gated", () => { // Catch-block main-inherit must refuse the non-overlapping cred and // propagate the original error rather than leaking main's credential. await expect( - resolveApiKeyForProfile({ + resolveApiKeyForProfileInTest({ store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, @@ -574,7 +606,7 @@ describe("OAuth credential adoption is identity-gated", () => { throw new Error("upstream 503 service unavailable"); }); - const result = await resolveApiKeyForProfile({ + const result = await resolveApiKeyForProfileInTest({ store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, @@ -643,7 +675,7 @@ describe("OAuth credential adoption is identity-gated", () => { }); await expect( - resolveApiKeyForProfile({ + resolveApiKeyForProfileInTest({ store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, diff --git a/src/agents/auth-profiles/oauth.concurrent-20-agents.test.ts b/src/agents/auth-profiles/oauth.concurrent-20-agents.test.ts index c4836e84e31..e89a2e609d0 100644 --- a/src/agents/auth-profiles/oauth.concurrent-20-agents.test.ts +++ b/src/agents/auth-profiles/oauth.concurrent-20-agents.test.ts @@ -18,6 +18,12 @@ async function loadOAuthModuleForTest() { ({ resolveApiKeyForProfile, resetOAuthRefreshQueuesForTest } = await import("./oauth.js")); } +function resolveApiKeyForProfileInTest( + params: Omit[0], "cfg">, +) { + return resolveApiKeyForProfile({ cfg: {}, ...params }); +} + const { refreshProviderOAuthCredentialWithPluginMock, formatProviderAuthProfileApiKeyWithPluginMock, @@ -35,12 +41,22 @@ vi.mock("../cli-credentials.js", () => ({ writeCodexCliCredentials: () => true, })); +vi.mock("@mariozechner/pi-ai/oauth", () => ({ + getOAuthApiKey: vi.fn(async () => null), + getOAuthProviders: () => [{ id: "openai-codex" }], +})); + vi.mock("../../plugins/provider-runtime.runtime.js", () => ({ formatProviderAuthProfileApiKeyWithPlugin: (params: { context?: { access?: string } }) => formatProviderAuthProfileApiKeyWithPluginMock() ?? params?.context?.access, refreshProviderOAuthCredentialWithPlugin: refreshProviderOAuthCredentialWithPluginMock, })); +vi.mock("./external-auth.js", () => ({ + overlayExternalAuthProfiles: (store: T) => store, + shouldPersistExternalAuthProfile: () => true, +})); + vi.mock("./doctor.js", () => ({ formatAuthDoctorHint: async () => undefined, })); @@ -79,7 +95,11 @@ function createExpiredOauthStore(params: { } describe("resolveApiKeyForProfile cross-agent refresh coordination (#26322)", () => { - const envSnapshot = captureEnv(["OPENCLAW_STATE_DIR"]); + const envSnapshot = captureEnv([ + "OPENCLAW_STATE_DIR", + "OPENCLAW_AGENT_DIR", + "PI_CODING_AGENT_DIR", + ]); let tempRoot = ""; let mainAgentDir = ""; @@ -93,6 +113,8 @@ describe("resolveApiKeyForProfile cross-agent refresh coordination (#26322)", () tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-oauth-concurrent-")); process.env.OPENCLAW_STATE_DIR = tempRoot; mainAgentDir = path.join(tempRoot, "agents", "main", "agent"); + process.env.OPENCLAW_AGENT_DIR = mainAgentDir; + process.env.PI_CODING_AGENT_DIR = mainAgentDir; await fs.mkdir(mainAgentDir, { recursive: true }); await loadOAuthModuleForTest(); // Drop any refresh-queue entries left behind by a prior timed-out test. @@ -150,7 +172,7 @@ describe("resolveApiKeyForProfile cross-agent refresh coordination (#26322)", () // performed; the remaining 19 adopt the resulting fresh credentials. const results = await Promise.all( subAgents.map((agentDir) => - resolveApiKeyForProfile({ + resolveApiKeyForProfileInTest({ store: ensureAuthProfileStore(agentDir), profileId, agentDir, @@ -165,6 +187,5 @@ describe("resolveApiKeyForProfile cross-agent refresh coordination (#26322)", () expect(result?.apiKey).toBe("cross-agent-refreshed-access"); expect(result?.provider).toBe(provider); } - }, // Generous timeout; the fix should complete well under 5s in practice. - 60_000); + }, 60_000); // Generous timeout; the fix should complete well under 5s in practice. }); diff --git a/src/agents/auth-profiles/oauth.mirror-refresh.test.ts b/src/agents/auth-profiles/oauth.mirror-refresh.test.ts index a3531cd70ed..f9d7d87c663 100644 --- a/src/agents/auth-profiles/oauth.mirror-refresh.test.ts +++ b/src/agents/auth-profiles/oauth.mirror-refresh.test.ts @@ -19,6 +19,12 @@ async function loadOAuthModuleForTest() { ({ resolveApiKeyForProfile, resetOAuthRefreshQueuesForTest } = await import("./oauth.js")); } +function resolveApiKeyForProfileInTest( + params: Omit[0], "cfg">, +) { + return resolveApiKeyForProfile({ cfg: {}, ...params }); +} + const { refreshProviderOAuthCredentialWithPluginMock, formatProviderAuthProfileApiKeyWithPluginMock, @@ -55,6 +61,16 @@ vi.mock("../../plugins/provider-runtime.runtime.js", () => ({ refreshProviderOAuthCredentialWithPlugin: refreshProviderOAuthCredentialWithPluginMock, })); +vi.mock("../../infra/file-lock.js", () => ({ + resetFileLockStateForTest: () => undefined, + withFileLock: async (_filePath: string, _options: unknown, run: () => Promise) => run(), +})); + +vi.mock("../../plugin-sdk/file-lock.js", () => ({ + resetFileLockStateForTest: () => undefined, + withFileLock: async (_filePath: string, _options: unknown, run: () => Promise) => run(), +})); + vi.mock("./doctor.js", () => ({ formatAuthDoctorHint: async () => undefined, })); @@ -90,7 +106,11 @@ function createExpiredOauthStore(params: { } describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => { - const envSnapshot = captureEnv(["OPENCLAW_STATE_DIR"]); + const envSnapshot = captureEnv([ + "OPENCLAW_STATE_DIR", + "OPENCLAW_AGENT_DIR", + "PI_CODING_AGENT_DIR", + ]); let tempRoot = ""; let mainAgentDir = ""; @@ -105,6 +125,8 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-oauth-mirror-")); process.env.OPENCLAW_STATE_DIR = tempRoot; mainAgentDir = path.join(tempRoot, "agents", "main", "agent"); + process.env.OPENCLAW_AGENT_DIR = mainAgentDir; + process.env.PI_CODING_AGENT_DIR = mainAgentDir; await fs.mkdir(mainAgentDir, { recursive: true }); await loadOAuthModuleForTest(); resetOAuthRefreshQueuesForTest(); @@ -146,7 +168,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => }) as never, ); - const result = await resolveApiKeyForProfile({ + const result = await resolveApiKeyForProfileInTest({ store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, @@ -190,7 +212,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 resolveApiKeyForProfile({ + const result = await resolveApiKeyForProfileInTest({ store: ensureAuthProfileStore(undefined), profileId, agentDir: undefined, @@ -244,7 +266,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => }) as never, ); - const result = await resolveApiKeyForProfile({ + const result = await resolveApiKeyForProfileInTest({ store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, @@ -310,7 +332,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => }) as never, ); - const result = await resolveApiKeyForProfile({ + const result = await resolveApiKeyForProfileInTest({ store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, @@ -375,7 +397,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => // The sub-agent will actually adopt main's fresher creds via the inside- // lock recheck (that's the whole point of #26322), so refresh may not // even fire. We only care that the main store is not regressed. - await resolveApiKeyForProfile({ + await resolveApiKeyForProfileInTest({ store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, @@ -431,7 +453,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => }) as never, ); - const result = await resolveApiKeyForProfile({ + const result = await resolveApiKeyForProfileInTest({ store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, @@ -489,7 +511,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => }) as never, ); - await resolveApiKeyForProfile({ + await resolveApiKeyForProfileInTest({ store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, @@ -538,7 +560,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 resolveApiKeyForProfile({ + const result = await resolveApiKeyForProfileInTest({ store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, @@ -601,7 +623,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => throw new Error("upstream 503 service unavailable"); }); - const result = await resolveApiKeyForProfile({ + const result = await resolveApiKeyForProfileInTest({ store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, @@ -665,7 +687,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => }) as never, ); - const result = await resolveApiKeyForProfile({ + const result = await resolveApiKeyForProfileInTest({ store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, @@ -723,7 +745,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => }) as never, ); - await resolveApiKeyForProfile({ + await resolveApiKeyForProfileInTest({ store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, @@ -763,7 +785,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => }) as never, ); - const result = await resolveApiKeyForProfile({ + const result = await resolveApiKeyForProfileInTest({ store: ensureAuthProfileStore(subAgentDir), profileId, agentDir: subAgentDir, diff --git a/src/agents/auth-profiles/order.test.ts b/src/agents/auth-profiles/order.test.ts index e1e9345f309..1aeb0afc3ab 100644 --- a/src/agents/auth-profiles/order.test.ts +++ b/src/agents/auth-profiles/order.test.ts @@ -23,6 +23,11 @@ vi.mock("../../plugins/manifest-registry.js", () => ({ loadPluginManifestRegistry, })); +vi.mock("./external-auth.js", () => ({ + overlayExternalAuthProfiles: (store: T) => store, + shouldPersistExternalAuthProfile: () => true, +})); + describe("resolveAuthProfileOrder", () => { beforeEach(() => { loadPluginManifestRegistry.mockClear();