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:
168
tests/server/checker/expect/command.test.ts
Normal file
168
tests/server/checker/expect/command.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user