1
0

feat: 新增 ICMP/Ping checker,支持跨平台主机存活检测与延迟监控

实现 type: ping checker,通过 Bun.spawn 调用系统 ping 命令,自行实现跨平台
输出解析器(Linux/macOS/Windows 含中文 locale),支持 alive、丢包率、延迟、
耗时等 expect 断言,复用现有 checker 架构零外部依赖。

包含完整的类型定义、TypeBox schema、语义校验、命令构建、解析、断言、执行、
注册、配置加载测试,以及 probe-config.schema.json 更新和文档更新。

审查修复:提取 buildPingCommand 为独立纯函数并补充跨平台单测,补充
maxDurationMs/maxAvgLatencyMs 类型非法和空字符串 host 边界测试用例。

变更已归档,delta specs 已同步至 main specs。
This commit is contained in:
2026-05-18 10:45:17 +08:00
parent c51bc5a0d8
commit 550c427814
30 changed files with 1132 additions and 330 deletions

View File

@@ -5,6 +5,7 @@ import { join } from "node:path";
import type { ResolvedCommandTarget } from "../../../src/server/checker/runner/cmd/types";
import type { ResolvedHttpTarget } from "../../../src/server/checker/runner/http/types";
import type { ResolvedPingTarget } from "../../../src/server/checker/runner/icmp/types";
import type { ResolvedTcpTarget } from "../../../src/server/checker/runner/tcp/types";
import { loadConfig, parseDuration } from "../../../src/server/checker/config-loader";
@@ -1974,4 +1975,99 @@ targets:
expect(t.expect?.connected).toBe(false);
expect(t.expect?.maxDurationMs).toBe(5000);
});
test("解析最简 ping 配置", async () => {
const configPath = join(tempDir, "minimal-ping.yaml");
await writeFile(
configPath,
`targets:
- id: "gateway"
type: ping
ping:
host: "10.0.0.1"
`,
);
const config = await loadConfig(configPath);
expect(config.targets).toHaveLength(1);
const t = config.targets[0]! as ResolvedPingTarget;
expect(t.type).toBe("ping");
expect(t.ping).toEqual({ count: 3, host: "10.0.0.1", packetSize: 56 });
expect(t.group).toBe("default");
expect(t.intervalMs).toBe(30000);
expect(t.timeoutMs).toBe(10000);
});
test("解析 ping expect 配置", async () => {
const configPath = join(tempDir, "ping-expect.yaml");
await writeFile(
configPath,
`targets:
- id: "gateway"
type: ping
ping:
host: "10.0.0.1"
count: 5
packetSize: 1472
expect:
alive: true
maxPacketLoss: 10
maxAvgLatencyMs: 200
maxMaxLatencyMs: 500
maxDurationMs: 5000
`,
);
const config = await loadConfig(configPath);
const t = config.targets[0]! as ResolvedPingTarget;
expect(t.ping).toEqual({ count: 5, host: "10.0.0.1", packetSize: 1472 });
expect(t.expect).toEqual({
alive: true,
maxAvgLatencyMs: 200,
maxDurationMs: 5000,
maxMaxLatencyMs: 500,
maxPacketLoss: 10,
});
});
test("ping 缺少 host 抛出错误", async () => {
await expectConfigError(
"ping-no-host.yaml",
`targets:
- id: "gateway"
type: ping
ping: {}
`,
"ping.host",
);
});
test("ping count 非法抛出错误", async () => {
await expectConfigError(
"ping-bad-count.yaml",
`targets:
- id: "gateway"
type: ping
ping:
host: "10.0.0.1"
count: 0
`,
"ping.count",
);
});
test("ping expect 未知字段抛出错误", async () => {
await expectConfigError(
"ping-unknown-expect.yaml",
`targets:
- id: "gateway"
type: ping
ping:
host: "10.0.0.1"
expect:
status: [200]
`,
"expect.status 是未知字段",
);
});
});