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 04f6ee44c5c..fdb4f99c8ed 100644 --- a/src/agents/auth-profiles/oauth-lock-timeout-classification.test.ts +++ b/src/agents/auth-profiles/oauth-lock-timeout-classification.test.ts @@ -1,12 +1,13 @@ -import fs from "node:fs/promises"; -import os from "node:os"; 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, + createOAuthMainAgentDir, + createOAuthTestTempRoot, createExpiredOauthStore, + removeOAuthTestTempRoot, resolveApiKeyForProfileInTest, } from "./oauth-test-utils.js"; import { resolveAuthStorePath, resolveOAuthRefreshLockPath } from "./paths.js"; @@ -91,7 +92,7 @@ describe("OAuth refresh lock timeout classification", () => { let caseIndex = 0; beforeAll(async () => { - tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-oauth-lock-timeout-")); + tempRoot = await createOAuthTestTempRoot("openclaw-oauth-lock-timeout-"); ({ resolveApiKeyForProfile, resetOAuthRefreshQueuesForTest } = await import("./oauth.js")); }); @@ -102,11 +103,7 @@ describe("OAuth refresh lock timeout classification", () => { ); clearRuntimeAuthProfileStoreSnapshots(); const caseRoot = path.join(tempRoot, `case-${++caseIndex}`); - process.env.OPENCLAW_STATE_DIR = caseRoot; - agentDir = path.join(caseRoot, "agents", "main", "agent"); - process.env.OPENCLAW_AGENT_DIR = agentDir; - process.env.PI_CODING_AGENT_DIR = agentDir; - await fs.mkdir(agentDir, { recursive: true }); + agentDir = await createOAuthMainAgentDir(caseRoot); resetOAuthRefreshQueuesForTest(); }); @@ -117,7 +114,7 @@ describe("OAuth refresh lock timeout classification", () => { }); afterAll(async () => { - await fs.rm(tempRoot, { recursive: true, force: true }); + await removeOAuthTestTempRoot(tempRoot); }); it("maps only global refresh lock timeouts to refresh_contention", async () => { diff --git a/src/agents/auth-profiles/oauth-refresh-queue.test.ts b/src/agents/auth-profiles/oauth-refresh-queue.test.ts index 182f4d6df91..cd6d435c792 100644 --- a/src/agents/auth-profiles/oauth-refresh-queue.test.ts +++ b/src/agents/auth-profiles/oauth-refresh-queue.test.ts @@ -1,13 +1,15 @@ -import fs from "node:fs/promises"; -import os from "node:os"; 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, + createOAuthMainAgentDir, + createOAuthTestTempRoot, createExpiredOauthStore, + removeOAuthTestTempRoot, resolveApiKeyForProfileInTest, + resetOAuthProviderRuntimeMocks, } from "./oauth-test-utils.js"; import { resolveApiKeyForProfile, resetOAuthRefreshQueuesForTest } from "./oauth.js"; import { @@ -87,22 +89,18 @@ describe("OAuth refresh in-process queue", () => { let caseIndex = 0; beforeAll(async () => { - tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-oauth-queue-")); + tempRoot = await createOAuthTestTempRoot("openclaw-oauth-queue-"); }); beforeEach(async () => { resetFileLockStateForTest(); - refreshProviderOAuthCredentialWithPluginMock.mockReset(); - refreshProviderOAuthCredentialWithPluginMock.mockResolvedValue(undefined); - formatProviderAuthProfileApiKeyWithPluginMock.mockReset(); - formatProviderAuthProfileApiKeyWithPluginMock.mockReturnValue(undefined); + resetOAuthProviderRuntimeMocks({ + refreshProviderOAuthCredentialWithPluginMock, + formatProviderAuthProfileApiKeyWithPluginMock, + }); clearRuntimeAuthProfileStoreSnapshots(); const caseRoot = path.join(tempRoot, `case-${++caseIndex}`); - process.env.OPENCLAW_STATE_DIR = caseRoot; - agentDir = path.join(caseRoot, "agents", "main", "agent"); - process.env.OPENCLAW_AGENT_DIR = agentDir; - process.env.PI_CODING_AGENT_DIR = agentDir; - await fs.mkdir(agentDir, { recursive: true }); + agentDir = await createOAuthMainAgentDir(caseRoot); resetOAuthRefreshQueuesForTest(); }); @@ -114,7 +112,7 @@ describe("OAuth refresh in-process queue", () => { }); afterAll(async () => { - await fs.rm(tempRoot, { recursive: true, force: true }); + await removeOAuthTestTempRoot(tempRoot); }); it("releases the queue even when the refresh throws", async () => { diff --git a/src/agents/auth-profiles/oauth-test-utils.ts b/src/agents/auth-profiles/oauth-test-utils.ts index 74b0f8ac44d..cbed1f4fc46 100644 --- a/src/agents/auth-profiles/oauth-test-utils.ts +++ b/src/agents/auth-profiles/oauth-test-utils.ts @@ -1,3 +1,6 @@ +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; import type { resolveApiKeyForProfile } from "./oauth.js"; import type { AuthProfileStore, OAuthCredential } from "./types.js"; @@ -53,6 +56,47 @@ export function createExpiredOauthStore(params: { }; } +export async function createOAuthTestTempRoot(prefix: string): Promise { + return fs.mkdtemp(path.join(os.tmpdir(), prefix)); +} + +export async function createOAuthMainAgentDir(stateDir: string): Promise { + const agentDir = path.join(stateDir, "agents", "main", "agent"); + process.env.OPENCLAW_STATE_DIR = stateDir; + process.env.OPENCLAW_AGENT_DIR = agentDir; + process.env.PI_CODING_AGENT_DIR = agentDir; + await fs.mkdir(agentDir, { recursive: true }); + return agentDir; +} + +export async function removeOAuthTestTempRoot(tempRoot: string): Promise { + if (tempRoot) { + await fs.rm(tempRoot, { recursive: true, force: true }); + } +} + +type ResettableMock = { + mockReset(): unknown; +}; + +type ResolvedValueMock = ResettableMock & { + mockResolvedValue(value: unknown): unknown; +}; + +type ReturnValueMock = ResettableMock & { + mockReturnValue(value: unknown): unknown; +}; + +export function resetOAuthProviderRuntimeMocks(mocks: { + refreshProviderOAuthCredentialWithPluginMock: ResolvedValueMock; + formatProviderAuthProfileApiKeyWithPluginMock: ReturnValueMock; +}): void { + mocks.refreshProviderOAuthCredentialWithPluginMock.mockReset(); + mocks.refreshProviderOAuthCredentialWithPluginMock.mockResolvedValue(undefined); + mocks.formatProviderAuthProfileApiKeyWithPluginMock.mockReset(); + mocks.formatProviderAuthProfileApiKeyWithPluginMock.mockReturnValue(undefined); +} + export function makeSeededRandom(seed: number): () => number { let state = seed >>> 0; return () => { diff --git a/src/agents/auth-profiles/oauth.adopt-identity.test.ts b/src/agents/auth-profiles/oauth.adopt-identity.test.ts index 744a4110c27..bda95c77ebf 100644 --- a/src/agents/auth-profiles/oauth.adopt-identity.test.ts +++ b/src/agents/auth-profiles/oauth.adopt-identity.test.ts @@ -1,13 +1,16 @@ import fs from "node:fs/promises"; -import os from "node:os"; 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, + createOAuthMainAgentDir, + createOAuthTestTempRoot, oauthCred, + removeOAuthTestTempRoot, resolveApiKeyForProfileInTest, + resetOAuthProviderRuntimeMocks, storeWith, } from "./oauth-test-utils.js"; import { resolveApiKeyForProfile, resetOAuthRefreshQueuesForTest } from "./oauth.js"; @@ -93,23 +96,19 @@ describe("OAuth credential adoption is identity-gated", () => { let mainAgentDir = ""; beforeAll(async () => { - tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-oauth-adopt-identity-")); + tempRoot = await createOAuthTestTempRoot("openclaw-oauth-adopt-identity-"); }); beforeEach(async () => { resetFileLockStateForTest(); - refreshProviderOAuthCredentialWithPluginMock.mockReset(); - refreshProviderOAuthCredentialWithPluginMock.mockResolvedValue(undefined); - formatProviderAuthProfileApiKeyWithPluginMock.mockReset(); - formatProviderAuthProfileApiKeyWithPluginMock.mockReturnValue(undefined); + resetOAuthProviderRuntimeMocks({ + refreshProviderOAuthCredentialWithPluginMock, + formatProviderAuthProfileApiKeyWithPluginMock, + }); clearRuntimeAuthProfileStoreSnapshots(); caseIndex += 1; const caseRoot = path.join(tempRoot, `case-${caseIndex}`); - process.env.OPENCLAW_STATE_DIR = caseRoot; - mainAgentDir = path.join(caseRoot, "agents", "main", "agent"); - process.env.OPENCLAW_AGENT_DIR = mainAgentDir; - process.env.PI_CODING_AGENT_DIR = mainAgentDir; - await fs.mkdir(mainAgentDir, { recursive: true }); + mainAgentDir = await createOAuthMainAgentDir(caseRoot); resetOAuthRefreshQueuesForTest(); }); @@ -121,9 +120,7 @@ describe("OAuth credential adoption is identity-gated", () => { }); afterAll(async () => { - if (tempRoot) { - await fs.rm(tempRoot, { recursive: true, force: true }); - } + await removeOAuthTestTempRoot(tempRoot); }); it("adoptNewerMainOAuthCredential refuses to adopt across accountId mismatch (pre-refresh path)", async () => { diff --git a/src/agents/auth-profiles/oauth.concurrent-agents.test.ts b/src/agents/auth-profiles/oauth.concurrent-agents.test.ts index bd1cd882aef..42bed5f0d33 100644 --- a/src/agents/auth-profiles/oauth.concurrent-agents.test.ts +++ b/src/agents/auth-profiles/oauth.concurrent-agents.test.ts @@ -1,13 +1,16 @@ import fs from "node:fs/promises"; -import os from "node:os"; 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, + createOAuthMainAgentDir, + createOAuthTestTempRoot, createExpiredOauthStore, + removeOAuthTestTempRoot, resolveApiKeyForProfileInTest, + resetOAuthProviderRuntimeMocks, } from "./oauth-test-utils.js"; import { clearRuntimeAuthProfileStoreSnapshots, @@ -86,17 +89,13 @@ describe("resolveApiKeyForProfile cross-agent refresh coordination (#26322)", () beforeEach(async () => { resetFileLockStateForTest(); - refreshProviderOAuthCredentialWithPluginMock.mockReset(); - refreshProviderOAuthCredentialWithPluginMock.mockResolvedValue(undefined); - formatProviderAuthProfileApiKeyWithPluginMock.mockReset(); - formatProviderAuthProfileApiKeyWithPluginMock.mockReturnValue(undefined); + resetOAuthProviderRuntimeMocks({ + refreshProviderOAuthCredentialWithPluginMock, + formatProviderAuthProfileApiKeyWithPluginMock, + }); clearRuntimeAuthProfileStoreSnapshots(); - 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 }); + tempRoot = await createOAuthTestTempRoot("openclaw-oauth-concurrent-"); + mainAgentDir = await createOAuthMainAgentDir(tempRoot); await loadOAuthModuleForTest(); // Drop any refresh-queue entries left behind by a prior timed-out test. resetOAuthRefreshQueuesForTest(); @@ -109,9 +108,7 @@ describe("resolveApiKeyForProfile cross-agent refresh coordination (#26322)", () if (resetOAuthRefreshQueuesForTest) { resetOAuthRefreshQueuesForTest(); } - if (tempRoot) { - await fs.rm(tempRoot, { recursive: true, force: true }); - } + await removeOAuthTestTempRoot(tempRoot); }); it("refreshes exactly once when many agents share one OAuth profile and all race on expiry", async () => { diff --git a/src/agents/auth-profiles/oauth.mirror-refresh.test.ts b/src/agents/auth-profiles/oauth.mirror-refresh.test.ts index d28638dacb7..91be35af7cf 100644 --- a/src/agents/auth-profiles/oauth.mirror-refresh.test.ts +++ b/src/agents/auth-profiles/oauth.mirror-refresh.test.ts @@ -1,5 +1,4 @@ import fs from "node:fs/promises"; -import os from "node:os"; import path from "node:path"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { resetFileLockStateForTest } from "../../infra/file-lock.js"; @@ -7,8 +6,12 @@ import { captureEnv } from "../../test-utils/env.js"; import { __testing as externalAuthTesting } from "./external-auth.js"; import { OAUTH_AGENT_ENV_KEYS, + createOAuthMainAgentDir, + createOAuthTestTempRoot, createExpiredOauthStore, + removeOAuthTestTempRoot, resolveApiKeyForProfileInTest, + resetOAuthProviderRuntimeMocks, } from "./oauth-test-utils.js"; import { resolveApiKeyForProfile, resetOAuthRefreshQueuesForTest } from "./oauth.js"; import { @@ -91,24 +94,20 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => let mainAgentDir = ""; beforeAll(async () => { - tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-oauth-mirror-")); + tempRoot = await createOAuthTestTempRoot("openclaw-oauth-mirror-"); }); beforeEach(async () => { resetFileLockStateForTest(); - refreshProviderOAuthCredentialWithPluginMock.mockReset(); - refreshProviderOAuthCredentialWithPluginMock.mockResolvedValue(undefined); - formatProviderAuthProfileApiKeyWithPluginMock.mockReset(); - formatProviderAuthProfileApiKeyWithPluginMock.mockReturnValue(undefined); + resetOAuthProviderRuntimeMocks({ + refreshProviderOAuthCredentialWithPluginMock, + formatProviderAuthProfileApiKeyWithPluginMock, + }); externalAuthTesting.setResolveExternalAuthProfilesForTest(() => []); clearRuntimeAuthProfileStoreSnapshots(); caseIndex += 1; const caseRoot = path.join(tempRoot, `case-${caseIndex}`); - process.env.OPENCLAW_STATE_DIR = caseRoot; - mainAgentDir = path.join(caseRoot, "agents", "main", "agent"); - process.env.OPENCLAW_AGENT_DIR = mainAgentDir; - process.env.PI_CODING_AGENT_DIR = mainAgentDir; - await fs.mkdir(mainAgentDir, { recursive: true }); + mainAgentDir = await createOAuthMainAgentDir(caseRoot); resetOAuthRefreshQueuesForTest(); }); @@ -121,9 +120,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => }); afterAll(async () => { - if (tempRoot) { - await fs.rm(tempRoot, { recursive: true, force: true }); - } + await removeOAuthTestTempRoot(tempRoot); }); it("mirrors refreshed credentials into the main store so peers skip refresh", async () => {