import { afterAll, beforeAll, describe, expect, it, } from "@suaveplan/testing/runner"; import { WebSocket as WsClient } from "ws"; import { WebSocketGateway } from "../src/gateway/gateway.js"; import { makeRealWsServerFactory } from "./setup/real-ws-adapter.js"; function baseUrl(address: string, port: number): string { const host = address === "::" || address === "0.0.0.0" ? "127.0.0.1" : address; return `http://${host}:${port}`; } function wsUrl(address: string, port: number): string { const host = address === "::" || address === "0.0.0.0" ? "127.0.0.1" : address; return `ws://${host}:${port}`; } let gateway: WebSocketGateway; let address = ""; let port = 0; beforeAll(async () => { let snap: () => ReturnType = () => ({ running: false, disposed: false, clientCount: 0, roomCount: 0, startedAt: undefined, }); const factory = makeRealWsServerFactory(() => snap()); gateway = new WebSocketGateway({ port: 0, host: "127.0.0.1", wsServerFactory: factory, heartbeatIntervalMs: 60_000, }); snap = () => gateway.snapshot(); const result = await gateway.start(); address = result.address; port = result.port; }); afterAll(async () => { await gateway?.stop(); }); describe("integration: real ws server boot + health probes", () => { it("/health returns 200 ok while running", { timeout: 10_000, }, async () => { const res = await fetch(`${baseUrl(address, port)}/health`); expect(res.status).toBe(200); const body = (await res.json()) as { status: string; running: boolean }; expect(body.status).toBe("ok"); expect(body.running).toBe(true); }); it("/ready returns 200 once started", { timeout: 10_000 }, async () => { const res = await fetch(`${baseUrl(address, port)}/ready`); expect(res.status).toBe(200); }); it("/live returns 200", { timeout: 10_000 }, async () => { const res = await fetch(`${baseUrl(address, port)}/live`); expect(res.status).toBe(200); }); }); describe("integration: 3 real ws clients connect, join a room, broadcast", () => { it("all 3 clients receive broadcasts to a shared room", { timeout: 15_000, }, async () => { // Open 3 real WebSocket connections to the gateway. const c1 = new WsClient(wsUrl(address, port)); const c2 = new WsClient(wsUrl(address, port)); const c3 = new WsClient(wsUrl(address, port)); const all = [c1, c2, c3]; await Promise.all( all.map( (c) => new Promise((resolve, reject) => { c.once("open", () => resolve()); c.once("error", reject); }) ) ); // Wait a tick for the daemon to register all three sockets. await new Promise((r) => setTimeout(r, 50)); const ids = Array.from( { length: 3 }, (_, i) => `wsc-${i + 1}` as const ); expect(gateway.clientCount).toBe(3); // Create the room and join all three clients to it. gateway.createRoom({ id: "shared", maxClients: 10 }); for (const id of ids) { const joined = await gateway.joinRoom("shared", id); expect(joined).toBe(true); } expect(gateway.getRoom("shared")?.size).toBe(3); // Broadcast from id-1; the room returns ids of (id-2, id-3). const result = await gateway.broadcastToRoom("shared", ids[0]!, { text: "hello", }); expect(result?.recipients.sort()).toEqual([ids[1]!, ids[2]!]); expect(result?.message.payload).toEqual({ text: "hello" }); for (const c of all) c.close(); await new Promise((r) => setTimeout(r, 50)); expect(gateway.clientCount).toBe(0); }); });