Merge remote-tracking branch 'origin/main' into codex/api-analytics-hardening

# Conflicts:
#	README.md
#	lib/queries.test.ts
#	lib/queries.ts
This commit is contained in:
2026-05-27 15:30:26 +08:00
4 changed files with 52 additions and 7 deletions

View File

@@ -1,6 +1,9 @@
import { beforeEach, describe, expect, mock, test } from "bun:test";
const queryMock = mock(async () => [
type QueryRow = Record<string, unknown>;
type QueryParams = Array<string | number | boolean | null>;
const queryMock = mock(async (): Promise<QueryRow[]> => [
{
date: "2026-04-01 13:00:00",
calls: 1,
@@ -21,7 +24,7 @@ const { getLogs, getTrends, getUserDetail } = await import("./queries");
describe("getTrends", () => {
beforeEach(() => {
queryMock.mockClear();
queryMock.mockImplementation(async () => [
queryMock.mockImplementation(async (): Promise<QueryRow[]> => [
{
date: "2026-04-01 13:00:00",
calls: 1,
@@ -62,7 +65,7 @@ describe("getUserDetail", () => {
});
test("returns token breakdown with nested model rows for user details", async () => {
queryMock.mockImplementation(async (sql: string) => {
queryMock.mockImplementation(async (sql: string): Promise<QueryRow[]> => {
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 },
@@ -127,16 +130,19 @@ describe("getUserDetail", () => {
describe("getLogs", () => {
beforeEach(() => {
queryMock.mockClear();
queryMock.mockImplementation(async (sql: string) => {
if (sql.includes("COUNT(*)::int as total")) {
queryMock.mockImplementation(async (sql: string): Promise<QueryRow[]> => {
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 [];
});
});
@@ -157,4 +163,31 @@ describe("getLogs", () => {
expect(result.page_size).toBe(100);
expect(dataQuery?.[1]).toEqual([100, 0]);
});
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]);
});
});