import { beforeEach, describe, expect, mock, test } from "bun:test"; type QueryRow = Record; type QueryParams = Array; const queryMock = mock(async (): Promise => [ { date: "2026-04-01 13:00:00", calls: 1, prompt_tokens: 10, completion_tokens: 20, cache_creation_tokens: 3, cache_read_tokens: 4, quota: 100, }, ]); mock.module("./db", () => ({ query: queryMock, })); const { getLogs, getTrends, getUserDetail } = await import("./queries"); describe("getTrends", () => { beforeEach(() => { queryMock.mockClear(); queryMock.mockImplementation(async (): Promise => [ { date: "2026-04-01 13:00:00", calls: 1, prompt_tokens: 10, completion_tokens: 20, cache_creation_tokens: 3, cache_read_tokens: 4, quota: 100, }, ]); }); test("adds optional detail filters to the trend query", async () => { await getTrends("day", 101, 201, { username: "alice" }); expect(queryMock.mock.calls[0][0]).toContain("username = $3"); expect(queryMock.mock.calls[0][1]).toEqual([101, 201, "alice"]); await getTrends("day", 102, 202, { model: "gpt-4.1" }); expect(queryMock.mock.calls[1][0]).toContain("= $3"); expect(queryMock.mock.calls[1][1]).toEqual([102, 202, "gpt-4.1"]); await getTrends("day", 103, 203, { channelId: 7 }); expect(queryMock.mock.calls[2][0]).toContain("channel_id = $3"); expect(queryMock.mock.calls[2][1]).toEqual([103, 203, 7]); }); test("keeps the hour in hourly trend buckets", async () => { const trends = await getTrends("hour", 104, 204); expect(queryMock.mock.calls[0][0]).toContain("date_trunc('hour'"); expect(trends[0].date).toBe("2026-04-01 13:00"); }); }); describe("getUserDetail", () => { beforeEach(() => { queryMock.mockClear(); }); test("returns token breakdown with nested model rows for user details", async () => { queryMock.mockImplementation(async (sql: string): Promise => { if (sql.includes("token_models AS")) { return [ { token_name: "prod-key", model: "claude-sonnet-4", calls: 3, tokens: 100, cache_creation: 5, cache_read: 7, quota: 50 }, { token_name: "", model: "gpt-4o", calls: 1, tokens: 20, cache_creation: 0, cache_read: 0, quota: 10 }, ]; } if (sql.includes("GROUP BY token_name")) { return [ { token_name: "prod-key", calls: 3, tokens: 100, cache_creation: 5, cache_read: 7, quota: 50 }, { token_name: "", calls: 1, tokens: 20, cache_creation: 0, cache_read: 0, quota: 10 }, ]; } if (sql.includes("GROUP BY model")) { return []; } if (sql.includes("SELECT id, display_name FROM users")) { return []; } if (sql.includes("SELECT id FROM users WHERE username")) { return []; } if (sql.includes("SELECT COUNT(*)::int as calls")) { return [ { calls: 4, prompt: 120, completion: 0, cache_creation: 5, cache_read: 7, quota: 60 }, ]; } return []; }); const detail = await getUserDetail("token-user", 301, 401); expect(detail.tokens).toEqual([ { name: "prod-key", calls: 3, total_tokens: 112, quota: 50, models: [ { name: "claude-sonnet-4", calls: 3, total_tokens: 112, quota: 50 }, ], }, { name: "", calls: 1, total_tokens: 20, quota: 10, models: [ { name: "gpt-4o", calls: 1, total_tokens: 20, quota: 10 }, ], }, ]); expect(queryMock.mock.calls.some(([sql]) => String(sql).includes("token_name"))).toBe(true); }); }); describe("getLogs", () => { beforeEach(() => { queryMock.mockClear(); queryMock.mockImplementation(async (sql: string): Promise => { if (sql.includes("SELECT COUNT(*)::int as total")) { return [{ total: 0 }]; } if (sql.includes("SELECT id, display_name FROM users")) { return []; } if (sql.includes("SELECT id, name FROM channels")) { return []; } return []; }); }); test("filters logs by fuzzy display name through the users table", async () => { await getLogs({ startTs: 501, endTs: 601, username: "张三" }); const countCall = queryMock.mock.calls[0]; const countSql = String(countCall[0]); const countParams = countCall[1] as QueryParams; expect(countSql).toContain("username ILIKE $3"); expect(countSql).toContain("SELECT id FROM users"); expect(countSql).toContain("display_name ILIKE $3"); expect(countSql).toContain("users.username ILIKE $3"); expect(countParams).toEqual([501, 601, "%张三%"]); }); test("uses the same fuzzy user filter for paginated log rows", async () => { await getLogs({ page: 2, pageSize: 25, username: "adm" }); const dataCall = queryMock.mock.calls[3]; const dataSql = String(dataCall[0]); const dataParams = dataCall[1] as QueryParams; expect(dataSql).toContain("username ILIKE $1"); expect(dataSql).toContain("display_name ILIKE $1"); expect(dataSql).toContain("users.username ILIKE $1"); expect(dataParams).toEqual(["%adm%", 25, 25]); }); });