Spaces:
Paused
Paused
| /** | |
| * Tests for usage stats API routes. | |
| */ | |
| import { describe, it, expect, vi } from "vitest"; | |
| vi.mock("../../paths.js", () => ({ | |
| getDataDir: vi.fn(() => "/tmp/test-data"), | |
| })); | |
| import { Hono } from "hono"; | |
| import { UsageStatsStore, type UsageStatsPersistence, type UsageSnapshot } from "../../auth/usage-stats.js"; | |
| import { createUsageStatsRoutes } from "../admin/usage-stats.js"; | |
| import type { AccountPool } from "../../auth/account-pool.js"; | |
| function createMockPool(totals: { input_tokens: number; output_tokens: number; request_count: number }): AccountPool { | |
| return { | |
| getAllEntries: () => [ | |
| { | |
| id: "e1", | |
| status: "active", | |
| usage: totals, | |
| }, | |
| ], | |
| } as unknown as AccountPool; | |
| } | |
| function createStore(snapshots: UsageSnapshot[] = []): UsageStatsStore { | |
| const persistence: UsageStatsPersistence = { | |
| load: () => ({ version: 1, snapshots: [...snapshots] }), | |
| save: vi.fn(), | |
| }; | |
| return new UsageStatsStore(persistence); | |
| } | |
| describe("usage stats routes", () => { | |
| describe("GET /admin/usage-stats/summary", () => { | |
| it("returns cumulative totals", async () => { | |
| const pool = createMockPool({ input_tokens: 5000, output_tokens: 1000, request_count: 20 }); | |
| const store = createStore(); | |
| const app = new Hono(); | |
| app.route("/", createUsageStatsRoutes(pool, store)); | |
| const res = await app.request("/admin/usage-stats/summary"); | |
| expect(res.status).toBe(200); | |
| const body = await res.json(); | |
| expect(body.total_input_tokens).toBe(5000); | |
| expect(body.total_output_tokens).toBe(1000); | |
| expect(body.total_request_count).toBe(20); | |
| expect(body.total_accounts).toBe(1); | |
| expect(body.active_accounts).toBe(1); | |
| }); | |
| }); | |
| describe("GET /admin/usage-stats/history", () => { | |
| it("returns empty data_points when no history", async () => { | |
| const pool = createMockPool({ input_tokens: 0, output_tokens: 0, request_count: 0 }); | |
| const store = createStore(); | |
| const app = new Hono(); | |
| app.route("/", createUsageStatsRoutes(pool, store)); | |
| const res = await app.request("/admin/usage-stats/history"); | |
| expect(res.status).toBe(200); | |
| const body = await res.json(); | |
| expect(body.granularity).toBe("hourly"); | |
| expect(body.data_points).toEqual([]); | |
| }); | |
| it("returns delta data points with raw granularity", async () => { | |
| const now = Date.now(); | |
| const snapshots: UsageSnapshot[] = [ | |
| { | |
| timestamp: new Date(now - 3600_000).toISOString(), | |
| totals: { input_tokens: 100, output_tokens: 10, request_count: 1, active_accounts: 1 }, | |
| }, | |
| { | |
| timestamp: new Date(now).toISOString(), | |
| totals: { input_tokens: 500, output_tokens: 50, request_count: 5, active_accounts: 1 }, | |
| }, | |
| ]; | |
| const pool = createMockPool({ input_tokens: 500, output_tokens: 50, request_count: 5 }); | |
| const store = createStore(snapshots); | |
| const app = new Hono(); | |
| app.route("/", createUsageStatsRoutes(pool, store)); | |
| const res = await app.request("/admin/usage-stats/history?granularity=raw&hours=2"); | |
| expect(res.status).toBe(200); | |
| const body = await res.json(); | |
| expect(body.granularity).toBe("raw"); | |
| expect(body.data_points).toHaveLength(1); | |
| expect(body.data_points[0].input_tokens).toBe(400); | |
| }); | |
| it("rejects invalid granularity", async () => { | |
| const pool = createMockPool({ input_tokens: 0, output_tokens: 0, request_count: 0 }); | |
| const store = createStore(); | |
| const app = new Hono(); | |
| app.route("/", createUsageStatsRoutes(pool, store)); | |
| const res = await app.request("/admin/usage-stats/history?granularity=yearly"); | |
| expect(res.status).toBe(400); | |
| }); | |
| }); | |
| }); | |