test: share oauth workspace helpers

This commit is contained in:
Peter Steinberger
2026-04-18 23:32:16 +01:00
parent 73728127b6
commit 7bc3019691
6 changed files with 94 additions and 64 deletions

View File

@@ -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 () => {

View File

@@ -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 () => {

View File

@@ -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<string> {
return fs.mkdtemp(path.join(os.tmpdir(), prefix));
}
export async function createOAuthMainAgentDir(stateDir: string): Promise<string> {
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<void> {
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 () => {

View File

@@ -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 () => {

View File

@@ -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 () => {

View File

@@ -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 () => {