import { LogPage } from "@umt/contracts"; import { sql } from "@umt/db"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { createLogStreamPoller, createLogsModule } from "../../src/modules/logs/index.ts"; import { logQueries } from "../../src/modules/logs/query.ts"; import { bearer, createHarness, type Harness, pgAvailable } from "../auth-settings/harness.ts"; import { DEV_KEY, json } from "../helpers.ts"; import { seedLogVolume, VOLUME_RUNS } from "./volume-seed.ts"; // 日志接口在真实数据量上的耗时(约 30,000 条引擎运行 + 定时任务运行 + 审计)。 // 改写前这几种查询在部署库上超时(> 60s);每个来源先按原始列筛选取前 n 条再算标题后,应在 1 秒内(阈值放宽到 1.5s 防抖动)。 const hasPg = await pgAvailable(); const LIMIT_MS = 1500; describe.skipIf(!hasPg)("日志查询耗时(约 3 万条引擎运行)", () => { let h: Harness; let admin: ReturnType; beforeAll(async () => { h = await createHarness({ engine: true }); const { createApp } = await import("../../src/http/app.ts"); const { createAuthSettingsModule } = await import("../../src/modules/auth-settings/index.ts"); h.app = createApp({ config: h.ctx.config, logger: h.ctx.logger, bus: h.bus, db: h.db, role: "all", version: "0.0.0-test", modules: [ createAuthSettingsModule({ setupTokenOnStart: false, engineSchema: h.engineSchema }), createLogsModule({ engineSchema: h.engineSchema }), ], ctx: h.ctx, }) as Harness["app"]; admin = bearer(h.app, DEV_KEY); await seedLogVolume(h.db, h.engineSchema); const n = await sql<{ n: number; }>`select count(*)::int as n from ${sql.raw(`"${h.engineSchema}".workflow_runs`)}`.execute(h.db); expect(n.rows[0]!.n).toBe(VOLUME_RUNS); // 预热连接 expect((await admin("GET", "/logs?limit=1")).status).toBe(200); }, 120_000); afterAll(async () => { await h?.close(); }); const since24h = () => encodeURIComponent(new Date(Date.now() - 24 * 3600_000).toISOString()); const cases: [string, () => string, (items: LogPage["items"]) => void][] = [ [ "level=error&since=24h", () => `?level=error&from=${since24h()}&limit=50`, (items) => { expect(items.length).toBeGreaterThan(0); expect(items.every((e) => e.level === "error")).toBe(true); }, ], [ "system=1&q=feeds", () => "?system=1&q=feeds&limit=50", (items) => { // 业务标题(RSS 源「my feeds 汇总」)命中的任务 expect(items.some((e) => e.source === "engine_run" && e.title === "my feeds 汇总")).toBe(true); }, ], [ "type=task&level=error", () => "?type=task&level=error&limit=50", (items) => { expect(items).toHaveLength(50); expect(items.every((e) => e.type === "task" && e.level === "error")).toBe(true); }, ], ["type=task", () => "?type=task&limit=50", (items) => expect(items).toHaveLength(50)], ["默认", () => "?limit=50", (items) => expect(items).toHaveLength(50)], // 关键词一条都不命中:引擎来源要扫完全部运行,是最坏情况 ["system=1&q=<不命中>", () => "?system=1&q=zz-nomatch&limit=50", (items) => expect(items).toEqual([])], ]; for (const [name, query, check] of cases) { it(`${name} < ${LIMIT_MS}ms`, async () => { const t = performance.now(); const res = await admin("GET", `/logs${query()}`); const ms = performance.now() - t; expect(res.status).toBe(200); const items = LogPage.parse((await json(res)).data).items; console.log(`[logs-volume] ${name}: ${ms.toFixed(0)}ms(${items.length} 条)`); check(items); expect(ms).toBeLessThan(LIMIT_MS); }); } it(`流的一轮轮询 < ${LIMIT_MS}ms`, async () => { const poller = createLogStreamPoller(logQueries(h.db, { engineSchema: h.engineSchema }), { system: "1" }); for (let i = 0; i < 2; i++) { const t = performance.now(); await poller.poll(); const ms = performance.now() - t; console.log(`[logs-volume] 流轮询 #${i + 1}: ${ms.toFixed(0)}ms`); expect(ms).toBeLessThan(LIMIT_MS); } }); });