| import { describe, it, expect, vi, afterEach } from "vitest"; |
|
|
| vi.mock("server-only", () => ({})); |
|
|
| const { cookieStore } = vi.hoisted(() => ({ |
| cookieStore: { |
| get: vi.fn(), |
| }, |
| })); |
|
|
| vi.mock("next/headers", () => ({ |
| cookies: vi.fn().mockResolvedValue(cookieStore), |
| })); |
|
|
| const makeHeaders = (values: Record<string, string>) => ({ |
| get: (key: string) => values[key.toLowerCase()] ?? values[key] ?? null, |
| }); |
|
|
| describe("GET /api/usage route", () => { |
| const originalEnv = process.env; |
|
|
| afterEach(() => { |
| process.env = originalEnv; |
| vi.unstubAllGlobals(); |
| vi.clearAllMocks(); |
| cookieStore.get.mockReturnValue(undefined); |
| }); |
|
|
| it("returns 200 with fallback payload when all upstreams fail", async () => { |
| process.env = { ...originalEnv, MANAGEMENT_API_KEY: "test-key", USAGE_BACKEND_BASE_URL: "http://primary.local" }; |
| cookieStore.get.mockReturnValue(undefined); |
|
|
| vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("connect ECONNREFUSED"))); |
|
|
| const { GET } = await import("@/app/api/usage/route"); |
| const res = await GET(new Request("http://localhost/api/usage")); |
| expect(res.status).toBe(200); |
|
|
| const body = await res.json(); |
| expect(body.error).toBe("upstream_unreachable"); |
| expect(body.usage).toBeTruthy(); |
| expect(body.failed_requests).toBe(0); |
| }); |
|
|
| it("passes through successful upstream JSON", async () => { |
| process.env = { ...originalEnv, MANAGEMENT_API_KEY: "test-key", USAGE_BACKEND_BASE_URL: "http://primary.local" }; |
| cookieStore.get.mockReturnValue(undefined); |
|
|
| vi.stubGlobal( |
| "fetch", |
| vi.fn().mockResolvedValue({ |
| ok: true, |
| headers: makeHeaders({ "content-type": "application/json" }), |
| json: async () => ({ |
| usage: { |
| total_requests: 1, |
| success_count: 1, |
| failure_count: 0, |
| total_tokens: 10, |
| apis: {}, |
| requests_by_day: {}, |
| requests_by_hour: {}, |
| tokens_by_day: {}, |
| tokens_by_hour: {}, |
| }, |
| failed_requests: 0, |
| }), |
| }) |
| ); |
|
|
| const { GET } = await import("@/app/api/usage/route"); |
| const res = await GET(new Request("http://localhost/api/usage")); |
| expect(res.status).toBe(200); |
|
|
| const body = await res.json(); |
| expect(body.failed_requests).toBe(0); |
| expect(body.usage.total_requests).toBe(1); |
| }); |
|
|
| it("aggregates usage from primary and extra sources", async () => { |
| process.env = { ...originalEnv, MANAGEMENT_API_KEY: "test-key", USAGE_BACKEND_BASE_URL: "http://primary.local" }; |
| cookieStore.get.mockReturnValue({ |
| value: encodeURIComponent(JSON.stringify(["http://secondary.local"])), |
| }); |
|
|
| const fetchMock = vi.fn(async (url: string | URL) => { |
| const asString = String(url); |
| if (asString.includes("primary.local")) { |
| return { |
| ok: true, |
| headers: makeHeaders({ "content-type": "application/json" }), |
| json: async () => ({ |
| usage: { |
| total_requests: 2, |
| success_count: 2, |
| failure_count: 0, |
| total_tokens: 100, |
| apis: { |
| primary: { |
| total_requests: 2, |
| total_tokens: 100, |
| models: { |
| "gpt-4-turbo": { |
| total_requests: 2, |
| total_tokens: 100, |
| details: [], |
| }, |
| }, |
| }, |
| }, |
| requests_by_day: { "2026-02-12": 2 }, |
| requests_by_hour: { "12": 2 }, |
| tokens_by_day: { "2026-02-12": 100 }, |
| tokens_by_hour: { "12": 100 }, |
| }, |
| failed_requests: 0, |
| }), |
| }; |
| } |
|
|
| return { |
| ok: true, |
| headers: makeHeaders({ "content-type": "application/json" }), |
| json: async () => ({ |
| usage: { |
| total_requests: 3, |
| success_count: 2, |
| failure_count: 1, |
| total_tokens: 150, |
| apis: { |
| secondary: { |
| total_requests: 3, |
| total_tokens: 150, |
| models: { |
| "gpt-4-turbo": { |
| total_requests: 3, |
| total_tokens: 150, |
| details: [], |
| }, |
| }, |
| }, |
| }, |
| requests_by_day: { "2026-02-12": 3 }, |
| requests_by_hour: { "12": 3 }, |
| tokens_by_day: { "2026-02-12": 150 }, |
| tokens_by_hour: { "12": 150 }, |
| }, |
| failed_requests: 1, |
| }), |
| }; |
| }); |
|
|
| vi.stubGlobal("fetch", fetchMock); |
|
|
| const { GET } = await import("@/app/api/usage/route"); |
| const res = await GET(new Request("http://localhost/api/usage")); |
| expect(res.status).toBe(200); |
|
|
| const body = await res.json(); |
| expect(body.usage.total_requests).toBe(5); |
| expect(body.usage.success_count).toBe(4); |
| expect(body.usage.failure_count).toBe(1); |
| expect(body.usage.total_tokens).toBe(250); |
| expect(body.failed_requests).toBe(1); |
| expect(body.usage.requests_by_day["2026-02-12"]).toBe(5); |
| expect(body.usage.tokens_by_hour["12"]).toBe(250); |
| expect(body.usage.apis.primary.total_requests).toBe(2); |
| expect(body.usage.apis.secondary.total_requests).toBe(3); |
| }); |
| }); |
|
|
|
|