1
0
Files
DiAL/tests/server/checker/runner/db/execute.test.ts
lanyuanxiaoyao 60a54b483f refactor: expect 类型模型重构,Raw/Resolved 双层分离与断言基础设施内聚
- 重命名 ContentRules→ContentExpectations, KeyValueExpect→KeyedExpectations
- 新增 Raw/Resolved 双层模型:resolve 阶段物化为执行计划,store 持久化 Raw 快照
- HTTP body 按需读取:status/headers 失败或无 body expectation 时不读取 body
- 新增 displayValueExpectation() 解包 failure.expected 用户可读展示
- 修复 checkEarlyTimeout 独立 lte/lt 检查,修复 KeyedExpectations JSON Schema
- 新增 expect/value.ts(resolve/check/display)、keyed.ts、content.ts、headers.ts、status.ts
- 删除旧 normalize.ts/matcher.ts/validate-matcher.ts/key-value.ts
- 更新 DEVELOPMENT.md:expect 五层管线表、displayValueExpectation、1.7↔1.10 交叉引用
- 同步 13 个 main specs,归档 refactor-expect-type-model 变更(62/62 tasks)
2026-05-20 16:12:48 +08:00

179 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, test } from "bun:test";
import type {
RawDbExpectConfig,
ResolvedDbExpectConfig,
ResolvedDbTarget,
} from "../../../../../src/server/checker/runner/db/types";
import type { CheckerContext } from "../../../../../src/server/checker/runner/types";
import { resolveContentExpectations } from "../../../../../src/server/checker/expect/content";
import { resolveKeyedExpectations } from "../../../../../src/server/checker/expect/keyed";
import { resolveValueExpectation } from "../../../../../src/server/checker/expect/value";
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<ResolvedDbTarget["db"]>, overrides?: { expect?: RawDbExpectConfig }): ResolvedDbTarget {
const raw = overrides?.expect;
const resolvedExpect: ResolvedDbExpectConfig | undefined = raw
? {
durationMs: resolveValueExpectation(raw.durationMs),
result: resolveContentExpectations(raw.result),
rowCount: resolveValueExpectation(raw.rowCount),
rows: raw.rows?.map((row) => resolveKeyedExpectations(row) ?? []),
}
: undefined;
return {
db: {
url: "sqlite://:memory:",
...db,
},
description: null,
expect: resolvedExpect,
group: "default",
id: "test-db",
intervalMs: 60000,
name: "test-db",
rawExpect: raw,
timeoutMs: 5000,
type: "db",
};
}
describe("DbChecker", () => {
test("无 query 时仅测试连接成功", async () => {
const result = await checker.execute(makeTarget({}), makeCtx());
expect(result.matched).toBe(true);
expect(result.observation).toMatchObject({ connected: true });
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.observation).toMatchObject({ connected: true, rowCount: 1 });
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.observation).toMatchObject({ connected: true, rowCount: 3 });
});
test("查询返回空结果", async () => {
const result = await checker.execute(makeTarget({ query: "SELECT 1 as n WHERE 1=0" }), makeCtx());
expect(result.matched).toBe(true);
expect(result.observation).toMatchObject({ connected: true, rowCount: 0 });
});
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");
});
});