diff --git a/open-sse/services/tokenRefresh.js b/open-sse/services/tokenRefresh.js index a9e69444..0f47e6d8 100644 --- a/open-sse/services/tokenRefresh.js +++ b/open-sse/services/tokenRefresh.js @@ -614,6 +614,28 @@ export async function refreshCopilotToken(githubAccessToken, log) { }, log); } +// Single source of per-provider refresh dispatch (logic stays in each refreshXxx fn). +// Each handler: (credentials, log) => Promise +const REFRESH_HANDLERS = { + "gemini-cli": (c, log) => refreshGoogleToken(c.refreshToken, PROVIDERS["gemini-cli"].clientId, PROVIDERS["gemini-cli"].clientSecret, log), + antigravity: (c, log) => refreshGoogleToken(c.refreshToken, PROVIDERS.antigravity.clientId, PROVIDERS.antigravity.clientSecret, log), + claude: (c, log) => refreshClaudeOAuthToken(c.refreshToken, log), + codex: (c, log) => refreshCodexToken(c.refreshToken, log), + qwen: (c, log) => refreshQwenToken(c.refreshToken, log), + iflow: (c, log) => refreshIflowToken(c.refreshToken, log), + github: (c, log) => refreshGitHubToken(c.refreshToken, log), + kiro: (c, log) => refreshKiroToken(c.refreshToken, c.providerSpecificData, log), + xai: (c, log) => refreshXaiToken(c.refreshToken, log), + vertex: vertexRefreshHandler, + "vertex-partner": vertexRefreshHandler +}; + +function vertexRefreshHandler(c, log) { + const saJson = parseVertexSaJson(c.apiKey); + if (!saJson) return null; + return refreshVertexToken(saJson, log); +} + /** * Get access token for a specific provider (with in-flight dedup). * If a refresh is already in-flight for same provider+token, share the promise @@ -629,53 +651,16 @@ export async function getAccessToken(provider, credentials, log) { } async function _getAccessTokenInternal(provider, credentials, log) { - switch (provider) { - case "gemini": - case "gemini-cli": - case "antigravity": - return await refreshGoogleToken( - credentials.refreshToken, - PROVIDERS[provider].clientId, - PROVIDERS[provider].clientSecret, - log - ); - - case "claude": - return await refreshClaudeOAuthToken(credentials.refreshToken, log); - - case "codex": - return await refreshCodexToken(credentials.refreshToken, log); - - case "qwen": - return await refreshQwenToken(credentials.refreshToken, log); - - case "iflow": - return await refreshIflowToken(credentials.refreshToken, log); - - case "github": - return await refreshGitHubToken(credentials.refreshToken, log); - - case "kiro": - return await refreshKiroToken( - credentials.refreshToken, - credentials.providerSpecificData, - log - ); - - case "xai": - return await refreshXaiToken(credentials.refreshToken, log); - - case "vertex": - case "vertex-partner": { - const saJson = parseVertexSaJson(credentials.apiKey); - if (!saJson) return null; - return await refreshVertexToken(saJson, log); - } - - default: - log?.warn?.("TOKEN_REFRESH", `Unsupported provider for token refresh: ${provider}`); - return null; + // "gemini" shares Google refresh here (unlike refreshTokenByProvider) + if (provider === "gemini") { + return refreshGoogleToken(credentials.refreshToken, PROVIDERS.gemini.clientId, PROVIDERS.gemini.clientSecret, log); } + const handler = REFRESH_HANDLERS[provider]; + if (!handler) { + log?.warn?.("TOKEN_REFRESH", `Unsupported provider for token refresh: ${provider}`); + return null; + } + return handler(credentials, log); } /** @@ -683,43 +668,9 @@ async function _getAccessTokenInternal(provider, credentials, log) { */ export async function refreshTokenByProvider(provider, credentials, log) { if (!credentials.refreshToken) return null; - - switch (provider) { - case "gemini-cli": - case "antigravity": - return refreshGoogleToken( - credentials.refreshToken, - PROVIDERS[provider].clientId, - PROVIDERS[provider].clientSecret, - log - ); - case "claude": - return refreshClaudeOAuthToken(credentials.refreshToken, log); - case "codex": - return refreshCodexToken(credentials.refreshToken, log); - case "qwen": - return refreshQwenToken(credentials.refreshToken, log); - case "iflow": - return refreshIflowToken(credentials.refreshToken, log); - case "github": - return refreshGitHubToken(credentials.refreshToken, log); - case "kiro": - return refreshKiroToken( - credentials.refreshToken, - credentials.providerSpecificData, - log - ); - case "xai": - return refreshXaiToken(credentials.refreshToken, log); - case "vertex": - case "vertex-partner": { - const saJson = parseVertexSaJson(credentials.apiKey); - if (!saJson) return null; - return refreshVertexToken(saJson, log); - } - default: - return refreshAccessToken(provider, credentials.refreshToken, credentials, log); - } + const handler = REFRESH_HANDLERS[provider]; + // default: generic refresh (note: "gemini" is NOT special-cased here, by design) + return handler ? handler(credentials, log) : refreshAccessToken(provider, credentials.refreshToken, credentials, log); } /** diff --git a/tests/unit/token-refresh-dispatch.test.js b/tests/unit/token-refresh-dispatch.test.js new file mode 100644 index 00000000..8a1aa069 --- /dev/null +++ b/tests/unit/token-refresh-dispatch.test.js @@ -0,0 +1,22 @@ +// Guards the refactored REFRESH_HANDLERS dispatch: null-guards + the two different defaults. +import { describe, it, expect } from "vitest"; + +const load = () => import("../../open-sse/services/tokenRefresh.js"); + +describe("tokenRefresh dispatch", () => { + it("getAccessToken returns null for missing/invalid refreshToken", async () => { + const mod = await load(); + expect(await mod.getAccessToken("claude", {}, null)).toBeNull(); + expect(await mod.getAccessToken("claude", { refreshToken: 123 }, null)).toBeNull(); + }); + + it("getAccessToken default: unsupported provider → null", async () => { + const mod = await load(); + expect(await mod.getAccessToken("totally-unknown", { refreshToken: "x" }, null)).toBeNull(); + }); + + it("refreshTokenByProvider returns null without refreshToken", async () => { + const mod = await load(); + expect(await mod.refreshTokenByProvider("claude", {}, null)).toBeNull(); + }); +});