1
0
Files
DiAL/tests/server/checker/engine.test.ts
lanyuanxiaoyao bb6b2bc20b refactor: checker 模块内聚化 — 每个 checker 自包含于独立目录
将 checker 架构重构为完全内聚模式:每个 checker 目录包含自身的
types、schema、validate、execute、expect 和 index,新增 checker
只需创建一个目录并在 runner/index.ts 添加一行注册。

主要变更:
- runner/shared/ 拆分:断言基础设施迁入 checker/expect/,
  body.ts 迁入 http/,text.ts 迁入 command/
- config-contract/ 重命名为 schema/,schema.ts → builder.ts
- size.ts + parseDuration 合并为 utils.ts
- 顶层 types.ts 改为 base interface + index signature,
  checker 专属类型下沉到各自 types.ts
- runner/index.ts 改为显式数组注册模式
- 更新 DEVELOPMENT.md 项目结构和开发新 Checker 指南
2026-05-13 14:38:21 +08:00

224 lines
7.7 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import type { ResolvedCommandTarget } from "../../../src/server/checker/runner/command/types";
import type { ResolvedHttpTarget } from "../../../src/server/checker/runner/http/types";
import type { ProbeStore } from "../../../src/server/checker/store";
import type { ResolvedTargetBase } from "../../../src/server/checker/types";
import { ProbeEngine } from "../../../src/server/checker/engine";
import { checkerRegistry } from "../../../src/server/checker/runner";
import { CommandChecker } from "../../../src/server/checker/runner/command/execute";
import { HttpChecker } from "../../../src/server/checker/runner/http/execute";
function createMockStore(targetNames: string[]) {
let nextId = 1;
const targets = targetNames.map((name) => ({ id: nextId++, name }));
const results: Array<Record<string, unknown>> = [];
return {
_results: results,
getTargets() {
return targets.map(({ id, name }) => ({
config: "",
expect: null,
grp: "default",
id,
interval_ms: 60000,
name,
target: "",
timeout_ms: 5000,
type: "command" as const,
}));
},
insertCheckResult(result: Record<string, unknown>) {
results.push(result);
},
};
}
function ensureRegistered() {
if (!checkerRegistry.supportedTypes.includes("http")) {
checkerRegistry.register(new HttpChecker());
checkerRegistry.register(new CommandChecker());
}
}
function makeCommandTarget(name: string, overrides?: Partial<ResolvedCommandTarget>): ResolvedCommandTarget {
return {
command: {
args: ["hello"],
cwd: "/tmp",
env: {},
exec: "echo",
maxOutputBytes: 1024 * 1024,
},
group: "default",
intervalMs: 60000,
name,
timeoutMs: 5000,
type: "command",
...overrides,
};
}
describe("ProbeEngine", () => {
test("start/stop 不抛错", () => {
ensureRegistered();
const mockStore = createMockStore(["test"]) as unknown as ProbeStore;
const targets: ResolvedTargetBase[] = [makeCommandTarget("test")];
const engine = new ProbeEngine(mockStore, targets);
engine.start();
engine.stop();
expect(true).toBe(true);
});
test("单次 probeGroup 执行 command 检查", async () => {
const target = makeCommandTarget("cmd-echo");
const mockStore = createMockStore(["cmd-echo"]) as unknown as ProbeStore;
const engine = new ProbeEngine(mockStore, [target]);
const probeGroup = (
engine as unknown as { probeGroup: (t: ResolvedTargetBase[]) => Promise<void> }
).probeGroup.bind(engine);
await probeGroup([target]);
const results = (mockStore as unknown as { _results: Array<Record<string, unknown>> })._results;
expect(results.length).toBe(1);
expect(results[0]!["matched"]).toBe(true);
expect(results[0]!["statusDetail"]).toBe("exitCode=0");
});
test("多个目标并发执行", async () => {
const targetA = makeCommandTarget("echo-a", {
command: { args: ["a"], cwd: "/tmp", env: {}, exec: "echo", maxOutputBytes: 1024 * 1024 },
});
const targetB = makeCommandTarget("echo-b", {
command: { args: ["b"], cwd: "/tmp", env: {}, exec: "echo", maxOutputBytes: 1024 * 1024 },
});
const mockStore = createMockStore(["echo-a", "echo-b"]) as unknown as ProbeStore;
const engine = new ProbeEngine(mockStore, [targetA, targetB]);
const probeGroup = (
engine as unknown as { probeGroup: (t: ResolvedTargetBase[]) => Promise<void> }
).probeGroup.bind(engine);
await probeGroup([targetA, targetB]);
const results = (mockStore as unknown as { _results: Array<Record<string, unknown>> })._results;
expect(results.length).toBe(2);
});
test("失败目标不阻塞其他目标", async () => {
const badTarget = makeCommandTarget("bad-cmd", {
command: { args: [], cwd: "/tmp", env: {}, exec: "false", maxOutputBytes: 1024 * 1024 },
});
const goodTarget = makeCommandTarget("good-cmd");
const mockStore = createMockStore(["bad-cmd", "good-cmd"]) as unknown as ProbeStore;
const engine = new ProbeEngine(mockStore, [badTarget, goodTarget]);
const probeGroup = (
engine as unknown as { probeGroup: (t: ResolvedTargetBase[]) => Promise<void> }
).probeGroup.bind(engine);
await probeGroup([badTarget, goodTarget]);
const results = (mockStore as unknown as { _results: Array<Record<string, unknown>> })._results;
expect(results.length).toBe(2);
const badResult = results.find((r) => r["matched"] === false);
const goodResult = results.find((r) => r["matched"] === true);
expect(badResult).toBeDefined();
expect(goodResult).toBeDefined();
});
test("并发限制 maxConcurrentChecks", async () => {
const targets = Array.from({ length: 5 }, (_, i) =>
makeCommandTarget(`cmd-${i}`, {
command: { args: [String(i)], cwd: "/tmp", env: {}, exec: "echo", maxOutputBytes: 1024 * 1024 },
}),
);
const mockStore = createMockStore(targets.map((t) => t.name)) as unknown as ProbeStore;
const engine = new ProbeEngine(mockStore, targets, 2);
const probeGroup = (
engine as unknown as { probeGroup: (t: ResolvedTargetBase[]) => Promise<void> }
).probeGroup.bind(engine);
await probeGroup(targets);
const results = (mockStore as unknown as { _results: Array<Record<string, unknown>> })._results;
expect(results.length).toBe(5);
for (const r of results) {
expect(r["matched"]).toBe(true);
}
});
test("groupByInterval 按间隔分组", () => {
const targetA = makeCommandTarget("a", { intervalMs: 30000 });
const targetB = makeCommandTarget("b", { intervalMs: 30000 });
const targetC = makeCommandTarget("c", { intervalMs: 60000 });
const mockStore = createMockStore(["a", "b", "c"]) as unknown as ProbeStore;
const engine = new ProbeEngine(mockStore, [targetA, targetB, targetC]);
engine.start();
engine.stop();
expect(true).toBe(true);
});
test("未注册的 targetName 不写入结果", async () => {
const target = makeCommandTarget("unknown-target");
const mockStore = createMockStore(["other-name"]) as unknown as ProbeStore;
const engine = new ProbeEngine(mockStore, [target]);
const probeGroup = (
engine as unknown as { probeGroup: (t: ResolvedTargetBase[]) => Promise<void> }
).probeGroup.bind(engine);
await probeGroup([target]);
const results = (mockStore as unknown as { _results: Array<Record<string, unknown>> })._results;
expect(results.length).toBe(0);
});
test("HTTP 目标运行", async () => {
const httpServer = Bun.serve({
fetch() {
return new Response("ok");
},
port: 0,
});
try {
const httpTarget: ResolvedHttpTarget = {
group: "default",
http: {
headers: {},
ignoreSSL: false,
maxBodyBytes: 1024 * 1024,
maxRedirects: 0,
method: "GET",
url: `http://localhost:${httpServer.port}/`,
},
intervalMs: 60000,
name: "http-test",
timeoutMs: 5000,
type: "http",
};
const mockStore = createMockStore(["http-test"]) as unknown as ProbeStore;
const engine = new ProbeEngine(mockStore, [httpTarget]);
const probeGroup = (
engine as unknown as { probeGroup: (t: ResolvedTargetBase[]) => Promise<void> }
).probeGroup.bind(engine);
await probeGroup([httpTarget]);
const results = (mockStore as unknown as { _results: Array<Record<string, unknown>> })._results;
expect(results.length).toBe(1);
expect(results[0]!["matched"]).toBe(true);
expect(results[0]!["statusDetail"]).toBe("HTTP 200");
} finally {
void httpServer.stop();
}
});
});