- 引入共享 ValueMatcher(equals/contains/regex/exists/empty/gt/gte/lt/lte) - 引入共享 ContentRules 数组(direct/json/css/xpath 提取器) - 引入共享 KeyValueExpect(动态键值断言,字面量等价 equals) - maxDurationMs → durationMs: ValueMatcher(所有 checker) - match → regex(固定无 flags) - Ping max* → packetLossPercent/avgLatencyMs/maxLatencyMs(ValueMatcher) - LLM finishReason/rawFinishReason → ValueMatcher - DB 新增 result: ContentRules - TCP banner → ContentRules 数组 - 删除旧模块:operator.ts、validate-operator.ts、duration.ts、body.ts、text.ts、output.ts - 更新全部 checker schema/validate/expect/execute - 更新 probe-config.schema.json、probes.example.yaml - 更新 README.md、DEVELOPMENT.md(含 expect 字段选择规范) - 同步 10 个 delta specs 到主 specs,归档 change
263 lines
7.8 KiB
TypeScript
263 lines
7.8 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
|
|
import type { CheckerValidationInput } from "../../../../../src/server/checker/runner/types";
|
|
|
|
import { validateUdpConfig } from "../../../../../src/server/checker/runner/udp/validate";
|
|
|
|
describe("validateUdpConfig", () => {
|
|
const makeInput = (overrides: {
|
|
defaults?: Record<string, unknown>;
|
|
targets?: Array<Record<string, unknown>>;
|
|
}): CheckerValidationInput => ({
|
|
defaults: overrides.defaults ?? {},
|
|
targets: (overrides.targets ?? []) as CheckerValidationInput["targets"],
|
|
});
|
|
|
|
it("accepts minimal valid UDP target", () => {
|
|
const issues = validateUdpConfig(
|
|
makeInput({
|
|
targets: [{ id: "test", type: "udp", udp: { host: "127.0.0.1", port: 53 } }],
|
|
}),
|
|
);
|
|
expect(issues).toHaveLength(0);
|
|
});
|
|
|
|
it("reports required when udp.host is missing", () => {
|
|
const issues = validateUdpConfig(
|
|
makeInput({
|
|
targets: [{ id: "test", type: "udp", udp: { port: 53 } }],
|
|
}),
|
|
);
|
|
expect(issues).toHaveLength(1);
|
|
expect(issues[0]!.code).toBe("required");
|
|
expect(issues[0]!.path).toContain("host");
|
|
});
|
|
|
|
it("reports required when udp.port is missing", () => {
|
|
const issues = validateUdpConfig(
|
|
makeInput({
|
|
targets: [{ id: "test", type: "udp", udp: { host: "127.0.0.1" } }],
|
|
}),
|
|
);
|
|
expect(issues).toHaveLength(1);
|
|
expect(issues[0]!.code).toBe("required");
|
|
expect(issues[0]!.path).toContain("port");
|
|
});
|
|
|
|
it("rejects invalid port values", () => {
|
|
for (const port of [0, -1, 65536, 1.5]) {
|
|
const issues = validateUdpConfig(
|
|
makeInput({
|
|
targets: [{ id: "test", type: "udp", udp: { host: "127.0.0.1", port } }],
|
|
}),
|
|
);
|
|
expect(issues.length).toBeGreaterThanOrEqual(1);
|
|
expect(issues.some((i) => i.code === "invalid-value" && i.path.includes("port"))).toBe(true);
|
|
}
|
|
});
|
|
|
|
it("reports required when udp section is missing", () => {
|
|
const issues = validateUdpConfig(
|
|
makeInput({
|
|
targets: [{ id: "test", type: "udp" }],
|
|
}),
|
|
);
|
|
expect(issues).toHaveLength(1);
|
|
expect(issues[0]!.code).toBe("required");
|
|
expect(issues[0]!.path).toContain("udp");
|
|
});
|
|
|
|
it("accepts valid defaults.udp with encoding, responseEncoding, maxResponseBytes", () => {
|
|
const issues = validateUdpConfig(
|
|
makeInput({
|
|
defaults: {
|
|
udp: { encoding: "hex", maxResponseBytes: 1024, responseEncoding: "text" },
|
|
},
|
|
targets: [{ id: "test", type: "udp", udp: { host: "127.0.0.1", port: 53 } }],
|
|
}),
|
|
);
|
|
expect(issues).toHaveLength(0);
|
|
});
|
|
|
|
it("reports unknown-field in defaults.udp", () => {
|
|
const issues = validateUdpConfig(
|
|
makeInput({
|
|
defaults: { udp: { unknownField: true } },
|
|
targets: [{ id: "test", type: "udp", udp: { host: "127.0.0.1", port: 53 } }],
|
|
}),
|
|
);
|
|
expect(issues).toHaveLength(1);
|
|
expect(issues[0]!.code).toBe("unknown-field");
|
|
});
|
|
|
|
it("reports invalid-value for udp.encoding with bad value", () => {
|
|
const issues = validateUdpConfig(
|
|
makeInput({
|
|
targets: [
|
|
{
|
|
id: "test",
|
|
type: "udp",
|
|
udp: { encoding: "json", host: "127.0.0.1", port: 53 },
|
|
},
|
|
],
|
|
}),
|
|
);
|
|
expect(issues.some((i) => i.code === "invalid-value" && i.path.includes("encoding"))).toBe(true);
|
|
});
|
|
|
|
it("reports invalid-value for udp.responseEncoding with bad value", () => {
|
|
const issues = validateUdpConfig(
|
|
makeInput({
|
|
targets: [
|
|
{
|
|
id: "test",
|
|
type: "udp",
|
|
udp: { host: "127.0.0.1", port: 53, responseEncoding: "binary" },
|
|
},
|
|
],
|
|
}),
|
|
);
|
|
expect(issues.some((i) => i.code === "invalid-value" && i.path.includes("responseEncoding"))).toBe(true);
|
|
});
|
|
|
|
it("reports invalid-value for hex payload that is not valid hex", () => {
|
|
const issues = validateUdpConfig(
|
|
makeInput({
|
|
targets: [
|
|
{
|
|
id: "test",
|
|
type: "udp",
|
|
udp: { encoding: "hex", host: "127.0.0.1", payload: "ZZZZ", port: 53 },
|
|
},
|
|
],
|
|
}),
|
|
);
|
|
expect(issues.some((i) => i.code === "invalid-value" && i.path.includes("payload"))).toBe(true);
|
|
});
|
|
|
|
it("reports invalid-value for base64 payload that is not valid base64", () => {
|
|
const issues = validateUdpConfig(
|
|
makeInput({
|
|
targets: [
|
|
{
|
|
id: "test",
|
|
type: "udp",
|
|
udp: { encoding: "base64", host: "127.0.0.1", payload: "!!!notbase64", port: 53 },
|
|
},
|
|
],
|
|
}),
|
|
);
|
|
expect(issues.some((i) => i.code === "invalid-value" && i.path.includes("payload"))).toBe(true);
|
|
});
|
|
|
|
it("reports unknown-field for unknown key in udp group", () => {
|
|
const issues = validateUdpConfig(
|
|
makeInput({
|
|
targets: [
|
|
{
|
|
id: "test",
|
|
type: "udp",
|
|
udp: { bogus: true, host: "127.0.0.1", port: 53 },
|
|
},
|
|
],
|
|
}),
|
|
);
|
|
expect(issues.some((i) => i.code === "unknown-field" && i.path.includes("bogus"))).toBe(true);
|
|
});
|
|
|
|
it("reports unknown-field for unknown key in expect", () => {
|
|
const issues = validateUdpConfig(
|
|
makeInput({
|
|
targets: [
|
|
{
|
|
expect: { unknownExpect: true },
|
|
id: "test",
|
|
type: "udp",
|
|
udp: { host: "127.0.0.1", port: 53 },
|
|
},
|
|
],
|
|
}),
|
|
);
|
|
expect(issues.some((i) => i.code === "unknown-field" && i.path.includes("unknownExpect"))).toBe(true);
|
|
});
|
|
|
|
it("reports conflict when expect.responded=false with expect.response", () => {
|
|
const issues = validateUdpConfig(
|
|
makeInput({
|
|
targets: [
|
|
{
|
|
expect: { responded: false, response: [{ type: "contains", value: "ok" }] },
|
|
id: "test",
|
|
type: "udp",
|
|
udp: { host: "127.0.0.1", port: 53 },
|
|
},
|
|
],
|
|
}),
|
|
);
|
|
expect(issues.some((i) => i.path.includes("responded") && i.message.includes("响应内容"))).toBe(true);
|
|
});
|
|
|
|
it("reports conflict when expect.responded=false with expect.sourceHost", () => {
|
|
const issues = validateUdpConfig(
|
|
makeInput({
|
|
targets: [
|
|
{
|
|
expect: { responded: false, sourceHost: { eq: "1.2.3.4" } },
|
|
id: "test",
|
|
type: "udp",
|
|
udp: { host: "127.0.0.1", port: 53 },
|
|
},
|
|
],
|
|
}),
|
|
);
|
|
expect(issues.some((i) => i.path.includes("responded") && i.message.includes("响应来源"))).toBe(true);
|
|
});
|
|
|
|
it("reports invalid-type for non-matcher expect.durationMs", () => {
|
|
const issues = validateUdpConfig(
|
|
makeInput({
|
|
targets: [
|
|
{
|
|
expect: { durationMs: -100 },
|
|
id: "test",
|
|
type: "udp",
|
|
udp: { host: "127.0.0.1", port: 53 },
|
|
},
|
|
],
|
|
}),
|
|
);
|
|
expect(issues.some((i) => i.code === "invalid-type" && i.path.includes("durationMs"))).toBe(true);
|
|
});
|
|
|
|
it("reports invalid-type for non-boolean expect.responded", () => {
|
|
const issues = validateUdpConfig(
|
|
makeInput({
|
|
targets: [
|
|
{
|
|
expect: { responded: "yes" },
|
|
id: "test",
|
|
type: "udp",
|
|
udp: { host: "127.0.0.1", port: 53 },
|
|
},
|
|
],
|
|
}),
|
|
);
|
|
expect(issues.some((i) => i.code === "invalid-type" && i.path.includes("responded"))).toBe(true);
|
|
});
|
|
|
|
it("reports invalid-value for negative maxResponseBytes", () => {
|
|
const issues = validateUdpConfig(
|
|
makeInput({
|
|
targets: [
|
|
{
|
|
id: "test",
|
|
type: "udp",
|
|
udp: { host: "127.0.0.1", maxResponseBytes: -1, port: 53 },
|
|
},
|
|
],
|
|
}),
|
|
);
|
|
expect(issues.some((i) => i.code === "invalid-value" && i.path.includes("maxResponseBytes"))).toBe(true);
|
|
});
|
|
});
|