From d4b95380b18b40de2bce706617b1cdc8dac20a3d Mon Sep 17 00:00:00 2001 From: decolua Date: Sat, 13 Jun 2026 21:46:13 +0700 Subject: [PATCH] =?UTF-8?q?refactor(open-sse):=20usage.js=20dispatcher=20s?= =?UTF-8?q?witch=20=E2=86=92=20USAGE=5FHANDLERS=20registry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gộp switch 13 nhánh getUsageForProvider thành 1 registry object (provider → handler), mỗi handler giữ nguyên signature/args qua ctx. Behavior giữ nguyên (ollama vẫn chỉ nhận accessToken như cũ). Thêm tests/unit/usage-dispatch.test.js guard dispatch. Co-authored-by: Cursor --- open-sse/services/usage.js | 54 +++++++++++++------------------ tests/unit/usage-dispatch.test.js | 39 ++++++++++++++++++++++ 2 files changed, 61 insertions(+), 32 deletions(-) create mode 100644 tests/unit/usage-dispatch.test.js diff --git a/open-sse/services/usage.js b/open-sse/services/usage.js index 95c4be7c..9425f09d 100644 --- a/open-sse/services/usage.js +++ b/open-sse/services/usage.js @@ -63,6 +63,25 @@ const CLAUDE_CONFIG = { * @param {Object} connection - Provider connection with accessToken * @returns {Object} Usage data with quotas */ +// provider → usage handler (ctx carries every arg each handler needs) +const USAGE_HANDLERS = { + github: (c) => getGitHubUsage(c.accessToken, c.providerSpecificData, c.proxyOptions), + "gemini-cli": (c) => getGeminiUsage(c.accessToken, c.providerDataWithProjectId, c.proxyOptions), + antigravity: (c) => getAntigravityUsage(c.accessToken, c.providerSpecificData, c.proxyOptions), + claude: (c) => getClaudeUsage(c.accessToken, c.proxyOptions), + codex: (c) => getCodexUsage(c.accessToken, c.proxyOptions), + kiro: (c) => getKiroUsage(c.accessToken, c.providerSpecificData, c.proxyOptions), + qoder: (c) => getQoderUsage(c.accessToken, c.proxyOptions), + qwen: (c) => getQwenUsage(c.accessToken, c.providerSpecificData), + iflow: (c) => getIflowUsage(c.accessToken), + ollama: (c) => getOllamaUsage(c.accessToken), + glm: (c) => getGlmUsage(c.apiKey, c.provider, c.proxyOptions), + "glm-cn": (c) => getGlmUsage(c.apiKey, c.provider, c.proxyOptions), + minimax: (c) => getMiniMaxUsage(c.apiKey, c.provider, c.proxyOptions), + "minimax-cn": (c) => getMiniMaxUsage(c.apiKey, c.provider, c.proxyOptions), + "vercel-ai-gateway": (c) => getVercelAiGatewayUsage(c.apiKey, c.proxyOptions), +}; + export async function getUsageForProvider(connection, proxyOptions = null) { const { provider, accessToken, apiKey, providerSpecificData, projectId } = connection; const providerDataWithProjectId = { @@ -70,38 +89,9 @@ export async function getUsageForProvider(connection, proxyOptions = null) { ...(projectId ? { projectId } : {}), }; - switch (provider) { - case "github": - return await getGitHubUsage(accessToken, providerSpecificData, proxyOptions); - case "gemini-cli": - return await getGeminiUsage(accessToken, providerDataWithProjectId, proxyOptions); - case "antigravity": - return await getAntigravityUsage(accessToken, providerSpecificData, proxyOptions); - case "claude": - return await getClaudeUsage(accessToken, proxyOptions); - case "codex": - return await getCodexUsage(accessToken, proxyOptions); - case "kiro": - return await getKiroUsage(accessToken, providerSpecificData, proxyOptions); - case "qoder": - return await getQoderUsage(accessToken, proxyOptions); - case "qwen": - return await getQwenUsage(accessToken, providerSpecificData); - case "iflow": - return await getIflowUsage(accessToken); - case "ollama": - return await getOllamaUsage(accessToken); - case "glm": - case "glm-cn": - return await getGlmUsage(apiKey, provider, proxyOptions); - case "minimax": - case "minimax-cn": - return await getMiniMaxUsage(apiKey, provider, proxyOptions); - case "vercel-ai-gateway": - return await getVercelAiGatewayUsage(apiKey, proxyOptions); - default: - return { message: `Usage API not implemented for ${provider}` }; - } + const handler = USAGE_HANDLERS[provider]; + if (!handler) return { message: `Usage API not implemented for ${provider}` }; + return await handler({ provider, accessToken, apiKey, providerSpecificData, providerDataWithProjectId, proxyOptions }); } /** diff --git a/tests/unit/usage-dispatch.test.js b/tests/unit/usage-dispatch.test.js new file mode 100644 index 00000000..e4d4ac82 --- /dev/null +++ b/tests/unit/usage-dispatch.test.js @@ -0,0 +1,39 @@ +// Guards the refactored USAGE_HANDLERS dispatch: unsupported → message, supported → routed. +import { describe, it, expect, vi, beforeEach } from "vitest"; + +// Stub network so handlers don't hit real APIs; each call resolves an empty 200. +vi.mock("../../open-sse/utils/proxyFetch.js", () => ({ + proxyAwareFetch: vi.fn(async () => ({ + ok: true, + status: 200, + json: async () => ({}), + text: async () => "{}", + })), +})); + +const load = () => import("../../open-sse/services/usage.js"); +const SUPPORTED = [ + "github", "gemini-cli", "antigravity", "claude", "codex", "kiro", + "qoder", "qwen", "iflow", "ollama", "glm", "glm-cn", + "minimax", "minimax-cn", "vercel-ai-gateway", +]; + +describe("usage dispatch", () => { + beforeEach(() => vi.clearAllMocks()); + + it("unsupported provider → not-implemented message", async () => { + const { getUsageForProvider } = await load(); + const res = await getUsageForProvider({ provider: "totally-unknown" }); + expect(res).toEqual({ message: "Usage API not implemented for totally-unknown" }); + }); + + it("every supported provider routes to its handler (no fallback message)", async () => { + const { getUsageForProvider } = await load(); + for (const provider of SUPPORTED) { + const res = await getUsageForProvider({ provider, accessToken: "t", apiKey: "k" }); + // Routed handler must return an object and never the unsupported fallback + expect(res, `${provider} routed`).toBeTypeOf("object"); + expect(res?.message).not.toBe(`Usage API not implemented for ${provider}`); + } + }); +});