openclaw/src/agents/auth-profiles.store-cache.t...

162 lines
5.4 KiB
TypeScript

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { AUTH_STORE_VERSION, EXTERNAL_CLI_SYNC_TTL_MS } from "./auth-profiles/constants.js";
import type { AuthProfileStore } from "./auth-profiles/types.js";
const mocks = vi.hoisted(() => ({
syncExternalCliCredentials: vi.fn((_: AuthProfileStore) => false),
}));
vi.mock("./auth-profiles/external-cli-sync.js", () => ({
syncExternalCliCredentials: mocks.syncExternalCliCredentials,
}));
let clearRuntimeAuthProfileStoreSnapshots: typeof import("./auth-profiles.js").clearRuntimeAuthProfileStoreSnapshots;
let ensureAuthProfileStore: typeof import("./auth-profiles.js").ensureAuthProfileStore;
async function loadFreshAuthProfilesModuleForTest() {
vi.resetModules();
({ clearRuntimeAuthProfileStoreSnapshots, ensureAuthProfileStore } =
await import("./auth-profiles.js"));
}
function withAgentDirEnv(prefix: string, run: (agentDir: string) => void | Promise<void>) {
const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
const previousAgentDir = process.env.OPENCLAW_AGENT_DIR;
const previousPiAgentDir = process.env.PI_CODING_AGENT_DIR;
try {
process.env.OPENCLAW_AGENT_DIR = agentDir;
process.env.PI_CODING_AGENT_DIR = agentDir;
return run(agentDir);
} finally {
if (previousAgentDir === undefined) {
delete process.env.OPENCLAW_AGENT_DIR;
} else {
process.env.OPENCLAW_AGENT_DIR = previousAgentDir;
}
if (previousPiAgentDir === undefined) {
delete process.env.PI_CODING_AGENT_DIR;
} else {
process.env.PI_CODING_AGENT_DIR = previousPiAgentDir;
}
fs.rmSync(agentDir, { recursive: true, force: true });
}
}
function writeAuthStore(agentDir: string, key: string) {
const authPath = path.join(agentDir, "auth-profiles.json");
fs.writeFileSync(
authPath,
`${JSON.stringify(
{
version: AUTH_STORE_VERSION,
profiles: {
"openai:default": {
type: "api_key",
provider: "openai",
key,
},
},
},
null,
2,
)}\n`,
"utf8",
);
return authPath;
}
describe("auth profile store cache", () => {
beforeEach(async () => {
await loadFreshAuthProfilesModuleForTest();
});
afterEach(() => {
vi.useRealTimers();
clearRuntimeAuthProfileStoreSnapshots();
vi.clearAllMocks();
});
it("reuses the synced auth store while auth-profiles.json is unchanged", async () => {
await withAgentDirEnv("openclaw-auth-store-cache-", (agentDir) => {
writeAuthStore(agentDir, "sk-test");
ensureAuthProfileStore(agentDir);
ensureAuthProfileStore(agentDir);
expect(mocks.syncExternalCliCredentials).toHaveBeenCalledTimes(1);
});
});
it("refreshes the cached auth store after auth-profiles.json changes", async () => {
await withAgentDirEnv("openclaw-auth-store-refresh-", async (agentDir) => {
const authPath = writeAuthStore(agentDir, "sk-test-1");
ensureAuthProfileStore(agentDir);
writeAuthStore(agentDir, "sk-test-2");
const bumpedMtime = new Date(Date.now() + 2_000);
fs.utimesSync(authPath, bumpedMtime, bumpedMtime);
const reloaded = ensureAuthProfileStore(agentDir);
expect(mocks.syncExternalCliCredentials).toHaveBeenCalledTimes(2);
expect(reloaded.profiles["openai:default"]).toMatchObject({
key: "sk-test-2",
});
});
});
it("re-syncs external CLI credentials after the cache ttl when auth-profiles.json is absent", () => {
const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-store-missing-"));
const previousAgentDir = process.env.OPENCLAW_AGENT_DIR;
const previousPiAgentDir = process.env.PI_CODING_AGENT_DIR;
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-03-21T15:00:00.000Z"));
let syncCount = 0;
mocks.syncExternalCliCredentials.mockImplementation((store) => {
syncCount += 1;
store.profiles["openai-codex:default"] = {
type: "oauth",
provider: "openai-codex",
access: `access-${syncCount}`,
refresh: `refresh-${syncCount}`,
expires: Date.now() + 60_000,
};
return true;
});
try {
process.env.OPENCLAW_AGENT_DIR = agentDir;
process.env.PI_CODING_AGENT_DIR = agentDir;
const first = ensureAuthProfileStore(agentDir);
const second = ensureAuthProfileStore(agentDir);
expect(first.profiles["openai-codex:default"]).toMatchObject({ access: "access-1" });
expect(second.profiles["openai-codex:default"]).toMatchObject({ access: "access-1" });
expect(mocks.syncExternalCliCredentials).toHaveBeenCalledTimes(1);
vi.advanceTimersByTime(EXTERNAL_CLI_SYNC_TTL_MS + 1);
const third = ensureAuthProfileStore(agentDir);
expect(mocks.syncExternalCliCredentials).toHaveBeenCalledTimes(2);
expect(third.profiles["openai-codex:default"]).toMatchObject({ access: "access-2" });
} finally {
if (previousAgentDir === undefined) {
delete process.env.OPENCLAW_AGENT_DIR;
} else {
process.env.OPENCLAW_AGENT_DIR = previousAgentDir;
}
if (previousPiAgentDir === undefined) {
delete process.env.PI_CODING_AGENT_DIR;
} else {
process.env.PI_CODING_AGENT_DIR = previousPiAgentDir;
}
fs.rmSync(agentDir, { recursive: true, force: true });
}
});
});