1
0

feat: 重构为多类型 checker 通用框架,支持 HTTP 与命令检查

- 引入 typed target 判别联合,支持 http 与 command 两种 checker
- expect 重构为有序规则数组,按配置顺序快速失败并生成结构化 failure
- 新增 command runner,支持 exec + args 本地命令执行
- 引入全局并发限制 maxConcurrentChecks 和 size 解析 (KB/MB/GB)
- HTTP/command 各自独立 expect pipeline,应用领域默认成功语义
- SQLite schema、API、Dashboard 全链路调整为 checker 通用契约
- 补充完整测试覆盖(192 tests),更新 README 与示例配置
This commit is contained in:
2026-05-10 22:25:21 +08:00
parent 599d973cbd
commit b8810f1182
46 changed files with 3562 additions and 1062 deletions

View File

@@ -0,0 +1,168 @@
import { describe, expect, test } from "bun:test";
import { checkCommandExpect } from "../../../../src/server/checker/expect/command";
import type { CommandObservation } from "../../../../src/server/checker/expect/command";
import type { CommandExpectConfig } from "../../../../src/server/checker/types";
function obs(overrides: Partial<CommandObservation> = {}): CommandObservation {
return {
exitCode: 0,
stdout: "",
stderr: "",
durationMs: 100,
...overrides,
};
}
describe("checkCommandExpect", () => {
test("无 expect 配置时默认检查 exitCode [0] 匹配成功", () => {
const r = checkCommandExpect(obs());
expect(r.matched).toBe(true);
expect(r.failure).toBeNull();
});
test("无 expect 配置时 exitCode 非 0 匹配失败", () => {
const r = checkCommandExpect(obs({ exitCode: 1 }));
expect(r.matched).toBe(false);
expect(r.failure).not.toBeNull();
expect(r.failure!.phase).toBe("exitCode");
expect(r.failure!.kind).toBe("mismatch");
});
test("exitCode 匹配指定退出码", () => {
const cfg: CommandExpectConfig = { exitCode: [0, 1] };
expect(checkCommandExpect(obs({ exitCode: 0 }), cfg).matched).toBe(true);
expect(checkCommandExpect(obs({ exitCode: 1 }), cfg).matched).toBe(true);
expect(checkCommandExpect(obs({ exitCode: 2 }), cfg).matched).toBe(false);
});
test("exitCode 不匹配返回 phase=exitCode 的失败", () => {
const r = checkCommandExpect(obs({ exitCode: 2 }), { exitCode: [0] });
expect(r.matched).toBe(false);
expect(r.failure!.phase).toBe("exitCode");
expect(r.failure!.expected).toEqual([0]);
expect(r.failure!.actual).toBe(2);
});
test("duration 在限制内匹配成功", () => {
const r = checkCommandExpect(obs({ durationMs: 50 }), { maxDurationMs: 100 });
expect(r.matched).toBe(true);
});
test("duration 超过限制匹配失败", () => {
const r = checkCommandExpect(obs({ durationMs: 200 }), { maxDurationMs: 100 });
expect(r.matched).toBe(false);
expect(r.failure!.phase).toBe("duration");
});
test("stdout TextRule 数组匹配", () => {
const o = obs({ stdout: "build completed successfully" });
expect(checkCommandExpect(o, { stdout: [{ contains: "completed" }] }).matched).toBe(true);
expect(checkCommandExpect(o, { stdout: [{ contains: "failed" }] }).matched).toBe(false);
expect(checkCommandExpect(o, { stdout: [{ match: "completed.*successfully$" }] }).matched).toBe(true);
});
test("stdout 多条规则全部通过", () => {
const o = obs({ stdout: "version: 3.2.1, build: ok" });
const r = checkCommandExpect(o, {
stdout: [{ contains: "version" }, { match: "\\d+\\.\\d+\\.\\d+" }],
});
expect(r.matched).toBe(true);
});
test("stdout 第一条规则失败立即返回", () => {
const o = obs({ stdout: "error occurred" });
const r = checkCommandExpect(o, {
stdout: [{ contains: "success" }, { contains: "error" }],
});
expect(r.matched).toBe(false);
expect(r.failure!.phase).toBe("stdout");
expect(r.failure!.path).toBe("stdout[0]");
});
test("stderr TextRule 数组匹配", () => {
const o = obs({ stderr: "warning: deprecated" });
expect(checkCommandExpect(o, { stderr: [{ contains: "warning" }] }).matched).toBe(true);
expect(checkCommandExpect(o, { stderr: [{ contains: "error" }] }).matched).toBe(false);
});
test("stdout 失败阻止 stderr 检查", () => {
const o = obs({ stdout: "bad output", stderr: "warning message" });
const r = checkCommandExpect(o, {
exitCode: [0],
stdout: [{ contains: "success" }],
stderr: [{ contains: "warning" }],
});
expect(r.matched).toBe(false);
expect(r.failure!.phase).toBe("stdout");
});
test("stdout 通过但 stderr 失败", () => {
const o = obs({ stdout: "ok", stderr: "fatal error" });
const r = checkCommandExpect(o, {
stdout: [{ contains: "ok" }],
stderr: [{ equals: "clean" }],
});
expect(r.matched).toBe(false);
expect(r.failure!.phase).toBe("stderr");
});
test("完整流水线 exitCode->duration->stdout->stderr 全部通过", () => {
const o = obs({
exitCode: 0,
durationMs: 50,
stdout: "build success",
stderr: "",
});
const r = checkCommandExpect(o, {
exitCode: [0],
maxDurationMs: 100,
stdout: [{ contains: "success" }],
stderr: [{ empty: true }],
});
expect(r.matched).toBe(true);
expect(r.failure).toBeNull();
});
test("完整流水线 exitCode 通过但 duration 失败", () => {
const o = obs({ exitCode: 0, durationMs: 500 });
const r = checkCommandExpect(o, {
exitCode: [0],
maxDurationMs: 100,
stdout: [{ contains: "ok" }],
});
expect(r.matched).toBe(false);
expect(r.failure!.phase).toBe("duration");
});
test("完整流水线 exitCode/duration 通过但 stdout 失败", () => {
const o = obs({ exitCode: 0, durationMs: 50, stdout: "error" });
const r = checkCommandExpect(o, {
exitCode: [0],
maxDurationMs: 100,
stdout: [{ contains: "success" }],
});
expect(r.matched).toBe(false);
expect(r.failure!.phase).toBe("stdout");
});
test("完整流水线 exitCode/duration/stdout 通过但 stderr 失败", () => {
const o = obs({ exitCode: 0, durationMs: 50, stdout: "ok", stderr: "warning" });
const r = checkCommandExpect(o, {
exitCode: [0],
maxDurationMs: 100,
stdout: [{ contains: "ok" }],
stderr: [{ empty: true }],
});
expect(r.matched).toBe(false);
expect(r.failure!.phase).toBe("stderr");
});
test("stdout 操作符组合", () => {
const o = obs({ stdout: "count: 42" });
expect(
checkCommandExpect(o, {
stdout: [{ contains: "count" }, { match: "\\d+" }],
}).matched,
).toBe(true);
});
});