feat: WS checker,支持可达性检测和单次请求-响应交互验证
This commit is contained in:
@@ -72,8 +72,8 @@ describe("CheckerRegistry", () => {
|
||||
const second = createDefaultCheckerRegistry();
|
||||
first.register(createChecker("custom"));
|
||||
|
||||
expect(first.supportedTypes).toEqual(["http", "cmd", "db", "tcp", "icmp", "udp", "llm", "dns", "custom"]);
|
||||
expect(second.supportedTypes).toEqual(["http", "cmd", "db", "tcp", "icmp", "udp", "llm", "dns"]);
|
||||
expect(first.supportedTypes).toEqual(["http", "cmd", "db", "tcp", "icmp", "udp", "llm", "dns", "ws", "custom"]);
|
||||
expect(second.supportedTypes).toEqual(["http", "cmd", "db", "tcp", "icmp", "udp", "llm", "dns", "ws"]);
|
||||
expect(
|
||||
first.definitions.every((checker) => checker.schemas.authoring.config && checker.schemas.normalized.expect),
|
||||
).toBe(true);
|
||||
|
||||
155
tests/server/checker/runner/ws/config-loader.test.ts
Normal file
155
tests/server/checker/runner/ws/config-loader.test.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||
import { mkdir, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
import type { ResolvedWsTarget } from "../../../../../src/server/checker/runner/ws/types";
|
||||
|
||||
import { loadConfig } from "../../../../../src/server/checker/config-loader";
|
||||
|
||||
describe("loadConfig with ws checker", () => {
|
||||
let tempDir: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
tempDir = join(tmpdir(), `ws-cfg-test-${Date.now()}`);
|
||||
await mkdir(tempDir, { recursive: true });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await rm(tempDir, { force: true, recursive: true });
|
||||
});
|
||||
|
||||
test("解析最简 ws 配置", async () => {
|
||||
const configPath = join(tempDir, "minimal-ws.yaml");
|
||||
await writeFile(
|
||||
configPath,
|
||||
`targets:
|
||||
- id: "ws-test"
|
||||
type: ws
|
||||
ws:
|
||||
url: "ws://example.com/ws"
|
||||
`,
|
||||
);
|
||||
|
||||
const config = await loadConfig(configPath);
|
||||
expect(config.targets).toHaveLength(1);
|
||||
const t = config.targets[0]! as ResolvedWsTarget;
|
||||
expect(t.type).toBe("ws");
|
||||
expect(t.id).toBe("ws-test");
|
||||
expect(t.ws.url).toBe("ws://example.com/ws");
|
||||
expect(t.ws.headers).toEqual({});
|
||||
expect(t.ws.ignoreSSL).toBe(false);
|
||||
expect(t.ws.maxMessageBytes).toBe(4096);
|
||||
expect(t.ws.receiveTimeout).toBe(5000);
|
||||
expect(t.ws.send).toBeUndefined();
|
||||
expect(t.ws.subprotocols).toEqual([]);
|
||||
expect(t.expect).toEqual({ connected: true });
|
||||
});
|
||||
|
||||
test("解析带 send 的 ws 配置", async () => {
|
||||
const configPath = join(tempDir, "ws-send.yaml");
|
||||
await writeFile(
|
||||
configPath,
|
||||
`targets:
|
||||
- id: "ws-echo"
|
||||
name: "WS Echo 检查"
|
||||
type: ws
|
||||
ws:
|
||||
url: "wss://api.example.com/ws"
|
||||
headers:
|
||||
Authorization: "Bearer token"
|
||||
subprotocols:
|
||||
- "json"
|
||||
ignoreSSL: true
|
||||
send: "ping"
|
||||
receiveTimeout: 3000
|
||||
maxMessageBytes: "8KB"
|
||||
expect:
|
||||
message:
|
||||
- contains: "pong"
|
||||
durationMs:
|
||||
lte: 5000
|
||||
`,
|
||||
);
|
||||
|
||||
const config = await loadConfig(configPath);
|
||||
expect(config.targets).toHaveLength(1);
|
||||
const t = config.targets[0]! as ResolvedWsTarget;
|
||||
expect(t.type).toBe("ws");
|
||||
expect(t.ws.url).toBe("wss://api.example.com/ws");
|
||||
expect(t.ws.headers).toEqual({ Authorization: "Bearer token" });
|
||||
expect(t.ws.ignoreSSL).toBe(true);
|
||||
expect(t.ws.maxMessageBytes).toBe(8192);
|
||||
expect(t.ws.receiveTimeout).toBe(3000);
|
||||
expect(t.ws.send).toBe("ping");
|
||||
expect(t.ws.subprotocols).toEqual(["json"]);
|
||||
expect(t.expect?.connected).toBe(true);
|
||||
expect(t.expect?.message).toBeDefined();
|
||||
expect(t.expect?.durationMs).toEqual({ lte: 5000 });
|
||||
});
|
||||
|
||||
test("ws 缺少 url 抛出错误", async () => {
|
||||
const configPath = join(tempDir, "ws-no-url.yaml");
|
||||
await writeFile(
|
||||
configPath,
|
||||
`targets:
|
||||
- id: "t"
|
||||
type: ws
|
||||
ws: {}
|
||||
`,
|
||||
);
|
||||
let error: unknown;
|
||||
try {
|
||||
await loadConfig(configPath);
|
||||
} catch (caught) {
|
||||
error = caught;
|
||||
}
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect((error as Error).message).toContain("ws.url");
|
||||
});
|
||||
|
||||
test("ws url 非 ws/wss 协议抛出错误", async () => {
|
||||
const configPath = join(tempDir, "ws-bad-url.yaml");
|
||||
await writeFile(
|
||||
configPath,
|
||||
`targets:
|
||||
- id: "t"
|
||||
type: ws
|
||||
ws:
|
||||
url: "http://example.com"
|
||||
`,
|
||||
);
|
||||
let error: unknown;
|
||||
try {
|
||||
await loadConfig(configPath);
|
||||
} catch (caught) {
|
||||
error = caught;
|
||||
}
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect((error as Error).message).toContain("ws:// 或 wss://");
|
||||
});
|
||||
|
||||
test("ws expect.message 未配置 send 抛出错误", async () => {
|
||||
const configPath = join(tempDir, "ws-no-send.yaml");
|
||||
await writeFile(
|
||||
configPath,
|
||||
`targets:
|
||||
- id: "t"
|
||||
type: ws
|
||||
ws:
|
||||
url: "ws://example.com"
|
||||
expect:
|
||||
message:
|
||||
- contains: "pong"
|
||||
`,
|
||||
);
|
||||
let error: unknown;
|
||||
try {
|
||||
await loadConfig(configPath);
|
||||
} catch (caught) {
|
||||
error = caught;
|
||||
}
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect((error as Error).message).toContain("send");
|
||||
});
|
||||
});
|
||||
201
tests/server/checker/runner/ws/execute.test.ts
Normal file
201
tests/server/checker/runner/ws/execute.test.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||
|
||||
import type { CheckerContext } from "../../../../../src/server/checker/runner/types";
|
||||
import type { ResolvedWsTarget } from "../../../../../src/server/checker/runner/ws/types";
|
||||
|
||||
import { WsChecker } from "../../../../../src/server/checker/runner/ws/execute";
|
||||
|
||||
function createEchoServer() {
|
||||
return Bun.serve({
|
||||
fetch(req, server) {
|
||||
const success = server.upgrade(req);
|
||||
if (!success) return new Response("Upgrade failed", { status: 500 });
|
||||
return undefined;
|
||||
},
|
||||
port: 0,
|
||||
websocket: {
|
||||
close() {
|
||||
/* ws close */
|
||||
},
|
||||
message(ws, message) {
|
||||
ws.send(message);
|
||||
},
|
||||
open() {
|
||||
/* ws open */
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function createNoReplyServer() {
|
||||
return Bun.serve({
|
||||
fetch(req, server) {
|
||||
const success = server.upgrade(req);
|
||||
if (!success) return new Response("Upgrade failed", { status: 500 });
|
||||
return undefined;
|
||||
},
|
||||
port: 0,
|
||||
websocket: {
|
||||
close() {
|
||||
/* ws close */
|
||||
},
|
||||
message() {
|
||||
/* no reply */
|
||||
},
|
||||
open() {
|
||||
/* ws open */
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function createRejectServer() {
|
||||
return Bun.serve({
|
||||
fetch() {
|
||||
return new Response("Forbidden", { status: 403 });
|
||||
},
|
||||
port: 0,
|
||||
});
|
||||
}
|
||||
|
||||
function makeContext(overrides?: Partial<CheckerContext>): CheckerContext {
|
||||
return {
|
||||
signal: AbortSignal.timeout(15000),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makeWsTarget(overrides?: Partial<ResolvedWsTarget>): ResolvedWsTarget {
|
||||
return {
|
||||
description: null,
|
||||
expect: { connected: true },
|
||||
group: "default",
|
||||
id: "test-ws",
|
||||
intervalMs: 30000,
|
||||
name: null,
|
||||
timeoutMs: 10000,
|
||||
type: "ws",
|
||||
ws: {
|
||||
headers: {},
|
||||
ignoreSSL: false,
|
||||
maxMessageBytes: 4096,
|
||||
receiveTimeout: 5000,
|
||||
subprotocols: [],
|
||||
url: "ws://127.0.0.1:19999/ws",
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
let echoServer: ReturnType<typeof createEchoServer>;
|
||||
let noReplyServer: ReturnType<typeof createNoReplyServer>;
|
||||
let rejectServer: ReturnType<typeof createRejectServer>;
|
||||
|
||||
beforeAll(() => {
|
||||
echoServer = createEchoServer();
|
||||
noReplyServer = createNoReplyServer();
|
||||
rejectServer = createRejectServer();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await echoServer.stop();
|
||||
await noReplyServer.stop();
|
||||
await rejectServer.stop();
|
||||
});
|
||||
|
||||
describe("WsChecker execute", () => {
|
||||
const checker = new WsChecker();
|
||||
|
||||
test("可达性检查 - 连接成功", async () => {
|
||||
const target = makeWsTarget({ ws: { ...makeWsTarget().ws, url: `ws://127.0.0.1:${echoServer.port}` } });
|
||||
const result = await checker.execute(target, makeContext());
|
||||
expect(result.matched).toBe(true);
|
||||
expect(result.failure).toBeNull();
|
||||
expect(result.observation!["connected"]).toBe(true);
|
||||
});
|
||||
|
||||
test("可达性检查 - 连接失败", async () => {
|
||||
const target = makeWsTarget({ ws: { ...makeWsTarget().ws, url: "ws://127.0.0.1:1" } });
|
||||
const result = await checker.execute(target, makeContext());
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.failure).not.toBeNull();
|
||||
expect(result.observation!["connected"]).toBe(false);
|
||||
});
|
||||
|
||||
test("可达性检查 - 连接失败但 expect.connected=false", async () => {
|
||||
const target = makeWsTarget({
|
||||
expect: { connected: false },
|
||||
ws: { ...makeWsTarget().ws, url: "ws://127.0.0.1:1" },
|
||||
});
|
||||
const result = await checker.execute(target, makeContext());
|
||||
expect(result.matched).toBe(true);
|
||||
expect(result.failure).toBeNull();
|
||||
});
|
||||
|
||||
test("交互模式 - 发送消息并收到响应", async () => {
|
||||
const target = makeWsTarget({
|
||||
expect: {
|
||||
connected: true,
|
||||
message: [{ kind: "value" as const, matcher: { equals: "ping" } }],
|
||||
},
|
||||
ws: {
|
||||
...makeWsTarget().ws,
|
||||
send: "ping",
|
||||
url: `ws://127.0.0.1:${echoServer.port}`,
|
||||
},
|
||||
});
|
||||
const result = await checker.execute(target, makeContext());
|
||||
expect(result.matched).toBe(true);
|
||||
expect(result.observation!["message"]).toBe("ping");
|
||||
expect(result.observation!["messageSize"]).toBe(4);
|
||||
});
|
||||
|
||||
test("交互模式 - 消息不匹配", async () => {
|
||||
const target = makeWsTarget({
|
||||
expect: {
|
||||
connected: true,
|
||||
message: [{ kind: "value" as const, matcher: { equals: "pong" } }],
|
||||
},
|
||||
ws: {
|
||||
...makeWsTarget().ws,
|
||||
send: "ping",
|
||||
url: `ws://127.0.0.1:${echoServer.port}`,
|
||||
},
|
||||
});
|
||||
const result = await checker.execute(target, makeContext());
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.failure).not.toBeNull();
|
||||
});
|
||||
|
||||
test("交互模式 - receiveTimeout 超时", async () => {
|
||||
const target = makeWsTarget({
|
||||
ws: {
|
||||
...makeWsTarget().ws,
|
||||
receiveTimeout: 500,
|
||||
send: "ping",
|
||||
url: `ws://127.0.0.1:${noReplyServer.port}`,
|
||||
},
|
||||
});
|
||||
const result = await checker.execute(target, makeContext());
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.failure?.phase).toBe("message");
|
||||
});
|
||||
|
||||
test("HTTP 403 握手失败", async () => {
|
||||
const target = makeWsTarget({ ws: { ...makeWsTarget().ws, url: `ws://127.0.0.1:${rejectServer.port}` } });
|
||||
const result = await checker.execute(target, makeContext());
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.observation!["connected"]).toBe(false);
|
||||
});
|
||||
|
||||
test("buildDetail 连接成功", () => {
|
||||
const detail = checker.buildDetail({ connected: true, connectTimeMs: 50, message: "hello" });
|
||||
expect(detail).toContain("connected");
|
||||
expect(detail).toContain("hello");
|
||||
});
|
||||
|
||||
test("buildDetail 连接失败", () => {
|
||||
const detail = checker.buildDetail({ connected: false, error: "connection refused" });
|
||||
expect(detail).toContain("connection failed");
|
||||
});
|
||||
});
|
||||
95
tests/server/checker/runner/ws/resolve.test.ts
Normal file
95
tests/server/checker/runner/ws/resolve.test.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import type { ResolveContext } from "../../../../../src/server/checker/runner/types";
|
||||
import type { ResolvedWsTarget } from "../../../../../src/server/checker/runner/ws/types";
|
||||
import type { RawTargetConfig } from "../../../../../src/server/checker/types";
|
||||
|
||||
import { checkerRegistry } from "../../../../../src/server/checker/runner";
|
||||
|
||||
function asWs(resolved: ReturnType<ReturnType<typeof checkerRegistry.get>["resolve"]>): ResolvedWsTarget {
|
||||
return resolved as ResolvedWsTarget;
|
||||
}
|
||||
|
||||
function makeRawTarget(overrides?: Partial<RawTargetConfig>): RawTargetConfig {
|
||||
return {
|
||||
id: "test-ws",
|
||||
type: "ws",
|
||||
ws: { url: "ws://example.com/ws" },
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makeResolveContext(overrides?: Partial<ResolveContext>): ResolveContext {
|
||||
return {
|
||||
configDir: "/tmp",
|
||||
defaultIntervalMs: 30000,
|
||||
defaultTimeoutMs: 10000,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("WsChecker resolve", () => {
|
||||
const checker = checkerRegistry.tryGet("ws")!;
|
||||
|
||||
test("最简 target 填充默认值", () => {
|
||||
const resolved = asWs(checker.resolve(makeRawTarget(), makeResolveContext()));
|
||||
expect(resolved.type).toBe("ws");
|
||||
expect(resolved.ws.url).toBe("ws://example.com/ws");
|
||||
expect(resolved.ws.headers).toEqual({});
|
||||
expect(resolved.ws.ignoreSSL).toBe(false);
|
||||
expect(resolved.ws.maxMessageBytes).toBe(4096);
|
||||
expect(resolved.ws.receiveTimeout).toBe(5000);
|
||||
expect(resolved.ws.send).toBeUndefined();
|
||||
expect(resolved.ws.subprotocols).toEqual([]);
|
||||
expect(resolved.expect).toEqual({ connected: true });
|
||||
expect(resolved.group).toBe("default");
|
||||
expect(resolved.intervalMs).toBe(30000);
|
||||
expect(resolved.timeoutMs).toBe(10000);
|
||||
});
|
||||
|
||||
test("完整配置正确 resolve", () => {
|
||||
const raw = makeRawTarget({
|
||||
expect: { connected: true, durationMs: { lte: 5000 } },
|
||||
ws: {
|
||||
headers: { Authorization: "Bearer token" },
|
||||
ignoreSSL: true,
|
||||
maxMessageBytes: "8KB",
|
||||
receiveTimeout: 3000,
|
||||
send: "ping",
|
||||
subprotocols: ["json"],
|
||||
url: "wss://api.example.com/ws",
|
||||
},
|
||||
});
|
||||
const resolved = asWs(checker.resolve(raw, makeResolveContext()));
|
||||
expect(resolved.ws.url).toBe("wss://api.example.com/ws");
|
||||
expect(resolved.ws.headers).toEqual({ Authorization: "Bearer token" });
|
||||
expect(resolved.ws.ignoreSSL).toBe(true);
|
||||
expect(resolved.ws.maxMessageBytes).toBe(8192);
|
||||
expect(resolved.ws.receiveTimeout).toBe(3000);
|
||||
expect(resolved.ws.send).toBe("ping");
|
||||
expect(resolved.ws.subprotocols).toEqual(["json"]);
|
||||
expect(resolved.expect?.connected).toBe(true);
|
||||
});
|
||||
|
||||
test("expect 默认 connected=true", () => {
|
||||
const raw = makeRawTarget({ expect: { durationMs: { lte: 1000 } } });
|
||||
const resolved = asWs(checker.resolve(raw, makeResolveContext()));
|
||||
expect(resolved.expect?.connected).toBe(true);
|
||||
});
|
||||
|
||||
test("expect.connected=false 保留", () => {
|
||||
const raw = makeRawTarget({ expect: { connected: false } });
|
||||
const resolved = asWs(checker.resolve(raw, makeResolveContext()));
|
||||
expect(resolved.expect?.connected).toBe(false);
|
||||
});
|
||||
|
||||
test("serialize 返回正确格式", () => {
|
||||
const resolved = asWs(checker.resolve(makeRawTarget(), makeResolveContext()));
|
||||
const serialized = checker.serialize(resolved);
|
||||
expect(serialized.target).toBe("ws://example.com/ws");
|
||||
const config = JSON.parse(serialized.config) as Record<string, unknown>;
|
||||
expect(config["url"]).toBe("ws://example.com/ws");
|
||||
expect(config["ignoreSSL"]).toBe(false);
|
||||
expect(config["receiveTimeout"]).toBe(5000);
|
||||
});
|
||||
});
|
||||
22
tests/server/checker/runner/ws/schema.test.ts
Normal file
22
tests/server/checker/runner/ws/schema.test.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import { checkerRegistry } from "../../../../../src/server/checker/runner";
|
||||
|
||||
describe("WsChecker schema", () => {
|
||||
const checker = checkerRegistry.tryGet("ws");
|
||||
|
||||
test("ws checker 注册到 registry", () => {
|
||||
expect(checker).toBeDefined();
|
||||
expect(checker?.type).toBe("ws");
|
||||
expect(checker?.configKey).toBe("ws");
|
||||
});
|
||||
|
||||
test("schemas 包含 authoring 和 normalized config/expect", () => {
|
||||
expect(checker).toBeDefined();
|
||||
expect(Object.keys(checker!.schemas).sort()).toEqual(["authoring", "normalized"].sort());
|
||||
expect(checker!.schemas.authoring.config).toBeDefined();
|
||||
expect(checker!.schemas.authoring.expect).toBeDefined();
|
||||
expect(checker!.schemas.normalized.config).toBeDefined();
|
||||
expect(checker!.schemas.normalized.expect).toBeDefined();
|
||||
});
|
||||
});
|
||||
215
tests/server/checker/runner/ws/validate.test.ts
Normal file
215
tests/server/checker/runner/ws/validate.test.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import type { CheckerValidationInput } from "../../../../../src/server/checker/runner/types";
|
||||
|
||||
import { validateWsConfig } from "../../../../../src/server/checker/runner/ws/validate";
|
||||
|
||||
function makeInput(targets: unknown[]): CheckerValidationInput {
|
||||
return {
|
||||
targets: targets as CheckerValidationInput["targets"],
|
||||
};
|
||||
}
|
||||
|
||||
describe("validateWsConfig", () => {
|
||||
test("合法 ws target 无错误", () => {
|
||||
const issues = validateWsConfig(makeInput([{ id: "t1", type: "ws", ws: { url: "ws://example.com" } }]));
|
||||
expect(issues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("合法 wss target 无错误", () => {
|
||||
const issues = validateWsConfig(makeInput([{ id: "t1", type: "ws", ws: { url: "wss://example.com/ws" } }]));
|
||||
expect(issues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("缺少 ws 分组", () => {
|
||||
const issues = validateWsConfig(makeInput([{ id: "t1", type: "ws" }]));
|
||||
expect(issues.length).toBeGreaterThan(0);
|
||||
expect(issues.some((i) => i.message.includes("ws"))).toBe(true);
|
||||
});
|
||||
|
||||
test("缺少 url", () => {
|
||||
const issues = validateWsConfig(makeInput([{ id: "t1", type: "ws", ws: {} }]));
|
||||
expect(issues.some((i) => i.path.includes("url"))).toBe(true);
|
||||
});
|
||||
|
||||
test("url 非 ws/wss 协议报错", () => {
|
||||
const issues = validateWsConfig(makeInput([{ id: "t1", type: "ws", ws: { url: "http://example.com" } }]));
|
||||
expect(issues.some((i) => i.code === "invalid-url")).toBe(true);
|
||||
});
|
||||
|
||||
test("url 格式非法报错", () => {
|
||||
const issues = validateWsConfig(makeInput([{ id: "t1", type: "ws", ws: { url: "not-a-url" } }]));
|
||||
expect(issues.some((i) => i.code === "invalid-url")).toBe(true);
|
||||
});
|
||||
|
||||
test("subprotocols 非数组报错", () => {
|
||||
const issues = validateWsConfig(
|
||||
makeInput([{ id: "t1", type: "ws", ws: { subprotocols: "json", url: "ws://example.com" } }]),
|
||||
);
|
||||
expect(issues.some((i) => i.path.includes("subprotocols"))).toBe(true);
|
||||
});
|
||||
|
||||
test("subprotocols 元素为空字符串报错", () => {
|
||||
const issues = validateWsConfig(
|
||||
makeInput([{ id: "t1", type: "ws", ws: { subprotocols: [""], url: "ws://example.com" } }]),
|
||||
);
|
||||
expect(issues.some((i) => i.path.includes("subprotocols"))).toBe(true);
|
||||
});
|
||||
|
||||
test("subprotocols 合法无错误", () => {
|
||||
const issues = validateWsConfig(
|
||||
makeInput([{ id: "t1", type: "ws", ws: { subprotocols: ["json", "binary"], url: "ws://example.com" } }]),
|
||||
);
|
||||
expect(issues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("ignoreSSL 非布尔值报错", () => {
|
||||
const issues = validateWsConfig(
|
||||
makeInput([{ id: "t1", type: "ws", ws: { ignoreSSL: "yes", url: "ws://example.com" } }]),
|
||||
);
|
||||
expect(issues.some((i) => i.path.includes("ignoreSSL"))).toBe(true);
|
||||
});
|
||||
|
||||
test("receiveTimeout 非数字报错", () => {
|
||||
const issues = validateWsConfig(
|
||||
makeInput([{ id: "t1", type: "ws", ws: { receiveTimeout: "slow", url: "ws://example.com" } }]),
|
||||
);
|
||||
expect(issues.some((i) => i.path.includes("receiveTimeout"))).toBe(true);
|
||||
});
|
||||
|
||||
test("receiveTimeout 为负数报错", () => {
|
||||
const issues = validateWsConfig(
|
||||
makeInput([{ id: "t1", type: "ws", ws: { receiveTimeout: -1, url: "ws://example.com" } }]),
|
||||
);
|
||||
expect(issues.some((i) => i.path.includes("receiveTimeout"))).toBe(true);
|
||||
});
|
||||
|
||||
test("maxMessageBytes 非法值报错", () => {
|
||||
const issues = validateWsConfig(
|
||||
makeInput([{ id: "t1", type: "ws", ws: { maxMessageBytes: -1, url: "ws://example.com" } }]),
|
||||
);
|
||||
expect(issues.some((i) => i.path.includes("maxMessageBytes"))).toBe(true);
|
||||
});
|
||||
|
||||
test("ws 分组未知字段", () => {
|
||||
const issues = validateWsConfig(makeInput([{ id: "t1", type: "ws", ws: { tls: true, url: "ws://example.com" } }]));
|
||||
expect(issues.some((i) => i.message.includes("未知字段"))).toBe(true);
|
||||
});
|
||||
|
||||
test("expect.message 未配置 ws.send 报错", () => {
|
||||
const issues = validateWsConfig(
|
||||
makeInput([
|
||||
{
|
||||
expect: { message: [{ contains: "pong" }] },
|
||||
id: "t1",
|
||||
type: "ws",
|
||||
ws: { url: "ws://example.com" },
|
||||
},
|
||||
]),
|
||||
);
|
||||
expect(issues.some((i) => i.message.includes("send"))).toBe(true);
|
||||
});
|
||||
|
||||
test("expect.message 配置 ws.send 无错误", () => {
|
||||
const issues = validateWsConfig(
|
||||
makeInput([
|
||||
{
|
||||
expect: { message: [{ contains: "pong" }] },
|
||||
id: "t1",
|
||||
type: "ws",
|
||||
ws: { send: "ping", url: "ws://example.com" },
|
||||
},
|
||||
]),
|
||||
);
|
||||
expect(issues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("expect.connected 非布尔值报错", () => {
|
||||
const issues = validateWsConfig(
|
||||
makeInput([
|
||||
{
|
||||
expect: { connected: "yes" },
|
||||
id: "t1",
|
||||
type: "ws",
|
||||
ws: { url: "ws://example.com" },
|
||||
},
|
||||
]),
|
||||
);
|
||||
expect(issues.some((i) => i.path.includes("connected"))).toBe(true);
|
||||
});
|
||||
|
||||
test("expect.connected=false 时 expect.message 报错", () => {
|
||||
const issues = validateWsConfig(
|
||||
makeInput([
|
||||
{
|
||||
expect: { connected: false, message: [{ contains: "pong" }] },
|
||||
id: "t1",
|
||||
type: "ws",
|
||||
ws: { send: "ping", url: "ws://example.com" },
|
||||
},
|
||||
]),
|
||||
);
|
||||
expect(issues.some((i) => i.message.includes("connected"))).toBe(true);
|
||||
});
|
||||
|
||||
test("expect.connected=false 时 expect.handshakeHeaders 报错", () => {
|
||||
const issues = validateWsConfig(
|
||||
makeInput([
|
||||
{
|
||||
expect: { connected: false, handshakeHeaders: { "Sec-WebSocket-Protocol": { equals: "json" } } },
|
||||
id: "t1",
|
||||
type: "ws",
|
||||
ws: { url: "ws://example.com" },
|
||||
},
|
||||
]),
|
||||
);
|
||||
expect(issues.some((i) => i.message.includes("connected"))).toBe(true);
|
||||
});
|
||||
|
||||
test("expect.connected=false 时 expect.connectTimeMs 报错", () => {
|
||||
const issues = validateWsConfig(
|
||||
makeInput([
|
||||
{
|
||||
expect: { connected: false, connectTimeMs: { lte: 1000 } },
|
||||
id: "t1",
|
||||
type: "ws",
|
||||
ws: { url: "ws://example.com" },
|
||||
},
|
||||
]),
|
||||
);
|
||||
expect(issues.some((i) => i.message.includes("connected"))).toBe(true);
|
||||
});
|
||||
|
||||
test("expect.connected=false 单独配置合法", () => {
|
||||
const issues = validateWsConfig(
|
||||
makeInput([
|
||||
{
|
||||
expect: { connected: false },
|
||||
id: "t1",
|
||||
type: "ws",
|
||||
ws: { url: "ws://example.com" },
|
||||
},
|
||||
]),
|
||||
);
|
||||
expect(issues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("expect 未知字段报错", () => {
|
||||
const issues = validateWsConfig(
|
||||
makeInput([
|
||||
{
|
||||
expect: { status: [200] },
|
||||
id: "t1",
|
||||
type: "ws",
|
||||
ws: { url: "ws://example.com" },
|
||||
},
|
||||
]),
|
||||
);
|
||||
expect(issues.some((i) => i.message.includes("未知字段"))).toBe(true);
|
||||
});
|
||||
|
||||
test("非 ws 类型 target 跳过", () => {
|
||||
const issues = validateWsConfig(makeInput([{ http: { url: "http://example.com" }, id: "t1", type: "http" }]));
|
||||
expect(issues).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user