openskynet / src /agents /auth-profiles /oauth.openai-codex-refresh-fallback.test.ts
Darochin's picture
Mirror OpenSkyNet workspace snapshot from Git HEAD
fc93158 verified
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 { captureEnv } from "../../test-utils/env.js";
import { resolveApiKeyForProfile } from "./oauth.js";
import {
clearRuntimeAuthProfileStoreSnapshots,
ensureAuthProfileStore,
saveAuthProfileStore,
} from "./store.js";
import type { AuthProfileStore } from "./types.js";
const { getOAuthApiKeyMock } = vi.hoisted(() => ({
getOAuthApiKeyMock: vi.fn(async () => {
throw new Error("Failed to extract accountId from token");
}),
}));
vi.mock("@mariozechner/pi-ai/oauth", () => ({
getOAuthApiKey: getOAuthApiKeyMock,
getOAuthProviders: () => [
{ id: "openai-codex", envApiKey: "OPENAI_API_KEY", oauthTokenEnv: "OPENAI_OAUTH_TOKEN" }, // pragma: allowlist secret
{ id: "anthropic", envApiKey: "ANTHROPIC_API_KEY", oauthTokenEnv: "ANTHROPIC_OAUTH_TOKEN" }, // pragma: allowlist secret
],
}));
function createExpiredOauthStore(params: {
profileId: string;
provider: string;
access?: string;
}): AuthProfileStore {
return {
version: 1,
profiles: {
[params.profileId]: {
type: "oauth",
provider: params.provider,
access: params.access ?? "cached-access-token",
refresh: "refresh-token",
expires: Date.now() - 60_000,
},
},
};
}
describe("resolveApiKeyForProfile openai-codex refresh fallback", () => {
const envSnapshot = captureEnv([
"OPENCLAW_STATE_DIR",
"OPENCLAW_AGENT_DIR",
"PI_CODING_AGENT_DIR",
]);
let tempRoot = "";
let agentDir = "";
beforeEach(async () => {
getOAuthApiKeyMock.mockClear();
clearRuntimeAuthProfileStoreSnapshots();
tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-refresh-fallback-"));
agentDir = path.join(tempRoot, "agents", "main", "agent");
await fs.mkdir(agentDir, { recursive: true });
process.env.OPENCLAW_STATE_DIR = tempRoot;
process.env.OPENCLAW_AGENT_DIR = agentDir;
process.env.PI_CODING_AGENT_DIR = agentDir;
});
afterEach(async () => {
clearRuntimeAuthProfileStoreSnapshots();
envSnapshot.restore();
await fs.rm(tempRoot, { recursive: true, force: true });
});
it("falls back to cached access token when openai-codex refresh fails on accountId extraction", async () => {
const profileId = "openai-codex:default";
saveAuthProfileStore(
createExpiredOauthStore({
profileId,
provider: "openai-codex",
}),
agentDir,
);
const result = await resolveApiKeyForProfile({
store: ensureAuthProfileStore(agentDir),
profileId,
agentDir,
});
expect(result).toEqual({
apiKey: "cached-access-token", // pragma: allowlist secret
provider: "openai-codex",
email: undefined,
});
expect(getOAuthApiKeyMock).toHaveBeenCalledTimes(1);
});
it("keeps throwing for non-codex providers on the same refresh error", async () => {
const profileId = "anthropic:default";
saveAuthProfileStore(
createExpiredOauthStore({
profileId,
provider: "anthropic",
}),
agentDir,
);
await expect(
resolveApiKeyForProfile({
store: ensureAuthProfileStore(agentDir),
profileId,
agentDir,
}),
).rejects.toThrow(/OAuth token refresh failed for anthropic/);
});
it("does not use fallback for unrelated openai-codex refresh errors", async () => {
const profileId = "openai-codex:default";
saveAuthProfileStore(
createExpiredOauthStore({
profileId,
provider: "openai-codex",
}),
agentDir,
);
getOAuthApiKeyMock.mockImplementationOnce(async () => {
throw new Error("invalid_grant");
});
await expect(
resolveApiKeyForProfile({
store: ensureAuthProfileStore(agentDir),
profileId,
agentDir,
}),
).rejects.toThrow(/OAuth token refresh failed for openai-codex/);
});
});