feat: 新增 TCP checker,支持端口可达性探测与 banner 读取
- 新增 src/server/checker/runner/tcp/ 自包含目录(types/schema/validate/execute/expect) - 注册 TcpChecker 到 checkerRegistry,schema/engine/store/config-loader 自动委托 - 支持 expect.connected 正反向语义(默认期待可达,可配置期待不可达) - 支持 readBanner opt-in banner 读取,受 bannerReadTimeout + maxBannerBytes 双重限制 - 复用电有 expect/operator/duration/failure 基础设施 - 新增 3 个测试文件 51 条用例(execute/validate/expect),全量 634 测试通过 - 更新 README/DEVELOPMENT/probes.example.yaml,新增 tcp-checker capability spec
This commit is contained in:
@@ -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 { ResolvedTcpTarget } from "../../../src/server/checker/runner/tcp/types";
|
||||
|
||||
import { loadConfig, parseDuration } from "../../../src/server/checker/config-loader";
|
||||
import { checkerRegistry } from "../../../src/server/checker/runner";
|
||||
@@ -1778,4 +1779,199 @@ targets:
|
||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
||||
await expect(loadConfig(configPath)).rejects.toThrow("name");
|
||||
});
|
||||
|
||||
test("解析最简 tcp 配置", async () => {
|
||||
const configPath = join(tempDir, "minimal-tcp.yaml");
|
||||
await writeFile(
|
||||
configPath,
|
||||
`targets:
|
||||
- id: "redis-port"
|
||||
type: tcp
|
||||
tcp:
|
||||
host: "127.0.0.1"
|
||||
port: 6379
|
||||
`,
|
||||
);
|
||||
|
||||
const config = await loadConfig(configPath);
|
||||
expect(config.targets).toHaveLength(1);
|
||||
const t = config.targets[0]! as ResolvedTcpTarget;
|
||||
expect(t.type).toBe("tcp");
|
||||
expect(t.id).toBe("redis-port");
|
||||
expect(t.name).toBeNull();
|
||||
expect(t.tcp.host).toBe("127.0.0.1");
|
||||
expect(t.tcp.port).toBe(6379);
|
||||
expect(t.tcp.readBanner).toBe(false);
|
||||
expect(t.tcp.bannerReadTimeout).toBe(2000);
|
||||
expect(t.tcp.maxBannerBytes).toBe(4096);
|
||||
expect(t.group).toBe("default");
|
||||
expect(t.intervalMs).toBe(30000);
|
||||
expect(t.timeoutMs).toBe(10000);
|
||||
});
|
||||
|
||||
test("tcp 缺少 host 抛出错误", async () => {
|
||||
await expectConfigError(
|
||||
"tcp-no-host.yaml",
|
||||
`targets:
|
||||
- id: "t"
|
||||
type: tcp
|
||||
tcp:
|
||||
port: 80
|
||||
`,
|
||||
"tcp.host",
|
||||
);
|
||||
});
|
||||
|
||||
test("tcp 缺少 port 抛出错误", async () => {
|
||||
await expectConfigError(
|
||||
"tcp-no-port.yaml",
|
||||
`targets:
|
||||
- id: "t"
|
||||
type: tcp
|
||||
tcp:
|
||||
host: "127.0.0.1"
|
||||
`,
|
||||
"tcp.port",
|
||||
);
|
||||
});
|
||||
|
||||
test("tcp 非法端口范围抛出错误", async () => {
|
||||
await expectConfigError(
|
||||
"tcp-bad-port.yaml",
|
||||
`targets:
|
||||
- id: "t"
|
||||
type: tcp
|
||||
tcp:
|
||||
host: "127.0.0.1"
|
||||
port: 99999
|
||||
`,
|
||||
"tcp.port",
|
||||
);
|
||||
});
|
||||
|
||||
test("tcp 未知分组字段抛出错误", async () => {
|
||||
await expectConfigError(
|
||||
"tcp-unknown-field.yaml",
|
||||
`targets:
|
||||
- id: "t"
|
||||
type: tcp
|
||||
tcp:
|
||||
host: "127.0.0.1"
|
||||
port: 80
|
||||
tls: true
|
||||
`,
|
||||
"是未知字段",
|
||||
);
|
||||
});
|
||||
|
||||
test("tcp readBanner 开启并配置 expect.banner", async () => {
|
||||
const configPath = join(tempDir, "tcp-banner.yaml");
|
||||
await writeFile(
|
||||
configPath,
|
||||
`targets:
|
||||
- id: "smtp-check"
|
||||
type: tcp
|
||||
tcp:
|
||||
host: "127.0.0.1"
|
||||
port: 25
|
||||
readBanner: true
|
||||
expect:
|
||||
banner:
|
||||
contains: "ESMTP"
|
||||
`,
|
||||
);
|
||||
|
||||
const config = await loadConfig(configPath);
|
||||
const t = config.targets[0]! as ResolvedTcpTarget;
|
||||
expect(t.tcp.readBanner).toBe(true);
|
||||
expect(t.expect?.banner).toEqual({ contains: "ESMTP" });
|
||||
});
|
||||
|
||||
test("tcp expect.banner 未开启 readBanner 抛出错误", async () => {
|
||||
await expectConfigError(
|
||||
"tcp-banner-no-read.yaml",
|
||||
`targets:
|
||||
- id: "t"
|
||||
type: tcp
|
||||
tcp:
|
||||
host: "127.0.0.1"
|
||||
port: 25
|
||||
expect:
|
||||
banner:
|
||||
contains: "ESMTP"
|
||||
`,
|
||||
"banner 断言需要启用 tcp.readBanner",
|
||||
);
|
||||
});
|
||||
|
||||
test("tcp defaults 覆盖 banner 参数", async () => {
|
||||
const configPath = join(tempDir, "tcp-defaults.yaml");
|
||||
await writeFile(
|
||||
configPath,
|
||||
`defaults:
|
||||
tcp:
|
||||
bannerReadTimeout: 1000
|
||||
maxBannerBytes: "8KB"
|
||||
targets:
|
||||
- id: "t1"
|
||||
type: tcp
|
||||
tcp:
|
||||
host: "127.0.0.1"
|
||||
port: 80
|
||||
- id: "t2"
|
||||
type: tcp
|
||||
tcp:
|
||||
host: "127.0.0.1"
|
||||
port: 81
|
||||
bannerReadTimeout: 3000
|
||||
`,
|
||||
);
|
||||
|
||||
const config = await loadConfig(configPath);
|
||||
const t1 = config.targets[0]! as ResolvedTcpTarget;
|
||||
expect(t1.tcp.bannerReadTimeout).toBe(1000);
|
||||
expect(t1.tcp.maxBannerBytes).toBe(8192);
|
||||
|
||||
const t2 = config.targets[1]! as ResolvedTcpTarget;
|
||||
expect(t2.tcp.bannerReadTimeout).toBe(3000);
|
||||
expect(t2.tcp.maxBannerBytes).toBe(8192);
|
||||
});
|
||||
|
||||
test("tcp expect 未知字段抛出错误", async () => {
|
||||
await expectConfigError(
|
||||
"tcp-unknown-expect.yaml",
|
||||
`targets:
|
||||
- id: "t"
|
||||
type: tcp
|
||||
tcp:
|
||||
host: "127.0.0.1"
|
||||
port: 80
|
||||
expect:
|
||||
status: [200]
|
||||
`,
|
||||
"是未知字段",
|
||||
);
|
||||
});
|
||||
|
||||
test("tcp expect connected 和 maxDurationMs", async () => {
|
||||
const configPath = join(tempDir, "tcp-expect-connected.yaml");
|
||||
await writeFile(
|
||||
configPath,
|
||||
`targets:
|
||||
- id: "t"
|
||||
type: tcp
|
||||
tcp:
|
||||
host: "127.0.0.1"
|
||||
port: 80
|
||||
expect:
|
||||
connected: false
|
||||
maxDurationMs: 5000
|
||||
`,
|
||||
);
|
||||
|
||||
const config = await loadConfig(configPath);
|
||||
const t = config.targets[0]! as ResolvedTcpTarget;
|
||||
expect(t.expect?.connected).toBe(false);
|
||||
expect(t.expect?.maxDurationMs).toBe(5000);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user