test: narrow auth profile runtime mocks

This commit is contained in:
Peter Steinberger
2026-04-17 18:05:01 +01:00
parent 42817a1707
commit 89d3117ad0
4 changed files with 107 additions and 27 deletions

View File

@@ -25,6 +25,12 @@ async function loadOAuthModuleForTest() {
({ resolveApiKeyForProfile, resetOAuthRefreshQueuesForTest } = await import("./oauth.js"));
}
function resolveApiKeyForProfileInTest(
params: Omit<Parameters<typeof resolveApiKeyForProfile>[0], "cfg">,
) {
return resolveApiKeyForProfile({ cfg: {}, ...params });
}
const {
refreshProviderOAuthCredentialWithPluginMock,
formatProviderAuthProfileApiKeyWithPluginMock,
@@ -42,12 +48,32 @@ vi.mock("../cli-credentials.js", () => ({
writeCodexCliCredentials: () => true,
}));
vi.mock("@mariozechner/pi-ai/oauth", () => ({
getOAuthApiKey: vi.fn(async () => null),
getOAuthProviders: () => [{ id: "openai-codex" }, { id: "anthropic" }],
}));
vi.mock("../../plugins/provider-runtime.runtime.js", () => ({
formatProviderAuthProfileApiKeyWithPlugin: (params: { context?: { access?: string } }) =>
formatProviderAuthProfileApiKeyWithPluginMock() ?? params?.context?.access,
refreshProviderOAuthCredentialWithPlugin: refreshProviderOAuthCredentialWithPluginMock,
}));
vi.mock("../../infra/file-lock.js", () => ({
resetFileLockStateForTest: () => undefined,
withFileLock: async <T>(_filePath: string, _options: unknown, run: () => Promise<T>) => run(),
}));
vi.mock("../../plugin-sdk/file-lock.js", () => ({
resetFileLockStateForTest: () => undefined,
withFileLock: async <T>(_filePath: string, _options: unknown, run: () => Promise<T>) => run(),
}));
vi.mock("./external-auth.js", () => ({
overlayExternalAuthProfiles: <T>(store: T) => store,
shouldPersistExternalAuthProfile: () => true,
}));
vi.mock("./doctor.js", () => ({
formatAuthDoctorHint: async () => undefined,
}));
@@ -74,7 +100,11 @@ function storeWith(profileId: string, cred: OAuthCredential): AuthProfileStore {
}
describe("OAuth credential adoption is identity-gated", () => {
const envSnapshot = captureEnv(["OPENCLAW_STATE_DIR"]);
const envSnapshot = captureEnv([
"OPENCLAW_STATE_DIR",
"OPENCLAW_AGENT_DIR",
"PI_CODING_AGENT_DIR",
]);
let tempRoot = "";
let mainAgentDir = "";
@@ -88,6 +118,8 @@ describe("OAuth credential adoption is identity-gated", () => {
tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-oauth-adopt-identity-"));
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 });
await loadOAuthModuleForTest();
resetOAuthRefreshQueuesForTest();
@@ -143,7 +175,7 @@ describe("OAuth credential adoption is identity-gated", () => {
mainAgentDir,
);
const result = await resolveApiKeyForProfile({
const result = await resolveApiKeyForProfileInTest({
store: ensureAuthProfileStore(subAgentDir),
profileId,
agentDir: subAgentDir,
@@ -212,7 +244,7 @@ describe("OAuth credential adoption is identity-gated", () => {
}) as never,
);
const result = await resolveApiKeyForProfile({
const result = await resolveApiKeyForProfileInTest({
store: ensureAuthProfileStore(subAgentDir),
profileId,
agentDir: subAgentDir,
@@ -274,7 +306,7 @@ describe("OAuth credential adoption is identity-gated", () => {
mainAgentDir,
);
const result = await resolveApiKeyForProfile({
const result = await resolveApiKeyForProfileInTest({
store: ensureAuthProfileStore(subAgentDir),
profileId,
agentDir: subAgentDir,
@@ -331,7 +363,7 @@ describe("OAuth credential adoption is identity-gated", () => {
// Plugin refresh must NOT be called — sub should adopt main's fresh
// cred rather than performing its own refresh.
const result = await resolveApiKeyForProfile({
const result = await resolveApiKeyForProfileInTest({
store: ensureAuthProfileStore(subAgentDir),
profileId,
agentDir: subAgentDir,
@@ -380,7 +412,7 @@ describe("OAuth credential adoption is identity-gated", () => {
mainAgentDir,
);
const result = await resolveApiKeyForProfile({
const result = await resolveApiKeyForProfileInTest({
store: ensureAuthProfileStore(subAgentDir),
profileId,
agentDir: subAgentDir,
@@ -444,7 +476,7 @@ describe("OAuth credential adoption is identity-gated", () => {
}) as never,
);
const result = await resolveApiKeyForProfile({
const result = await resolveApiKeyForProfileInTest({
store: ensureAuthProfileStore(subAgentDir),
profileId,
agentDir: subAgentDir,
@@ -511,7 +543,7 @@ describe("OAuth credential adoption is identity-gated", () => {
// Catch-block main-inherit must refuse the non-overlapping cred and
// propagate the original error rather than leaking main's credential.
await expect(
resolveApiKeyForProfile({
resolveApiKeyForProfileInTest({
store: ensureAuthProfileStore(subAgentDir),
profileId,
agentDir: subAgentDir,
@@ -574,7 +606,7 @@ describe("OAuth credential adoption is identity-gated", () => {
throw new Error("upstream 503 service unavailable");
});
const result = await resolveApiKeyForProfile({
const result = await resolveApiKeyForProfileInTest({
store: ensureAuthProfileStore(subAgentDir),
profileId,
agentDir: subAgentDir,
@@ -643,7 +675,7 @@ describe("OAuth credential adoption is identity-gated", () => {
});
await expect(
resolveApiKeyForProfile({
resolveApiKeyForProfileInTest({
store: ensureAuthProfileStore(subAgentDir),
profileId,
agentDir: subAgentDir,

View File

@@ -18,6 +18,12 @@ async function loadOAuthModuleForTest() {
({ resolveApiKeyForProfile, resetOAuthRefreshQueuesForTest } = await import("./oauth.js"));
}
function resolveApiKeyForProfileInTest(
params: Omit<Parameters<typeof resolveApiKeyForProfile>[0], "cfg">,
) {
return resolveApiKeyForProfile({ cfg: {}, ...params });
}
const {
refreshProviderOAuthCredentialWithPluginMock,
formatProviderAuthProfileApiKeyWithPluginMock,
@@ -35,12 +41,22 @@ vi.mock("../cli-credentials.js", () => ({
writeCodexCliCredentials: () => true,
}));
vi.mock("@mariozechner/pi-ai/oauth", () => ({
getOAuthApiKey: vi.fn(async () => null),
getOAuthProviders: () => [{ id: "openai-codex" }],
}));
vi.mock("../../plugins/provider-runtime.runtime.js", () => ({
formatProviderAuthProfileApiKeyWithPlugin: (params: { context?: { access?: string } }) =>
formatProviderAuthProfileApiKeyWithPluginMock() ?? params?.context?.access,
refreshProviderOAuthCredentialWithPlugin: refreshProviderOAuthCredentialWithPluginMock,
}));
vi.mock("./external-auth.js", () => ({
overlayExternalAuthProfiles: <T>(store: T) => store,
shouldPersistExternalAuthProfile: () => true,
}));
vi.mock("./doctor.js", () => ({
formatAuthDoctorHint: async () => undefined,
}));
@@ -79,7 +95,11 @@ function createExpiredOauthStore(params: {
}
describe("resolveApiKeyForProfile cross-agent refresh coordination (#26322)", () => {
const envSnapshot = captureEnv(["OPENCLAW_STATE_DIR"]);
const envSnapshot = captureEnv([
"OPENCLAW_STATE_DIR",
"OPENCLAW_AGENT_DIR",
"PI_CODING_AGENT_DIR",
]);
let tempRoot = "";
let mainAgentDir = "";
@@ -93,6 +113,8 @@ describe("resolveApiKeyForProfile cross-agent refresh coordination (#26322)", ()
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 });
await loadOAuthModuleForTest();
// Drop any refresh-queue entries left behind by a prior timed-out test.
@@ -150,7 +172,7 @@ describe("resolveApiKeyForProfile cross-agent refresh coordination (#26322)", ()
// performed; the remaining 19 adopt the resulting fresh credentials.
const results = await Promise.all(
subAgents.map((agentDir) =>
resolveApiKeyForProfile({
resolveApiKeyForProfileInTest({
store: ensureAuthProfileStore(agentDir),
profileId,
agentDir,
@@ -165,6 +187,5 @@ describe("resolveApiKeyForProfile cross-agent refresh coordination (#26322)", ()
expect(result?.apiKey).toBe("cross-agent-refreshed-access");
expect(result?.provider).toBe(provider);
}
}, // Generous timeout; the fix should complete well under 5s in practice.
60_000);
}, 60_000); // Generous timeout; the fix should complete well under 5s in practice.
});

View File

@@ -19,6 +19,12 @@ async function loadOAuthModuleForTest() {
({ resolveApiKeyForProfile, resetOAuthRefreshQueuesForTest } = await import("./oauth.js"));
}
function resolveApiKeyForProfileInTest(
params: Omit<Parameters<typeof resolveApiKeyForProfile>[0], "cfg">,
) {
return resolveApiKeyForProfile({ cfg: {}, ...params });
}
const {
refreshProviderOAuthCredentialWithPluginMock,
formatProviderAuthProfileApiKeyWithPluginMock,
@@ -55,6 +61,16 @@ vi.mock("../../plugins/provider-runtime.runtime.js", () => ({
refreshProviderOAuthCredentialWithPlugin: refreshProviderOAuthCredentialWithPluginMock,
}));
vi.mock("../../infra/file-lock.js", () => ({
resetFileLockStateForTest: () => undefined,
withFileLock: async <T>(_filePath: string, _options: unknown, run: () => Promise<T>) => run(),
}));
vi.mock("../../plugin-sdk/file-lock.js", () => ({
resetFileLockStateForTest: () => undefined,
withFileLock: async <T>(_filePath: string, _options: unknown, run: () => Promise<T>) => run(),
}));
vi.mock("./doctor.js", () => ({
formatAuthDoctorHint: async () => undefined,
}));
@@ -90,7 +106,11 @@ function createExpiredOauthStore(params: {
}
describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => {
const envSnapshot = captureEnv(["OPENCLAW_STATE_DIR"]);
const envSnapshot = captureEnv([
"OPENCLAW_STATE_DIR",
"OPENCLAW_AGENT_DIR",
"PI_CODING_AGENT_DIR",
]);
let tempRoot = "";
let mainAgentDir = "";
@@ -105,6 +125,8 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () =>
tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-oauth-mirror-"));
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 });
await loadOAuthModuleForTest();
resetOAuthRefreshQueuesForTest();
@@ -146,7 +168,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () =>
}) as never,
);
const result = await resolveApiKeyForProfile({
const result = await resolveApiKeyForProfileInTest({
store: ensureAuthProfileStore(subAgentDir),
profileId,
agentDir: subAgentDir,
@@ -190,7 +212,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () =>
// Main-agent refresh uses undefined agentDir; the mirror path is a no-op
// (local == main). Just make sure the main store still reflects the refresh
// and no double-write happens.
const result = await resolveApiKeyForProfile({
const result = await resolveApiKeyForProfileInTest({
store: ensureAuthProfileStore(undefined),
profileId,
agentDir: undefined,
@@ -244,7 +266,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () =>
}) as never,
);
const result = await resolveApiKeyForProfile({
const result = await resolveApiKeyForProfileInTest({
store: ensureAuthProfileStore(subAgentDir),
profileId,
agentDir: subAgentDir,
@@ -310,7 +332,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () =>
}) as never,
);
const result = await resolveApiKeyForProfile({
const result = await resolveApiKeyForProfileInTest({
store: ensureAuthProfileStore(subAgentDir),
profileId,
agentDir: subAgentDir,
@@ -375,7 +397,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () =>
// The sub-agent will actually adopt main's fresher creds via the inside-
// lock recheck (that's the whole point of #26322), so refresh may not
// even fire. We only care that the main store is not regressed.
await resolveApiKeyForProfile({
await resolveApiKeyForProfileInTest({
store: ensureAuthProfileStore(subAgentDir),
profileId,
agentDir: subAgentDir,
@@ -431,7 +453,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () =>
}) as never,
);
const result = await resolveApiKeyForProfile({
const result = await resolveApiKeyForProfileInTest({
store: ensureAuthProfileStore(subAgentDir),
profileId,
agentDir: subAgentDir,
@@ -489,7 +511,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () =>
}) as never,
);
await resolveApiKeyForProfile({
await resolveApiKeyForProfileInTest({
store: ensureAuthProfileStore(subAgentDir),
profileId,
agentDir: subAgentDir,
@@ -538,7 +560,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () =>
// Refresh mock intentionally left as default-undefined — it should not
// be called, the pre-refresh adopt wins.
const result = await resolveApiKeyForProfile({
const result = await resolveApiKeyForProfileInTest({
store: ensureAuthProfileStore(subAgentDir),
profileId,
agentDir: subAgentDir,
@@ -601,7 +623,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () =>
throw new Error("upstream 503 service unavailable");
});
const result = await resolveApiKeyForProfile({
const result = await resolveApiKeyForProfileInTest({
store: ensureAuthProfileStore(subAgentDir),
profileId,
agentDir: subAgentDir,
@@ -665,7 +687,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () =>
}) as never,
);
const result = await resolveApiKeyForProfile({
const result = await resolveApiKeyForProfileInTest({
store: ensureAuthProfileStore(subAgentDir),
profileId,
agentDir: subAgentDir,
@@ -723,7 +745,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () =>
}) as never,
);
await resolveApiKeyForProfile({
await resolveApiKeyForProfileInTest({
store: ensureAuthProfileStore(subAgentDir),
profileId,
agentDir: subAgentDir,
@@ -763,7 +785,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () =>
}) as never,
);
const result = await resolveApiKeyForProfile({
const result = await resolveApiKeyForProfileInTest({
store: ensureAuthProfileStore(subAgentDir),
profileId,
agentDir: subAgentDir,

View File

@@ -23,6 +23,11 @@ vi.mock("../../plugins/manifest-registry.js", () => ({
loadPluginManifestRegistry,
}));
vi.mock("./external-auth.js", () => ({
overlayExternalAuthProfiles: <T>(store: T) => store,
shouldPersistExternalAuthProfile: () => true,
}));
describe("resolveAuthProfileOrder", () => {
beforeEach(() => {
loadPluginManifestRegistry.mockClear();