codex-proxy / src /routes /__tests__ /usage-stats.test.ts
icebear
feat: usage stats page with time-series token tracking (#147)
df56b50 unverified
raw
history blame
3.79 kB
/**
* 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);
});
});
});