File size: 3,786 Bytes
df56b50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/**
 * 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);
    });
  });
});