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:
@@ -26,18 +26,27 @@ describe("API 路由", () => {
|
||||
store = new ProbeStore(join(tempDir, "test.db"));
|
||||
store.syncTargets([
|
||||
{
|
||||
type: "http",
|
||||
name: "test-a",
|
||||
url: "http://a.com",
|
||||
method: "GET",
|
||||
headers: {},
|
||||
http: {
|
||||
url: "http://a.com",
|
||||
method: "GET",
|
||||
headers: {},
|
||||
maxBodyBytes: 104857600,
|
||||
},
|
||||
intervalMs: 30000,
|
||||
timeoutMs: 10000,
|
||||
},
|
||||
{
|
||||
type: "command",
|
||||
name: "test-b",
|
||||
url: "http://b.com",
|
||||
method: "POST",
|
||||
headers: {},
|
||||
command: {
|
||||
exec: "echo",
|
||||
args: ["hello"],
|
||||
cwd: "/tmp",
|
||||
env: {},
|
||||
maxOutputBytes: 104857600,
|
||||
},
|
||||
intervalMs: 60000,
|
||||
timeoutMs: 5000,
|
||||
},
|
||||
@@ -48,19 +57,26 @@ describe("API 路由", () => {
|
||||
targetId: targets[0]!.id,
|
||||
timestamp: "2025-01-01T00:00:00.000Z",
|
||||
success: true,
|
||||
statusCode: 200,
|
||||
latencyMs: 150,
|
||||
error: null,
|
||||
matched: true,
|
||||
durationMs: 150,
|
||||
statusDetail: "200 OK",
|
||||
failure: null,
|
||||
});
|
||||
store.insertCheckResult({
|
||||
targetId: targets[0]!.id,
|
||||
timestamp: "2025-01-01T00:00:30.000Z",
|
||||
success: false,
|
||||
statusCode: null,
|
||||
latencyMs: null,
|
||||
error: "timeout",
|
||||
matched: false,
|
||||
durationMs: null,
|
||||
statusDetail: null,
|
||||
failure: {
|
||||
kind: "error",
|
||||
phase: "status",
|
||||
path: "$.status",
|
||||
expected: 200,
|
||||
actual: 500,
|
||||
message: "状态码不匹配",
|
||||
},
|
||||
});
|
||||
|
||||
fetchHandler = createFetchHandler({ mode: "test", staticAssets, store });
|
||||
@@ -88,6 +104,7 @@ describe("API 路由", () => {
|
||||
expect(body.up).toBeGreaterThanOrEqual(0);
|
||||
expect(body.down).toBeGreaterThanOrEqual(0);
|
||||
expect(body.up + body.down).toBe(2);
|
||||
expect(body.avgDurationMs).toBeDefined();
|
||||
});
|
||||
|
||||
test("/api/targets 返回目标列表", async () => {
|
||||
@@ -96,12 +113,23 @@ describe("API 路由", () => {
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(body).toHaveLength(2);
|
||||
expect(body[0]!.name).toBe("test-a");
|
||||
expect(body[0]!.latestCheck).not.toBeNull();
|
||||
expect(body[0]!.latestCheck!.success).toBe(false);
|
||||
expect(body[0]!.sparkline).toBeDefined();
|
||||
expect(Array.isArray(body[0]!.sparkline)).toBe(true);
|
||||
expect(body[1]!.latestCheck).toBeNull();
|
||||
|
||||
const tA = body.find((t) => t.name === "test-a")!;
|
||||
expect(tA.type).toBe("http");
|
||||
expect(tA.target).toBe("http://a.com");
|
||||
expect(tA.latestCheck).not.toBeNull();
|
||||
expect(tA.latestCheck!.success).toBe(false);
|
||||
expect(tA.latestCheck!.matched).toBe(false);
|
||||
expect(tA.latestCheck!.failure).not.toBeNull();
|
||||
expect(tA.sparkline).toBeDefined();
|
||||
expect(Array.isArray(tA.sparkline)).toBe(true);
|
||||
expect(tA.stats.avgDurationMs).toBeDefined();
|
||||
expect(tA.stats.p99DurationMs).toBeDefined();
|
||||
|
||||
const tB = body.find((t) => t.name === "test-b")!;
|
||||
expect(tB.type).toBe("command");
|
||||
expect(tB.target).toBe("exec echo hello");
|
||||
expect(tB.latestCheck).toBeNull();
|
||||
});
|
||||
|
||||
test("/api/targets/:id/history 返回历史记录", async () => {
|
||||
@@ -111,6 +139,8 @@ describe("API 路由", () => {
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(body).toHaveLength(2);
|
||||
expect(body[0].failure).not.toBeNull();
|
||||
expect(body[0].failure.kind).toBe("error");
|
||||
});
|
||||
|
||||
test("/api/targets/:id/history 支持 limit 参数", async () => {
|
||||
|
||||
Reference in New Issue
Block a user