import { buildServer, type BuiltServer } from "../src/app.js"; import { io as ioc, type Socket } from "socket.io-client"; import type { BoardState } from "../src/room.js"; export type TestServer = BuiltServer & { port: number; url: string }; export async function startTestServer(): Promise { const server = buildServer({ cleanup: false }); const port = await server.listen(0); // puerto efímero return Object.assign(server, { port, url: `http://localhost:${port}` }); } export async function createBoard( url: string, body: Record = {}, ): Promise<{ boardId: string; moderatorToken: string; expiresAt: string | null }> { const res = await fetch(`${url}/api/boards`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ templateId: "mad-sad-glad", votesPerUser: 3, ...body }), }); return res.json(); } // Cliente de test: mantiene el último estado recibido para evitar carreras // (el estado buscado puede haber llegado antes de adjuntar el listener). export class Client { last: BoardState | null = null; constructor(public socket: Socket) { socket.on("board:state", (s: BoardState) => (this.last = s)); } emit(event: string, payload: Record) { this.socket.emit(event, payload); } // Resuelve cuando el último estado (presente o futuro) cumple el predicado. waitFor(predicate: (s: BoardState) => boolean, timeoutMs = 10000): Promise { if (this.last && predicate(this.last)) return Promise.resolve(this.last); return new Promise((resolve, reject) => { const timer = setTimeout(() => { this.socket.off("board:state", handler); reject(new Error("waitFor timeout")); }, timeoutMs); const handler = (s: BoardState) => { if (predicate(s)) { clearTimeout(timer); this.socket.off("board:state", handler); resolve(s); } }; this.socket.on("board:state", handler); }); } close() { this.socket.close(); } } // Conecta, hace join y devuelve un Client ya con su primer estado. export function join( url: string, boardId: string, participantId: string, name: string, ): Promise { return new Promise((resolve, reject) => { const socket = ioc(url, { transports: ["websocket"], forceNew: true }); const client = new Client(socket); const timer = setTimeout(() => reject(new Error("join timeout")), 10000); socket.on("connect", () => socket.emit("board:join", { boardId, participantId, name })); socket.once("error", (e) => { clearTimeout(timer); reject(new Error(`join error: ${JSON.stringify(e)}`)); }); const ready = (s: BoardState) => { clearTimeout(timer); socket.off("board:state", ready); client.last = s; resolve(client); }; socket.on("board:state", ready); }); }