1
0

feat: WS checker,支持可达性检测和单次请求-响应交互验证

This commit is contained in:
2026-05-25 14:13:43 +08:00
parent 714b635aef
commit c1db793073
20 changed files with 2339 additions and 4 deletions

View 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");
});
});