Files
openclaw/src/agents/auth-profiles.store-cache.test.ts
2026-04-24 08:45:26 +01:00

142 lines
4.6 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 } from "./auth-profiles/constants.js";
import {
clearRuntimeAuthProfileStoreSnapshots,
ensureAuthProfileStore,
} from "./auth-profiles/store.js";
import type { OAuthCredential } from "./auth-profiles/types.js";
type RuntimeOnlyOverlay = { profileId: string; credential: OAuthCredential };
const mocks = vi.hoisted(() => ({
resolveExternalCliAuthProfiles: vi.fn<() => RuntimeOnlyOverlay[]>(() => []),
}));
vi.mock("./auth-profiles/external-cli-sync.js", () => ({
resolveExternalCliAuthProfiles: mocks.resolveExternalCliAuthProfiles,
}));
vi.mock("../plugins/provider-runtime.js", () => ({
resolveExternalAuthProfilesWithPlugins: () => [],
}));
async 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;
await 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(() => {
clearRuntimeAuthProfileStoreSnapshots();
mocks.resolveExternalCliAuthProfiles.mockReset();
mocks.resolveExternalCliAuthProfiles.mockReturnValue([]);
});
afterEach(() => {
vi.useRealTimers();
clearRuntimeAuthProfileStoreSnapshots();
});
function createRuntimeOnlyOverlay(access: string): RuntimeOnlyOverlay {
return {
profileId: "openai-codex:default",
credential: {
type: "oauth",
provider: "openai-codex",
access,
refresh: `refresh-${access}`,
expires: Date.now() + 60_000,
},
};
}
it("recomputes runtime-only external auth overlays even while the base store is cached", async () => {
await withAgentDirEnv("openclaw-auth-store-cache-", (agentDir) => {
writeAuthStore(agentDir, "sk-test");
mocks.resolveExternalCliAuthProfiles
.mockReturnValueOnce([createRuntimeOnlyOverlay("access-1")])
.mockReturnValueOnce([createRuntimeOnlyOverlay("access-2")]);
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-2" });
expect(mocks.resolveExternalCliAuthProfiles).toHaveBeenCalledTimes(2);
});
});
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(reloaded.profiles["openai:default"]).toMatchObject({
key: "sk-test-2",
});
});
});
it("keeps runtime-only external auth out of persisted auth-profiles.json files", async () => {
mocks.resolveExternalCliAuthProfiles.mockReturnValue([createRuntimeOnlyOverlay("access-1")]);
await withAgentDirEnv("openclaw-auth-store-missing-", (agentDir) => {
const store = ensureAuthProfileStore(agentDir);
expect(store.profiles["openai-codex:default"]).toMatchObject({ access: "access-1" });
expect(fs.existsSync(path.join(agentDir, "auth-profiles.json"))).toBe(false);
});
});
});