import { describe, expect, test } from "bun:test"; import type { ResolvedDbTarget } from "../../../../../src/server/checker/runner/db/types"; import type { CheckerContext } from "../../../../../src/server/checker/runner/types"; import { DbChecker } from "../../../../../src/server/checker/runner/db/execute"; const checker = new DbChecker(); function makeCtx(timeoutMs = 5000): CheckerContext { const controller = new AbortController(); setTimeout(() => controller.abort(), timeoutMs); return { signal: controller.signal }; } function makeTarget(db: Partial, overrides?: Partial): ResolvedDbTarget { return { db: { url: "sqlite://:memory:", ...db, }, description: null, group: "default", id: "test-db", intervalMs: 60000, name: "test-db", timeoutMs: 5000, type: "db", ...overrides, }; } describe("DbChecker", () => { test("无 query 时仅测试连接成功", async () => { const result = await checker.execute(makeTarget({}), makeCtx()); expect(result.matched).toBe(true); expect(result.statusDetail).toBe("connected"); expect(result.failure).toBeNull(); }); test("执行查询成功", async () => { const result = await checker.execute(makeTarget({ query: "SELECT 1 as num, 'hello' as str" }), makeCtx()); expect(result.matched).toBe(true); expect(result.statusDetail).toBe("1 rows"); expect(result.durationMs).toBeGreaterThanOrEqual(0); }); test("查询返回多行", async () => { const result = await checker.execute( makeTarget({ query: "SELECT 1 as n UNION ALL SELECT 2 UNION ALL SELECT 3" }), makeCtx(), ); expect(result.matched).toBe(true); expect(result.statusDetail).toBe("3 rows"); }); test("查询返回空结果", async () => { const result = await checker.execute(makeTarget({ query: "SELECT 1 as n WHERE 1=0" }), makeCtx()); expect(result.matched).toBe(true); expect(result.statusDetail).toBe("0 rows"); }); test("连接失败返回 connect phase 错误", async () => { const result = await checker.execute(makeTarget({ url: "sqlite:///nonexistent/path/db.db" }), makeCtx()); expect(result.matched).toBe(false); expect(result.failure!.phase).toBe("connect"); expect(result.failure!.message).toBeTruthy(); }); test("SQL 语法错误返回 query phase 错误", async () => { const result = await checker.execute(makeTarget({ query: "SELECT INVALID SQL" }), makeCtx()); expect(result.matched).toBe(false); expect(result.failure!.phase).toBe("query"); expect(result.failure!.message).toBeTruthy(); }); test("durationMs 超时返回失败", async () => { const result = await checker.execute( makeTarget({ query: "SELECT 1" }, { expect: { durationMs: { lt: 0 } } }), makeCtx(), ); expect(result.matched).toBe(false); expect(result.failure!.phase).toBe("duration"); }); test("rowCount 断言通过", async () => { const result = await checker.execute( makeTarget({ query: "SELECT 1 UNION ALL SELECT 2" }, { expect: { rowCount: { gte: 2 } } }), makeCtx(), ); expect(result.matched).toBe(true); }); test("rowCount 断言失败", async () => { const result = await checker.execute( makeTarget({ query: "SELECT 1 UNION ALL SELECT 2" }, { expect: { rowCount: { gte: 5 } } }), makeCtx(), ); expect(result.matched).toBe(false); expect(result.failure!.phase).toBe("rowCount"); }); test("rows 断言通过(字面量形式)", async () => { const result = await checker.execute( makeTarget({ query: "SELECT 100 as cnt" }, { expect: { rows: [{ cnt: 100 }] } }), makeCtx(), ); expect(result.matched).toBe(true); }); test("rows 断言通过(operator 形式)", async () => { const result = await checker.execute( makeTarget({ query: "SELECT 150 as cnt" }, { expect: { rows: [{ cnt: { gte: 100 } }] } }), makeCtx(), ); expect(result.matched).toBe(true); }); test("rows 断言失败", async () => { const result = await checker.execute( makeTarget({ query: "SELECT 50 as cnt" }, { expect: { rows: [{ cnt: { gte: 100 } }] } }), makeCtx(), ); expect(result.matched).toBe(false); expect(result.failure!.phase).toBe("row"); expect(result.failure!.path).toBe("rows[0].cnt"); }); test("rows 结果行数不足", async () => { const result = await checker.execute( makeTarget({ query: "SELECT 1 as n" }, { expect: { rows: [{ n: 1 }, { n: 2 }] } }), makeCtx(), ); expect(result.matched).toBe(false); expect(result.failure!.phase).toBe("row"); expect(result.failure!.message).toContain("行数不足"); }); test("rows 只检查声明的列", async () => { const result = await checker.execute( makeTarget({ query: "SELECT 1 as cnt, 'ignored' as other" }, { expect: { rows: [{ cnt: { gte: 1 } }] } }), makeCtx(), ); expect(result.matched).toBe(true); }); test("serialize 屏蔽凭据", () => { const target = makeTarget({ url: "postgres://user:pass@host:5432/db" }); const s = checker.serialize(target); expect(s.target).toBe("postgres://***:***@host:5432/db"); const config = JSON.parse(s.config) as { url: string }; expect(config.url).toBe("postgres://***:***@host:5432/db"); }); test("serialize 无凭据的 url 保持原样", () => { const target = makeTarget({ url: "sqlite:///data/app.db" }); const s = checker.serialize(target); expect(s.target).toBe("sqlite:///data/app.db"); }); });