import test from "node:test"; import assert from "node:assert/strict"; const tokenRefresh = await import("../../open-sse/services/tokenRefresh.ts"); const { PROVIDERS, OAUTH_ENDPOINTS } = await import("../../open-sse/config/constants.ts"); const { TOKEN_EXPIRY_BUFFER_MS, refreshAccessToken, refreshClineToken, refreshKimiCodingToken, refreshClaudeOAuthToken, refreshGoogleToken, refreshQwenToken, refreshCodexToken, refreshKiroToken, refreshQoderToken, refreshGitHubToken, refreshCopilotToken, supportsTokenRefresh, isUnrecoverableRefreshError, getAccessToken, formatProviderCredentials, getAllAccessTokens, isProviderBlocked, getCircuitBreakerStatus, getConnectionRefreshMutexStatus, refreshWithRetry, } = tokenRefresh; type LogLevel = "debug" | "info" | "warn" | "error"; type LogEntry = { level: LogLevel; scope: unknown; message: unknown; meta: unknown; }; type MockLogger = { entries: LogEntry[]; debug: (...args: [unknown?, unknown?, unknown?]) => void; info: (...args: [unknown?, unknown?, unknown?]) => void; warn: (...args: [unknown?, unknown?, unknown?]) => void; error: (...args: [unknown?, unknown?, unknown?]) => void; }; type TestFetch = typeof fetch; type FastSetTimeout = typeof globalThis.setTimeout & { __promisify__?: typeof globalThis.setTimeout.__promisify__; }; function createLog(): MockLogger { const entries: LogEntry[] = []; const push = (level: LogLevel, args: [unknown?, unknown?, unknown?]) => { const [scope, message, meta] = args; entries.push({ level, scope, message, meta }); }; return { entries, debug: (...args) => push("debug", args), info: (...args) => push("info", args), warn: (...args) => push("warn", args), error: (...args) => push("error", args), }; } function jsonResponse(body: any, status = 200) { return new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" }, }); } function textResponse(text: any, status = 400) { return new Response(text, { status, headers: { "content-type": "text/plain" }, }); } function bodyToString(body: BodyInit | null | undefined) { if (typeof body === "string") return body; if (body instanceof URLSearchParams) return body.toString(); return String(body ?? ""); } async function withMockedFetch(fetchImpl: TestFetch, fn: () => Promise) { const originalFetch = globalThis.fetch; globalThis.fetch = fetchImpl; try { return await fn(); } finally { globalThis.fetch = originalFetch; } } async function withMockedNow(now: number, fn: () => Promise) { const originalNow = Date.now; Date.now = () => now; try { return await fn(); } finally { Date.now = originalNow; } } async function withPatchedProperties( target: object, patch: Record, fn: () => Promise ) { const previous = new Map(); const targetRecord = target as Record; for (const [key, value] of Object.entries(patch)) { previous.set( key, Object.prototype.hasOwnProperty.call(targetRecord, key) ? targetRecord[key] : undefined ); targetRecord[key] = value; } try { return await fn(); } finally { for (const [key] of Object.entries(patch)) { const prior = previous.get(key); if (prior === undefined) { delete targetRecord[key]; } else { targetRecord[key] = prior; } } } } async function withFastRetryTimers(fn: () => Promise) { const originalSetTimeout = globalThis.setTimeout as FastSetTimeout; const fastSetTimeout: FastSetTimeout = Object.assign( ((callback: TimerHandler, delay = 0, ...args: unknown[]) => originalSetTimeout(callback, delay === 30_000 ? delay : 0, ...args)) as typeof setTimeout, { __promisify__: originalSetTimeout.__promisify__ } ); globalThis.setTimeout = fastSetTimeout; try { return await fn(); } finally { globalThis.setTimeout = originalSetTimeout; } } test("TOKEN_EXPIRY_BUFFER_MS stays at five minutes", () => { assert.equal(TOKEN_EXPIRY_BUFFER_MS, 5 * 60 * 1000); }); test("refreshAccessToken returns null when no provider refresh endpoint exists", async () => { const log = createLog(); const result = await refreshAccessToken("qoder", "refresh-token", {}, log); assert.equal(result, null); assert.equal( log.entries.some((entry) => entry.level === "warn"), true ); }); test("refreshAccessToken returns null when refresh token is missing", async () => { const log = createLog(); await withPatchedProperties( PROVIDERS, { "custom-oauth-task-207": { tokenUrl: "https://auth.example.com/token" }, }, async () => { const result = await refreshAccessToken("custom-oauth-task-207", null, {}, log); assert.equal(result, null); } ); }); test("refreshAccessToken posts form data and returns rotated tokens", async () => { const log = createLog(); const calls: any[] = []; await withPatchedProperties( PROVIDERS, { "custom-oauth-task-207": { refreshUrl: "https://auth.example.com/token", clientId: "client-id", clientSecret: "client-secret", }, }, async () => { await withMockedFetch( async (url, options = {}) => { calls.push({ url, options }); return jsonResponse({ access_token: "new-access", refresh_token: "new-refresh", expires_in: 3600, }); }, async () => { const result = await refreshAccessToken("custom-oauth-task-207", "refresh-123", {}, log); assert.deepEqual(result, { accessToken: "new-access", refreshToken: "new-refresh", expiresIn: 3600, }); } ); } ); assert.equal(calls[0].url, "https://auth.example.com/token"); assert.equal( bodyToString(calls[0].options.body), "grant_type=refresh_token&refresh_token=refresh-123&client_id=client-id&client_secret=client-secret" ); }); test("refreshAccessToken returns null on upstream refresh failure", async () => { const log = createLog(); await withPatchedProperties( PROVIDERS, { "custom-oauth-task-207": { tokenUrl: "https://auth.example.com/token" }, }, async () => { await withMockedFetch( async () => textResponse("rate limited", 429), async () => { const result = await refreshAccessToken("custom-oauth-task-207", "refresh-123", {}, log); assert.equal(result, null); assert.equal( log.entries.some((entry) => entry.level === "error"), true ); } ); } ); }); test("refreshClineToken handles nested payloads and computes expiresIn", async () => { const log = createLog(); const calls: any[] = []; await withMockedNow(1_700_000_000_000, async () => { await withMockedFetch( async (url, options = {}) => { calls.push({ url, options }); return jsonResponse({ data: { accessToken: "cline-access", refreshToken: "cline-refresh", expiresAt: new Date(Date.now() + 95_000).toISOString(), }, }); }, async () => { const result = await refreshClineToken("refresh-cline", log); assert.equal(result?.accessToken, "cline-access"); assert.equal(result?.refreshToken, "cline-refresh"); assert.equal(result?.expiresIn, 95); } ); }); assert.equal(calls[0].url, PROVIDERS.cline.refreshUrl); assert.deepEqual(JSON.parse(calls[0].options.body), { refreshToken: "refresh-cline", grantType: "refresh_token", clientType: "extension", }); }); test("refreshKimiCodingToken adds provider-specific headers and fields", async () => { const log = createLog(); const calls: any[] = []; await withMockedFetch( async (url, options = {}) => { calls.push({ url, options }); return jsonResponse({ access_token: "kimi-access", refresh_token: "kimi-refresh-next", expires_in: 7200, token_type: "Bearer", scope: "coding offline_access", }); }, async () => { // Pass providerSpecificData with a stable deviceId (second positional arg after signature change) const result = await refreshKimiCodingToken( "kimi-refresh", { deviceId: "test-stable-device" }, log ); assert.deepEqual(result, { accessToken: "kimi-access", refreshToken: "kimi-refresh-next", expiresIn: 7200, tokenType: "Bearer", scope: "coding offline_access", }); } ); assert.equal(calls[0].url, PROVIDERS["kimi-coding"].refreshUrl); // Platform is now "kimi_cli" (matching the real Kimi CLI fingerprint) assert.equal(calls[0].options.headers["X-Msh-Platform"], "kimi_cli"); // Version comes from KIMI_CLI_VERSION env or default "1.36.0" assert.ok(calls[0].options.headers["X-Msh-Version"], "X-Msh-Version must be set"); // Device-Id must NOT be an ephemeral "kimi-refresh-" value assert.ok( calls[0].options.headers["X-Msh-Device-Id"] && !calls[0].options.headers["X-Msh-Device-Id"].startsWith("kimi-refresh-"), "X-Msh-Device-Id must be stable (not ephemeral kimi-refresh-)" ); // When providerSpecificData.deviceId is provided, it should be used directly assert.equal(calls[0].options.headers["X-Msh-Device-Id"], "test-stable-device"); assert.match(bodyToString(calls[0].options.body), /grant_type=refresh_token/); }); test("refreshClaudeOAuthToken posts the anthropic oauth refresh contract", async () => { const log = createLog(); const calls: any[] = []; await withMockedFetch( async (url, options = {}) => { calls.push({ url, options }); return jsonResponse({ access_token: "claude-access", refresh_token: "claude-refresh-next", expires_in: 1800, }); }, async () => { const result = await refreshClaudeOAuthToken("claude-refresh", log); assert.deepEqual(result, { accessToken: "claude-access", refreshToken: "claude-refresh-next", expiresIn: 1800, }); } ); assert.equal(calls[0].url, OAUTH_ENDPOINTS.anthropic.token); assert.equal(calls[0].options.headers["anthropic-beta"], "oauth-2025-04-20"); assert.match(calls[0].options.body, /grant_type=refresh_token/); assert.match(calls[0].options.body, /client_id=/); }); test("refreshGoogleToken exchanges refresh tokens against the shared google endpoint", async () => { const log = createLog(); const calls: any[] = []; await withMockedFetch( async (url, options = {}) => { calls.push({ url, options }); return jsonResponse({ access_token: "google-access", refresh_token: "google-refresh-next", expires_in: 3600, }); }, async () => { const result = await refreshGoogleToken("google-refresh", "gid", "gsecret", log); assert.deepEqual(result, { accessToken: "google-access", refreshToken: "google-refresh-next", expiresIn: 3600, }); } ); assert.equal(calls[0].url, OAUTH_ENDPOINTS.google.token); assert.equal( bodyToString(calls[0].options.body), "grant_type=refresh_token&refresh_token=google-refresh&client_id=gid&client_secret=gsecret" ); }); test("refreshQwenToken maps resource_url into providerSpecificData", async () => { const log = createLog(); await withMockedFetch( async () => jsonResponse({ access_token: "qwen-access", refresh_token: "qwen-refresh-next", expires_in: 1500, resource_url: "https://chat.qwen.ai/workspace/resource", }), async () => { const result = await refreshQwenToken("qwen-refresh", log); assert.deepEqual(result, { accessToken: "qwen-access", refreshToken: "qwen-refresh-next", expiresIn: 1500, providerSpecificData: { resourceUrl: "https://chat.qwen.ai/workspace/resource", }, }); } ); }); test("refreshQwenToken surfaces invalid_request as unrecoverable", async () => { const log = createLog(); await withMockedFetch( async () => textResponse(JSON.stringify({ error: "invalid_request" }), 400), async () => { const result = await refreshQwenToken("qwen-refresh", log); // Normalized to unrecoverable_refresh_error sentinel (Fix 4) assert.deepEqual(result, { error: "unrecoverable_refresh_error", code: "invalid_request" }); } ); }); test("refreshCodexToken recognizes refresh_token_reused responses", async () => { const log = createLog(); await withMockedFetch( async () => textResponse(JSON.stringify({ error: { code: "refresh_token_reused" } }), 400), async () => { const result = await refreshCodexToken("codex-refresh", log); assert.deepEqual(result, { error: "unrecoverable_refresh_error", code: "refresh_token_reused", }); } ); }); // Port from decolua/9router#1821 (sacwooky): a 401 from OpenAI's OAuth token // endpoint means the refresh credential itself was rejected (e.g. rotated away // or a payload whose error code we do not yet recognize). Retrying with the // same refresh token will never succeed — surface re-auth, do not loop. test("refreshCodexToken treats any 401 from the token endpoint as unrecoverable", async () => { const log = createLog(); await withMockedFetch( async () => textResponse( JSON.stringify({ error: { // A payload variant whose code/type are NOT in the existing // unrecoverable set — only the 401 status proves the token is dead. message: "Could not validate your token. Please try signing in again.", type: "invalid_request_error", }, }), 401 ), async () => { const result = await refreshCodexToken("codex-refresh", log); assert.equal( result?.error, "unrecoverable_refresh_error", "401 from OpenAI token endpoint must surface re-auth instead of returning null (which triggers retry)" ); } ); }); test("refreshKiroToken uses the AWS OIDC flow when client credentials are present", async () => { const log = createLog(); const calls: any[] = []; await withMockedFetch( async (url, options = {}) => { calls.push({ url, options }); return jsonResponse({ accessToken: "kiro-aws-access", refreshToken: "kiro-aws-refresh-next", expiresIn: 900, }); }, async () => { const result = await refreshKiroToken( "kiro-refresh", { authMethod: "idc", clientId: "aws-client", clientSecret: "aws-secret", region: "eu-west-1", }, log ); assert.deepEqual(result, { accessToken: "kiro-aws-access", refreshToken: "kiro-aws-refresh-next", expiresIn: 900, }); } ); assert.equal(calls[0].url, "https://oidc.eu-west-1.amazonaws.com/token"); assert.deepEqual(JSON.parse(calls[0].options.body), { clientId: "aws-client", clientSecret: "aws-secret", refreshToken: "kiro-refresh", grantType: "refresh_token", }); }); test("refreshKiroToken uses stored region for AWS OIDC refresh without authMethod", async () => { const log = createLog(); const calls: any[] = []; await withMockedFetch( async (url, options = {}) => { calls.push({ url, options }); return jsonResponse({ accessToken: "kiro-aws-access", refreshToken: "kiro-aws-refresh-next", expiresIn: 900, }); }, async () => { const result = await refreshKiroToken( "kiro-refresh", { clientId: "aws-client", clientSecret: "aws-secret", region: "ap-southeast-1", }, log ); assert.deepEqual(result, { accessToken: "kiro-aws-access", refreshToken: "kiro-aws-refresh-next", expiresIn: 900, }); } ); assert.equal(calls[0].url, "https://oidc.ap-southeast-1.amazonaws.com/token"); assert.deepEqual(JSON.parse(calls[0].options.body), { clientId: "aws-client", clientSecret: "aws-secret", refreshToken: "kiro-refresh", grantType: "refresh_token", }); }); test("refreshKiroToken falls back to the social-auth refresh endpoint", async () => { const log = createLog(); const calls: any[] = []; await withMockedFetch( async (url, options = {}) => { calls.push({ url, options }); return jsonResponse({ accessToken: "kiro-social-access", refreshToken: "kiro-social-refresh-next", expiresIn: 1200, }); }, async () => { const result = await refreshKiroToken("kiro-refresh", null, log); assert.deepEqual(result, { accessToken: "kiro-social-access", refreshToken: "kiro-social-refresh-next", expiresIn: 1200, }); } ); assert.equal(calls[0].url, PROVIDERS.kiro.tokenUrl); assert.deepEqual(JSON.parse(calls[0].options.body), { refreshToken: "kiro-refresh", }); }); // Issue #2328 — once a social-auth token has clientId/clientSecret stored // (because it was imported after v3.8.0), refreshKiroToken must use the AWS OIDC // endpoint, not the shared social-auth endpoint, even though authMethod is "google". test("refreshKiroToken uses AWS OIDC path for social-auth token when clientId is present (#2328)", async () => { const log = createLog(); const calls: any[] = []; await withMockedFetch( async (url, options = {}) => { calls.push({ url, options }); return jsonResponse({ accessToken: "kiro-isolated-access", refreshToken: "kiro-isolated-refresh-next", expiresIn: 900, }); }, async () => { const result = await refreshKiroToken( "kiro-social-refresh", { authMethod: "google", clientId: "isolated-client-id", clientSecret: "isolated-client-secret", region: "us-east-1", }, log ); assert.deepEqual(result, { accessToken: "kiro-isolated-access", refreshToken: "kiro-isolated-refresh-next", expiresIn: 900, }); } ); // Must call the AWS OIDC endpoint — not the shared social-auth tokenUrl assert.ok( calls[0].url.includes("oidc.us-east-1.amazonaws.com/token"), `expected AWS OIDC endpoint but got ${calls[0].url}` ); assert.notEqual( calls[0].url, PROVIDERS.kiro.tokenUrl, "should not call the shared social-auth endpoint when clientId is set" ); assert.deepEqual(JSON.parse(calls[0].options.body), { clientId: "isolated-client-id", clientSecret: "isolated-client-secret", refreshToken: "kiro-social-refresh", grantType: "refresh_token", }); }); // Issue #2467 — an IMPORTED social token (authMethod === "imported") carries a // freshly-registered clientId/clientSecret, but its refresh token is Kiro-social-issued // and the isolated OIDC client cannot refresh it. It must use the social-auth endpoint, // NOT AWS OIDC (which is what #2328 enabled for authMethod "google"). test("refreshKiroToken uses social-auth path for imported token even with clientId (#2467)", async () => { const log = createLog(); const calls: any[] = []; await withMockedFetch( async (url, options = {}) => { calls.push({ url, options }); return jsonResponse({ accessToken: "kiro-imported-access", refreshToken: "kiro-imported-refresh-next", expiresIn: 1100, }); }, async () => { const result = await refreshKiroToken( "kiro-imported-refresh", { authMethod: "imported", clientId: "isolated-client-id", clientSecret: "isolated-client-secret", region: "us-east-1", }, log ); assert.equal(result.accessToken, "kiro-imported-access"); } ); // Must call the shared social-auth tokenUrl — NOT the AWS OIDC endpoint. assert.equal( calls[0].url, PROVIDERS.kiro.tokenUrl, `expected social-auth endpoint but got ${calls[0].url}` ); assert.ok(!calls[0].url.includes("oidc."), "imported token must not use AWS OIDC"); }); test("refreshQoderToken uses basic auth once qoder oauth settings are configured", async () => { const log = createLog(); const calls: any[] = []; await withPatchedProperties( PROVIDERS.qoder, { clientId: "qoder-client", clientSecret: "qoder-secret", }, async () => { await withPatchedProperties( OAUTH_ENDPOINTS.qoder, { token: "https://qoder.example.com/oauth/token", }, async () => { await withMockedFetch( async (url, options = {}) => { calls.push({ url, options }); return jsonResponse({ access_token: "qoder-access", refresh_token: "qoder-refresh-next", expires_in: 2400, }); }, async () => { const result = await refreshQoderToken("qoder-refresh", log); assert.deepEqual(result, { accessToken: "qoder-access", refreshToken: "qoder-refresh-next", expiresIn: 2400, }); } ); } ); } ); assert.equal(calls[0].url, "https://qoder.example.com/oauth/token"); assert.match(calls[0].options.headers.Authorization, /^Basic /); }); test("refreshGitHubToken sends the real public github client_id and no client_secret (port from 9router#442)", async () => { // GitHub Copilot's OAuth app is a public device-flow client: it has a client_id but // NO client_secret. PROVIDERS.github.clientId must be populated from the embedded public // cred so the refresh request actually carries a client_id — a missing one makes GitHub // reject the refresh. The previous test patched a fake clientId/clientSecret onto // PROVIDERS.github, masking the fact that the real config had neither. This uses the real // config and asserts the real client_id is sent and no client_secret leaks out. const log = createLog(); const calls: any[] = []; await withMockedFetch( async (url, options = {}) => { calls.push({ url, options }); return jsonResponse({ access_token: "github-access", refresh_token: "github-refresh-next", expires_in: 3600, }); }, async () => { const result = await refreshGitHubToken("github-refresh", log); assert.deepEqual(result, { accessToken: "github-access", refreshToken: "github-refresh-next", expiresIn: 3600, }); } ); const body = bodyToString(calls[0].options.body); assert.equal(calls[0].url, OAUTH_ENDPOINTS.github.token); assert.ok(PROVIDERS.github.clientId, "PROVIDERS.github.clientId must be populated from the public cred"); assert.match(body, /client_id=Iv1\./, "the real public github client_id must be sent on refresh"); assert.ok(!body.includes("client_secret="), "no client_secret for the public github client"); }); test("refreshCopilotToken returns the short-lived copilot token", async () => { const log = createLog(); const calls: any[] = []; await withMockedFetch( async (url, options = {}) => { calls.push({ url, options }); return jsonResponse({ token: "copilot-session-token", expires_at: "2026-01-01T00:00:00.000Z", }); }, async () => { const result = await refreshCopilotToken("github-access-token", log); assert.deepEqual(result, { token: "copilot-session-token", expiresAt: "2026-01-01T00:00:00.000Z", }); } ); assert.equal(calls[0].url, "https://api.github.com/copilot_internal/v2/token"); assert.equal(calls[0].options.headers.Authorization, "token github-access-token"); }); test("supportsTokenRefresh, isUnrecoverableRefreshError and formatProviderCredentials cover provider helpers", async () => { const log = createLog(); await withPatchedProperties( PROVIDERS, { "custom-oauth-task-207": { tokenUrl: "https://auth.example.com/token" }, }, async () => { assert.equal(supportsTokenRefresh("claude"), true); assert.equal(supportsTokenRefresh("amazon-q"), true); assert.equal(supportsTokenRefresh("custom-oauth-task-207"), true); assert.equal(supportsTokenRefresh("missing-provider"), false); } ); assert.equal(isUnrecoverableRefreshError({ error: "refresh_token_reused" }), true); assert.equal(isUnrecoverableRefreshError({ error: "invalid_request" }), true); assert.equal(isUnrecoverableRefreshError({ error: "temporary_failure" }), false); assert.deepEqual( formatProviderCredentials( "gemini", { apiKey: "gemini-key", accessToken: "gemini-access", projectId: "project-1", refreshToken: "ignored", }, log ), { apiKey: "gemini-key", accessToken: "gemini-access", projectId: "project-1", } ); assert.deepEqual( formatProviderCredentials( "antigravity", { accessToken: "google-access", refreshToken: "google-refresh", }, log ), { accessToken: "google-access", refreshToken: "google-refresh", } ); assert.equal(formatProviderCredentials("missing-provider", {}, log), null); }); test("getAccessToken deduplicates concurrent refreshes for the same provider and token", async () => { const log = createLog(); let fetchCount = 0; await withPatchedProperties( PROVIDERS, { "custom-oauth-task-207": { tokenUrl: "https://auth.example.com/token" }, }, async () => { await withMockedFetch( async () => { fetchCount += 1; return jsonResponse({ access_token: "shared-access", refresh_token: "shared-refresh-next", expires_in: 600, }); }, async () => { const [first, second] = await Promise.all([ getAccessToken("custom-oauth-task-207", { refreshToken: "same-refresh" }, log), getAccessToken("custom-oauth-task-207", { refreshToken: "same-refresh" }, log), ]); assert.equal(fetchCount, 1); assert.strictEqual(first, second); assert.equal(first.accessToken, "shared-access"); } ); } ); }); test("getAccessToken cleans the in-flight cache after resolve and separates different tokens", async () => { const log = createLog(); let fetchCount = 0; await withPatchedProperties( PROVIDERS, { "custom-oauth-task-207": { tokenUrl: "https://auth.example.com/token" }, }, async () => { await withMockedFetch( async (_url, options: RequestInit = {}) => { fetchCount += 1; const refreshToken = new URLSearchParams(bodyToString(options.body)).get("refresh_token"); return jsonResponse({ access_token: `access-${refreshToken}`, refresh_token: `next-${refreshToken}`, expires_in: 600, }); }, async () => { const first = await getAccessToken( "custom-oauth-task-207", { refreshToken: "refresh-a" }, log ); const second = await getAccessToken( "custom-oauth-task-207", { refreshToken: "refresh-a" }, log ); const third = await getAccessToken( "custom-oauth-task-207", { refreshToken: "refresh-b" }, log ); assert.equal(fetchCount, 3); assert.equal(first.accessToken, "access-refresh-a"); assert.equal(second.accessToken, "access-refresh-a"); assert.equal(third.accessToken, "access-refresh-b"); } ); } ); }); test("getAccessToken returns null for invalid refresh token input", async () => { const log = createLog(); const result = await getAccessToken("codex", { refreshToken: null }, log); assert.equal(result, null); }); test("getAllAccessTokens refreshes only active connections with providers", async () => { const log = createLog(); let fetchCount = 0; await withPatchedProperties( PROVIDERS, { "custom-oauth-task-207": { tokenUrl: "https://auth.example.com/token" }, }, async () => { await withMockedFetch( async (_url, options: RequestInit = {}) => { fetchCount += 1; const refreshToken = new URLSearchParams(bodyToString(options.body)).get("refresh_token"); return jsonResponse({ access_token: `access-${refreshToken}`, refresh_token: `next-${refreshToken}`, expires_in: 900, }); }, async () => { const tokens = await getAllAccessTokens( { connections: [ { provider: "custom-oauth-task-207", refreshToken: "active-one", isActive: true, }, { provider: "custom-oauth-task-207", refreshToken: "inactive-one", isActive: false, }, { provider: null, refreshToken: "missing-provider", isActive: true, }, ], }, log ); assert.equal(fetchCount, 1); assert.deepEqual(tokens, { "custom-oauth-task-207": { accessToken: "access-active-one", refreshToken: "next-active-one", expiresIn: 900, }, }); } ); } ); }); test("refreshWithRetry retries to success and clears prior circuit-breaker state", async () => { const provider = `retry-success-${Date.now()}`; const log = createLog(); await refreshWithRetry(async () => null, 1, log, provider); assert.equal(getCircuitBreakerStatus()[provider].failures, 1); await withFastRetryTimers(async () => { let attempts = 0; const result = await refreshWithRetry( async () => { attempts += 1; return attempts === 2 ? { accessToken: "recovered" } : null; }, 3, log, provider ); assert.deepEqual(result, { accessToken: "recovered" }); assert.equal(attempts, 2); assert.equal(getCircuitBreakerStatus()[provider], undefined); }); }); test("refreshWithRetry trips the circuit breaker after repeated failures and blocks new calls", async () => { const provider = `retry-blocked-${Date.now()}`; const log = createLog(); for (let attempt = 0; attempt < 5; attempt += 1) { const result = await refreshWithRetry(async () => null, 1, log, provider); assert.equal(result, null); } assert.equal(isProviderBlocked(provider), true); assert.equal(getCircuitBreakerStatus()[provider].blocked, true); let called = false; const blockedResult = await refreshWithRetry( async () => { called = true; return { accessToken: "should-not-run" }; }, 1, log, provider ); assert.equal(blockedResult, null); assert.equal(called, false); }); test("isProviderBlocked clears expired circuit-breaker entries once cooldown passes", async () => { const provider = `retry-expiry-${Date.now()}`; const log = createLog(); for (let attempt = 0; attempt < 5; attempt += 1) { await refreshWithRetry(async () => null, 1, log, provider); } const blockedUntil = Date.parse(getCircuitBreakerStatus()[provider].blockedUntil as string); await withMockedNow(blockedUntil + 1, async () => { assert.equal(isProviderBlocked(provider), false); assert.equal(getCircuitBreakerStatus()[provider], undefined); }); }); // ─── Per-connection mutex tests ──────────────────────────────────────────────── test("getAccessToken per-connection mutex: 5 concurrent callers fire exactly one upstream call", async () => { const log = createLog(); let upstreamCallCount = 0; await withPatchedProperties( PROVIDERS, { "custom-oauth-conn-mutex": { tokenUrl: "https://auth.example.com/token" } }, async () => { await withMockedFetch( async () => { upstreamCallCount++; // Simulate 50ms upstream latency so all 5 callers are concurrent await new Promise((r) => setTimeout(r, 50)); return jsonResponse({ access_token: "new-access-token", refresh_token: "new-refresh-token", expires_in: 3600, }); }, async () => { const credentials = { connectionId: "conn-abc-123", refreshToken: "old-refresh-token", }; const results = await Promise.all([ getAccessToken("custom-oauth-conn-mutex", { ...credentials }, log), getAccessToken("custom-oauth-conn-mutex", { ...credentials }, log), getAccessToken("custom-oauth-conn-mutex", { ...credentials }, log), getAccessToken("custom-oauth-conn-mutex", { ...credentials }, log), getAccessToken("custom-oauth-conn-mutex", { ...credentials }, log), ]); assert.equal(upstreamCallCount, 1, "upstream called exactly once"); for (const result of results) { assert.equal(result?.accessToken, "new-access-token", "all callers got same token"); assert.equal(result?.refreshToken, "new-refresh-token"); } // All results are the same object reference (shared promise) assert.strictEqual(results[0], results[1]); assert.strictEqual(results[1], results[4]); } ); } ); }); test("getAccessToken per-connection mutex: logs concurrent refresh with waiter count", async () => { const log = createLog(); await withPatchedProperties( PROVIDERS, { "custom-oauth-conn-mutex": { tokenUrl: "https://auth.example.com/token" } }, async () => { await withMockedFetch( async () => { await new Promise((r) => setTimeout(r, 20)); return jsonResponse({ access_token: "tok", refresh_token: "rtok", expires_in: 600 }); }, async () => { const credentials = { connectionId: "conn-log-test", refreshToken: "rt" }; await Promise.all([ getAccessToken("custom-oauth-conn-mutex", { ...credentials }, log), getAccessToken("custom-oauth-conn-mutex", { ...credentials }, log), getAccessToken("custom-oauth-conn-mutex", { ...credentials }, log), ]); const concurrentLogs = log.entries.filter( (e) => e.level === "info" && e.message === "Concurrent refresh detected — sharing in-flight refresh" ); assert.ok(concurrentLogs.length >= 1, "logged at least one concurrent refresh event"); assert.ok( concurrentLogs.some((e) => e.meta?.connectionId === "conn-log-test"), "log includes connectionId" ); assert.ok( concurrentLogs.some((e) => typeof e.meta?.waiters === "number" && e.meta.waiters >= 1), "log includes waiter count" ); } ); } ); }); test("getAccessToken per-connection mutex: failed refresh propagates null to all waiters (idempotent error)", async () => { const log = createLog(); await withPatchedProperties( PROVIDERS, { "custom-oauth-conn-mutex": { tokenUrl: "https://auth.example.com/token" } }, async () => { await withMockedFetch( async () => { await new Promise((r) => setTimeout(r, 20)); // 400 response causes refreshAccessToken to return null return new Response("bad_request", { status: 400 }); }, async () => { const credentials = { connectionId: "conn-fail-test", refreshToken: "expired-rt" }; const results = await Promise.all([ getAccessToken("custom-oauth-conn-mutex", { ...credentials }, log), getAccessToken("custom-oauth-conn-mutex", { ...credentials }, log), getAccessToken("custom-oauth-conn-mutex", { ...credentials }, log), ]); for (const result of results) { assert.equal(result, null, "failed refresh returns null to all waiters"); } // Mutex cleaned up after failure assert.equal( getConnectionRefreshMutexStatus()["conn-fail-test"], undefined, "mutex entry removed after failure" ); } ); } ); }); test("getAccessToken per-connection mutex: different connections run independently", async () => { const log = createLog(); let upstreamCallCount = 0; await withPatchedProperties( PROVIDERS, { "custom-oauth-conn-mutex": { tokenUrl: "https://auth.example.com/token" } }, async () => { await withMockedFetch( async () => { upstreamCallCount++; await new Promise((r) => setTimeout(r, 20)); return jsonResponse({ access_token: `access-${upstreamCallCount}`, refresh_token: `refresh-${upstreamCallCount}`, expires_in: 600, }); }, async () => { const [groupA, groupB] = await Promise.all([ Promise.all([ getAccessToken( "custom-oauth-conn-mutex", { connectionId: "conn-A", refreshToken: "rt-a" }, log ), getAccessToken( "custom-oauth-conn-mutex", { connectionId: "conn-A", refreshToken: "rt-a" }, log ), ]), Promise.all([ getAccessToken( "custom-oauth-conn-mutex", { connectionId: "conn-B", refreshToken: "rt-b" }, log ), getAccessToken( "custom-oauth-conn-mutex", { connectionId: "conn-B", refreshToken: "rt-b" }, log ), ]), ]); assert.equal(upstreamCallCount, 2, "one upstream call per distinct connection"); assert.strictEqual(groupA[0], groupA[1], "conn-A callers share same result"); assert.strictEqual(groupB[0], groupB[1], "conn-B callers share same result"); assert.notStrictEqual(groupA[0], groupB[0], "conn-A and conn-B got different results"); } ); } ); }); test("getAccessToken per-connection mutex: mutex cleared after success, next call re-fires upstream", async () => { const log = createLog(); let upstreamCallCount = 0; // The rotation map (added for the codex-multi-auth pattern) is process-wide // and intentionally redirects a stale-token caller to the cached rotated // tokens. Clear it BEFORE and BETWEEN calls so this test exercises the // lower-level mutex semantics it was designed for. tokenRefresh._clearTokenRotationMap(); await withPatchedProperties( PROVIDERS, { "custom-oauth-conn-mutex": { tokenUrl: "https://auth.example.com/token" } }, async () => { await withMockedFetch( async () => { upstreamCallCount++; return jsonResponse({ access_token: `access-${upstreamCallCount}`, refresh_token: `refresh-${upstreamCallCount}`, expires_in: 600, }); }, async () => { const credentials = { connectionId: "conn-refire", refreshToken: "rt" }; const first = await getAccessToken("custom-oauth-conn-mutex", { ...credentials }, log); tokenRefresh._clearTokenRotationMap(); const second = await getAccessToken("custom-oauth-conn-mutex", { ...credentials }, log); assert.equal(upstreamCallCount, 2, "each sequential call fires upstream once"); assert.equal(first?.accessToken, "access-1"); assert.equal(second?.accessToken, "access-2"); } ); } ); }); // ─── Unrecoverable error bail-out tests ────────────────────────────────────── test("refreshWithRetry bails immediately on unrecoverable error without retrying", async () => { const provider = `bail-unrecoverable-${Date.now()}`; const log = createLog(); let callCount = 0; const result = await refreshWithRetry( async () => { callCount++; return { error: "unrecoverable_refresh_error", code: "http_400" }; }, 3, log, provider ); assert.equal(callCount, 1, "should only call refreshFn once (no retries)"); assert.deepEqual(result, { error: "unrecoverable_refresh_error", code: "http_400" }); const warnMessages = log.entries.filter((e) => e.level === "warn").map((e) => e.message); assert.ok( warnMessages.some((m) => String(m).includes("Unrecoverable")), "should log an unrecoverable warning" ); }); test("refreshWithRetry bails immediately on invalid_grant error without retrying", async () => { const provider = `bail-invalid-grant-${Date.now()}`; const log = createLog(); let callCount = 0; const result = await refreshWithRetry( async () => { callCount++; return { error: "invalid_grant", code: "http_400" }; }, 3, log, provider ); assert.equal(callCount, 1, "should only call refreshFn once (no retries)"); assert.deepEqual(result, { error: "invalid_grant", code: "http_400" }); }); test("refreshClaudeOAuthToken returns error object for invalid_grant (expired refresh token)", async () => { const log = createLog(); await withMockedFetch( async () => new Response(JSON.stringify({ error: "invalid_grant", error_description: "Token expired" }), { status: 400, headers: { "Content-Type": "application/json" }, }), async () => { const result = await refreshClaudeOAuthToken("expired-token", log); assert.ok(result && typeof result === "object", "should return error object, not null"); // Normalized to unrecoverable_refresh_error sentinel (Fix 6) assert.equal((result as any).error, "unrecoverable_refresh_error"); assert.equal((result as any).code, "invalid_grant"); assert.ok(isUnrecoverableRefreshError(result), "should be detected as unrecoverable"); } ); }); test("refreshClaudeOAuthToken returns null for transient server errors (not unrecoverable)", async () => { const log = createLog(); await withMockedFetch( async () => new Response(JSON.stringify({ error: "server_error" }), { status: 503, headers: { "Content-Type": "application/json" }, }), async () => { const result = await refreshClaudeOAuthToken("some-token", log); assert.equal(result, null, "transient server errors should return null (retryable)"); } ); });