mirror of https://github.com/openclaw/openclaw.git
162 lines
5.4 KiB
TypeScript
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 });
|
|
}
|
|
});
|
|
});
|