test: share oauth mock setup

This commit is contained in:
Peter Steinberger
2026-04-18 23:48:16 +01:00
parent 58da2f5897
commit 57326feb8d
8 changed files with 100 additions and 254 deletions

View File

@@ -0,0 +1,48 @@
import { vi } from "vitest";
import type { OAuthCredential } from "./types.js";
const oauthProviderRuntimeMocks = vi.hoisted(() => ({
refreshProviderOAuthCredentialWithPluginMock: vi.fn(
async (_params?: { context?: unknown }) => undefined,
),
formatProviderAuthProfileApiKeyWithPluginMock: vi.fn(() => undefined),
}));
export function getOAuthProviderRuntimeMocks() {
return oauthProviderRuntimeMocks;
}
vi.mock("../cli-credentials.js", () => ({
readCodexCliCredentialsCached: () => null,
readMiniMaxCliCredentialsCached: () => null,
resetCliCredentialCachesForTest: () => undefined,
writeCodexCliCredentials: () => true,
}));
vi.mock("../../plugins/provider-runtime.runtime.js", () => ({
formatProviderAuthProfileApiKeyWithPlugin: (params: { context?: { access?: string } }) =>
oauthProviderRuntimeMocks.formatProviderAuthProfileApiKeyWithPluginMock() ??
params?.context?.access,
refreshProviderOAuthCredentialWithPlugin:
oauthProviderRuntimeMocks.refreshProviderOAuthCredentialWithPluginMock,
}));
vi.mock("./doctor.js", () => ({
formatAuthDoctorHint: async () => undefined,
}));
vi.mock("./external-cli-sync.js", () => ({
areOAuthCredentialsEquivalent: (a: unknown, b: unknown) => a === b,
hasUsableOAuthCredential: (credential: OAuthCredential | undefined, now = Date.now()) =>
credential?.type === "oauth" &&
credential.access.trim().length > 0 &&
Number.isFinite(credential.expires) &&
credential.expires - now > 5 * 60 * 1000,
isSafeToUseExternalCliCredential: () => true,
readExternalCliBootstrapCredential: () => null,
readManagedExternalCliCredential: () => null,
resolveExternalCliAuthProfiles: () => [],
shouldBootstrapFromExternalCliCredential: () => false,
shouldReplaceStoredOAuthCredential: (existing: unknown, incoming: unknown) =>
existing !== incoming,
}));

View File

@@ -0,0 +1,6 @@
import { vi } from "vitest";
vi.mock("./external-auth.js", () => ({
overlayExternalAuthProfiles: <T>(store: T) => store,
shouldPersistExternalAuthProfile: () => true,
}));

View File

@@ -0,0 +1,11 @@
import { vi } from "vitest";
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(),
}));

View File

@@ -2,6 +2,8 @@ import path from "node:path";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { FILE_LOCK_TIMEOUT_ERROR_CODE, type FileLockTimeoutError } from "../../infra/file-lock.js";
import { captureEnv } from "../../test-utils/env.js";
import { getOAuthProviderRuntimeMocks } from "./oauth-common-mocks.test-support.js";
import "./oauth-external-auth-passthrough.test-support.js";
import {
OAUTH_AGENT_ENV_KEYS,
createOAuthMainAgentDir,
@@ -9,10 +11,15 @@ import {
createExpiredOauthStore,
removeOAuthTestTempRoot,
resolveApiKeyForProfileInTest,
resetOAuthProviderRuntimeMocks,
} from "./oauth-test-utils.js";
import { resolveAuthStorePath, resolveOAuthRefreshLockPath } from "./paths.js";
import { clearRuntimeAuthProfileStoreSnapshots, saveAuthProfileStore } from "./store.js";
import type { OAuthCredential } from "./types.js";
const {
refreshProviderOAuthCredentialWithPluginMock,
formatProviderAuthProfileApiKeyWithPluginMock,
} = getOAuthProviderRuntimeMocks();
let resolveApiKeyForProfile: typeof import("./oauth.js").resolveApiKeyForProfile;
let resetOAuthRefreshQueuesForTest: typeof import("./oauth.js").resetOAuthRefreshQueuesForTest;
@@ -23,24 +30,11 @@ const { withFileLockMock } = vi.hoisted(() => ({
),
}));
vi.mock("../cli-credentials.js", () => ({
readCodexCliCredentialsCached: () => null,
readMiniMaxCliCredentialsCached: () => null,
resetCliCredentialCachesForTest: () => undefined,
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 } }) =>
params?.context?.access,
refreshProviderOAuthCredentialWithPlugin: async () => undefined,
}));
vi.mock("../../infra/file-lock.js", () => ({
FILE_LOCK_TIMEOUT_ERROR_CODE: "file_lock_timeout",
resetFileLockStateForTest: () => undefined,
@@ -53,31 +47,6 @@ vi.mock("../../plugin-sdk/file-lock.js", () => ({
withFileLock: withFileLockMock,
}));
vi.mock("./doctor.js", () => ({
formatAuthDoctorHint: async () => undefined,
}));
vi.mock("./external-auth.js", () => ({
overlayExternalAuthProfiles: <T>(store: T) => store,
shouldPersistExternalAuthProfile: () => true,
}));
vi.mock("./external-cli-sync.js", () => ({
areOAuthCredentialsEquivalent: (a: unknown, b: unknown) => a === b,
hasUsableOAuthCredential: (credential: OAuthCredential | undefined, now = Date.now()) =>
credential?.type === "oauth" &&
credential.access.trim().length > 0 &&
Number.isFinite(credential.expires) &&
credential.expires - now > 5 * 60 * 1000,
isSafeToUseExternalCliCredential: () => true,
readExternalCliBootstrapCredential: () => null,
readManagedExternalCliCredential: () => null,
resolveExternalCliAuthProfiles: () => [],
shouldBootstrapFromExternalCliCredential: () => false,
shouldReplaceStoredOAuthCredential: (existing: unknown, incoming: unknown) =>
existing !== incoming,
}));
function createLockTimeoutError(lockPath: string): FileLockTimeoutError {
return Object.assign(new Error(`file lock timeout for ${lockPath.slice(0, -5)}`), {
code: FILE_LOCK_TIMEOUT_ERROR_CODE as typeof FILE_LOCK_TIMEOUT_ERROR_CODE,
@@ -97,6 +66,10 @@ describe("OAuth refresh lock timeout classification", () => {
});
beforeEach(async () => {
resetOAuthProviderRuntimeMocks({
refreshProviderOAuthCredentialWithPluginMock,
formatProviderAuthProfileApiKeyWithPluginMock,
});
withFileLockMock.mockReset();
withFileLockMock.mockImplementation(
async <T>(_filePath: string, _options: unknown, run: () => Promise<T>) => await run(),

View File

@@ -2,6 +2,9 @@ import path from "node:path";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { resetFileLockStateForTest } from "../../infra/file-lock.js";
import { captureEnv } from "../../test-utils/env.js";
import { getOAuthProviderRuntimeMocks } from "./oauth-common-mocks.test-support.js";
import "./oauth-external-auth-passthrough.test-support.js";
import "./oauth-file-lock-passthrough.test-support.js";
import {
OAUTH_AGENT_ENV_KEYS,
createOAuthMainAgentDir,
@@ -17,71 +20,17 @@ import {
ensureAuthProfileStore,
saveAuthProfileStore,
} from "./store.js";
import type { OAuthCredential } from "./types.js";
const {
refreshProviderOAuthCredentialWithPluginMock,
formatProviderAuthProfileApiKeyWithPluginMock,
} = vi.hoisted(() => ({
refreshProviderOAuthCredentialWithPluginMock: vi.fn(
async (_params?: { context?: unknown }) => undefined,
),
formatProviderAuthProfileApiKeyWithPluginMock: vi.fn(() => undefined),
}));
vi.mock("../cli-credentials.js", () => ({
readCodexCliCredentialsCached: () => null,
readMiniMaxCliCredentialsCached: () => null,
resetCliCredentialCachesForTest: () => undefined,
writeCodexCliCredentials: () => true,
}));
} = getOAuthProviderRuntimeMocks();
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("../../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,
}));
vi.mock("./external-auth.js", () => ({
overlayExternalAuthProfiles: <T>(store: T) => store,
shouldPersistExternalAuthProfile: () => true,
}));
vi.mock("./external-cli-sync.js", () => ({
areOAuthCredentialsEquivalent: (a: unknown, b: unknown) => a === b,
hasUsableOAuthCredential: (credential: OAuthCredential | undefined, now = Date.now()) =>
credential?.type === "oauth" &&
credential.access.trim().length > 0 &&
Number.isFinite(credential.expires) &&
credential.expires - now > 5 * 60 * 1000,
isSafeToUseExternalCliCredential: () => true,
readExternalCliBootstrapCredential: () => null,
readManagedExternalCliCredential: () => null,
resolveExternalCliAuthProfiles: () => [],
shouldBootstrapFromExternalCliCredential: () => false,
shouldReplaceStoredOAuthCredential: (existing: unknown, incoming: unknown) =>
existing !== incoming,
}));
describe("OAuth refresh in-process queue", () => {
const envSnapshot = captureEnv(OAUTH_AGENT_ENV_KEYS);
let tempRoot = "";

View File

@@ -3,6 +3,9 @@ import path from "node:path";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { resetFileLockStateForTest } from "../../infra/file-lock.js";
import { captureEnv } from "../../test-utils/env.js";
import { getOAuthProviderRuntimeMocks } from "./oauth-common-mocks.test-support.js";
import "./oauth-external-auth-passthrough.test-support.js";
import "./oauth-file-lock-passthrough.test-support.js";
import {
OAUTH_AGENT_ENV_KEYS,
createOAuthMainAgentDir,
@@ -19,76 +22,23 @@ import {
ensureAuthProfileStore,
saveAuthProfileStore,
} from "./store.js";
import type { AuthProfileStore, OAuthCredential } from "./types.js";
import type { AuthProfileStore } from "./types.js";
const {
refreshProviderOAuthCredentialWithPluginMock,
formatProviderAuthProfileApiKeyWithPluginMock,
} = getOAuthProviderRuntimeMocks();
// Cross-account-leak defense-in-depth: each adopt site in oauth.ts calls the
// shared identity copy gate before copying main-store credentials into the
// sub-agent store. Unit tests cover policy variants; this suite proves each
// production branch refuses a mismatched accountId.
const {
refreshProviderOAuthCredentialWithPluginMock,
formatProviderAuthProfileApiKeyWithPluginMock,
} = vi.hoisted(() => ({
refreshProviderOAuthCredentialWithPluginMock: vi.fn(
async (_params?: { context?: unknown }) => undefined,
),
formatProviderAuthProfileApiKeyWithPluginMock: vi.fn(() => undefined),
}));
vi.mock("../cli-credentials.js", () => ({
readCodexCliCredentialsCached: () => null,
readMiniMaxCliCredentialsCached: () => null,
resetCliCredentialCachesForTest: () => undefined,
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,
}));
vi.mock("./external-cli-sync.js", () => ({
areOAuthCredentialsEquivalent: (a: unknown, b: unknown) => a === b,
hasUsableOAuthCredential: (credential: OAuthCredential | undefined, now = Date.now()) =>
credential?.type === "oauth" &&
credential.access.trim().length > 0 &&
Number.isFinite(credential.expires) &&
credential.expires - now > 5 * 60 * 1000,
isSafeToUseExternalCliCredential: () => true,
readExternalCliBootstrapCredential: () => null,
readManagedExternalCliCredential: () => null,
resolveExternalCliAuthProfiles: () => [],
shouldBootstrapFromExternalCliCredential: () => false,
shouldReplaceStoredOAuthCredential: (existing: unknown, incoming: unknown) =>
existing !== incoming,
}));
describe("OAuth credential adoption is identity-gated", () => {
const envSnapshot = captureEnv(OAUTH_AGENT_ENV_KEYS);
let tempRoot = "";

View File

@@ -3,6 +3,8 @@ import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { resetFileLockStateForTest } from "../../infra/file-lock.js";
import { captureEnv } from "../../test-utils/env.js";
import { getOAuthProviderRuntimeMocks } from "./oauth-common-mocks.test-support.js";
import "./oauth-external-auth-passthrough.test-support.js";
import {
OAUTH_AGENT_ENV_KEYS,
createOAuthMainAgentDir,
@@ -17,7 +19,11 @@ import {
ensureAuthProfileStore,
saveAuthProfileStore,
} from "./store.js";
import type { OAuthCredential } from "./types.js";
const {
refreshProviderOAuthCredentialWithPluginMock,
formatProviderAuthProfileApiKeyWithPluginMock,
} = getOAuthProviderRuntimeMocks();
let resolveApiKeyForProfile: typeof import("./oauth.js").resolveApiKeyForProfile;
let resetOAuthRefreshQueuesForTest: typeof import("./oauth.js").resetOAuthRefreshQueuesForTest;
@@ -26,62 +32,11 @@ async function loadOAuthModuleForTest() {
({ resolveApiKeyForProfile, resetOAuthRefreshQueuesForTest } = await import("./oauth.js"));
}
const {
refreshProviderOAuthCredentialWithPluginMock,
formatProviderAuthProfileApiKeyWithPluginMock,
} = vi.hoisted(() => ({
refreshProviderOAuthCredentialWithPluginMock: vi.fn(
async (_params?: { context?: unknown }) => undefined,
),
formatProviderAuthProfileApiKeyWithPluginMock: vi.fn(() => undefined),
}));
vi.mock("../cli-credentials.js", () => ({
readCodexCliCredentialsCached: () => null,
readMiniMaxCliCredentialsCached: () => null,
resetCliCredentialCachesForTest: () => undefined,
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,
}));
// External-CLI sync does real I/O against the user's Codex/MiniMax CLI
// credential files; it is slow and can pollute test state. Stub it to a no-op
// so the suite only exercises in-repo auth-profile logic.
vi.mock("./external-cli-sync.js", () => ({
areOAuthCredentialsEquivalent: (a: unknown, b: unknown) => a === b,
hasUsableOAuthCredential: (credential: OAuthCredential | undefined, now = Date.now()) =>
credential?.type === "oauth" &&
credential.access.trim().length > 0 &&
Number.isFinite(credential.expires) &&
credential.expires - now > 5 * 60 * 1000,
isSafeToUseExternalCliCredential: () => true,
readExternalCliBootstrapCredential: () => null,
readManagedExternalCliCredential: () => null,
resolveExternalCliAuthProfiles: () => [],
shouldBootstrapFromExternalCliCredential: () => false,
shouldReplaceStoredOAuthCredential: (existing: unknown, incoming: unknown) =>
existing !== incoming,
}));
describe("resolveApiKeyForProfile cross-agent refresh coordination (#26322)", () => {
const envSnapshot = captureEnv(OAUTH_AGENT_ENV_KEYS);
let tempRoot = "";

View File

@@ -4,6 +4,8 @@ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi }
import { resetFileLockStateForTest } from "../../infra/file-lock.js";
import { captureEnv } from "../../test-utils/env.js";
import { __testing as externalAuthTesting } from "./external-auth.js";
import "./oauth-file-lock-passthrough.test-support.js";
import { getOAuthProviderRuntimeMocks } from "./oauth-common-mocks.test-support.js";
import {
OAUTH_AGENT_ENV_KEYS,
createOAuthMainAgentDir,
@@ -24,19 +26,7 @@ import type { AuthProfileStore, OAuthCredential } from "./types.js";
const {
refreshProviderOAuthCredentialWithPluginMock,
formatProviderAuthProfileApiKeyWithPluginMock,
} = vi.hoisted(() => ({
refreshProviderOAuthCredentialWithPluginMock: vi.fn(
async (_params?: { context?: unknown }) => undefined,
),
formatProviderAuthProfileApiKeyWithPluginMock: vi.fn(() => undefined),
}));
vi.mock("../cli-credentials.js", () => ({
readCodexCliCredentialsCached: () => null,
readMiniMaxCliCredentialsCached: () => null,
resetCliCredentialCachesForTest: () => undefined,
writeCodexCliCredentials: () => true,
}));
} = getOAuthProviderRuntimeMocks();
vi.mock("@mariozechner/pi-ai/oauth", () => ({
getOAuthProviders: () => [{ id: "anthropic" }, { id: "openai-codex" }],
@@ -51,42 +41,6 @@ vi.mock("@mariozechner/pi-ai/oauth", () => ({
}),
}));
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("./doctor.js", () => ({
formatAuthDoctorHint: async () => undefined,
}));
vi.mock("./external-cli-sync.js", () => ({
areOAuthCredentialsEquivalent: (a: unknown, b: unknown) => a === b,
hasUsableOAuthCredential: (credential: OAuthCredential | undefined, now = Date.now()) =>
credential?.type === "oauth" &&
credential.access.trim().length > 0 &&
Number.isFinite(credential.expires) &&
credential.expires - now > 5 * 60 * 1000,
isSafeToUseExternalCliCredential: () => true,
readExternalCliBootstrapCredential: () => null,
readManagedExternalCliCredential: () => null,
resolveExternalCliAuthProfiles: () => [],
shouldBootstrapFromExternalCliCredential: () => false,
shouldReplaceStoredOAuthCredential: (existing: unknown, incoming: unknown) =>
existing !== incoming,
}));
describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => {
const envSnapshot = captureEnv(OAUTH_AGENT_ENV_KEYS);
let tempRoot = "";