import { describe, expect, it, afterEach, beforeEach } from "vitest" import { serve } from "@hono/node-server" import { app, injectWebSocket } from "../src/app" import { registerWebSocketRoutes } from "../src/ws/handler" import { roomManager } from "../src/rooms/index" // Register WS routes once registerWebSocketRoutes() let server: ReturnType function waitForMessage(ws: WebSocket): Promise { return new Promise((resolve) => { ws.addEventListener( "message", (event) => { resolve(JSON.parse(event.data as string)) }, { once: true }, ) }) } function waitForOpen(ws: WebSocket): Promise { return new Promise((resolve) => { if (ws.readyState === WebSocket.OPEN) { resolve() } else { ws.addEventListener("open", () => resolve(), { once: true }) } }) } describe("WebSocket handler", () => { let port: number beforeEach(async () => { roomManager.reset() port = 3100 + Math.floor(Math.random() * 900) server = serve({ fetch: app.fetch, port }) injectWebSocket(server) }) afterEach(() => { server.close() }) it("creates a room via HTTP and connects via WebSocket", async () => { // Create room via HTTP const res = await fetch(`http://localhost:${port}/rooms`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ displayName: "Host" }), }) const { data } = (await res.json()) as { data: { code: string; sessionId: string } } expect(data.code).toMatch(/^[A-Z0-9]{4}$/) // Connect as host via WebSocket const ws = new WebSocket(`ws://localhost:${port}/ws/${data.code}?sessionId=${data.sessionId}`) await waitForOpen(ws) const msg = (await waitForMessage(ws)) as { type: string; room: { code: string } } expect(msg.type).toBe("room_state") expect(msg.room.code).toBe(data.code) ws.close() }) it("player joins room via WebSocket", async () => { // Create room const res = await fetch(`http://localhost:${port}/rooms`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ displayName: "Host" }), }) const { data } = (await res.json()) as { data: { code: string; sessionId: string } } // Connect host const hostWs = new WebSocket(`ws://localhost:${port}/ws/${data.code}?sessionId=${data.sessionId}`) await waitForOpen(hostWs) await waitForMessage(hostWs) // room_state // Connect player (no sessionId) const playerWs = new WebSocket(`ws://localhost:${port}/ws/${data.code}`) await waitForOpen(playerWs) await waitForMessage(playerWs) // initial room_state // Set up listeners BEFORE sending to avoid race conditions const playerMsgPromise = waitForMessage(playerWs) const hostMsgPromise = waitForMessage(hostWs) // Player sends join_room playerWs.send(JSON.stringify({ type: "join_room", displayName: "Player 1" })) // Player receives room_state with sessionId const playerMsg = (await playerMsgPromise) as { type: string; sessionId?: string } expect(playerMsg.type).toBe("room_state") expect(playerMsg.sessionId).toBeDefined() // Host receives player_joined broadcast const hostMsg = (await hostMsgPromise) as { type: string; player: { displayName: string } } expect(hostMsg.type).toBe("player_joined") expect(hostMsg.player.displayName).toBe("Player 1") hostWs.close() playerWs.close() }) })