// Gartic mode socket flow test. const { io } = require("socket.io-client"); const URL = "http://localhost:3000"; const TIMEOUT_MS = 30000; const results = []; let exitCode = 0; const pass = (n) => { results.push({ name: n, status: "pass" }); console.log("PASS:", n); }; const fail = (n, e) => { results.push({ name: n, status: "fail", error: String(e) }); console.log("FAIL:", n, "-", e); exitCode = 1; }; function waitFor(socket, event, predicate = () => true, timeout = 8000) { return new Promise((resolve, reject) => { const t = setTimeout(() => reject(new Error(`timeout waiting for ${event}`)), timeout); const handler = (data) => { if (predicate(data)) { clearTimeout(t); socket.off(event, handler); resolve(data); } }; socket.on(event, handler); }); } function emitAck(s, event, payload) { return new Promise((resolve, reject) => { const t = setTimeout(() => reject(new Error(`ack timeout for ${event}`)), 5000); s.emit(event, payload, (resp) => { clearTimeout(t); resolve(resp); }); }); } const killer = setTimeout(() => { console.log("FAIL: Global timeout"); process.exit(1); }, TIMEOUT_MS); killer.unref(); (async () => { let a, b, c; try { a = io(URL, { transports: ["websocket"], forceNew: true }); b = io(URL, { transports: ["websocket"], forceNew: true }); c = io(URL, { transports: ["websocket"], forceNew: true }); await Promise.all([ new Promise((r) => a.on("connect", r)), new Promise((r) => b.on("connect", r)), new Promise((r) => c.on("connect", r)), ]); pass("gartic: three clients connected"); // A creates gartic room const create = await emitAck(a, "room:create", { nickname: "Alice", avatar: "🐱", mode: "gartic", settings: {}, }); if (!create || !create.ok) throw new Error("create failed: " + JSON.stringify(create)); const code = create.code; pass("gartic: room:create ok, code=" + code); // B and C join const j1 = await emitAck(b, "room:join", { code, nickname: "Bob", avatar: "🐶" }); if (!j1 || !j1.ok) throw new Error("B join failed: " + JSON.stringify(j1)); pass("gartic: B joined"); const j2 = await emitAck(c, "room:join", { code, nickname: "Carol", avatar: "🦊" }); if (!j2 || !j2.ok) throw new Error("C join failed: " + JSON.stringify(j2)); pass("gartic: C joined"); // Each client should receive a "gartic:turn" with task=prompt after game:start const aTurn0P = waitFor(a, "gartic:turn", () => true, 5000); const bTurn0P = waitFor(b, "gartic:turn", () => true, 5000); const cTurn0P = waitFor(c, "gartic:turn", () => true, 5000); const start = await emitAck(a, "game:start", {}); if (!start || !start.ok) throw new Error("game:start: " + JSON.stringify(start)); pass("gartic: game:start ok"); const aT0 = await aTurn0P; const bT0 = await bTurn0P; const cT0 = await cTurn0P; if (aT0.task !== "prompt" || bT0.task !== "prompt" || cT0.task !== "prompt") throw new Error("expected prompt task, got " + aT0.task + "/" + bT0.task + "/" + cT0.task); pass("gartic: all three got gartic:turn task=prompt"); // All three submit prompts const aTurn1P = waitFor(a, "gartic:turn", () => true, 8000); const bTurn1P = waitFor(b, "gartic:turn", () => true, 8000); const cTurn1P = waitFor(c, "gartic:turn", () => true, 8000); const sub1 = await emitAck(a, "gartic:submit", { type: "prompt", data: "a cat" }); if (!sub1 || !sub1.ok) throw new Error("A prompt submit failed: " + JSON.stringify(sub1)); const sub2 = await emitAck(b, "gartic:submit", { type: "prompt", data: "a robot" }); if (!sub2 || !sub2.ok) throw new Error("B prompt submit failed: " + JSON.stringify(sub2)); const sub3 = await emitAck(c, "gartic:submit", { type: "prompt", data: "a tree" }); if (!sub3 || !sub3.ok) throw new Error("C prompt submit failed: " + JSON.stringify(sub3)); pass("gartic: prompts submitted ok"); const aT1 = await aTurn1P; const bT1 = await bTurn1P; const cT1 = await cTurn1P; if (aT1.task !== "drawing" || bT1.task !== "drawing" || cT1.task !== "drawing") throw new Error("expected drawing task, got " + aT1.task); pass("gartic: all three got next turn task=drawing with content"); if (!aT1.content || !bT1.content || !cT1.content) throw new Error("drawing content missing"); pass("gartic: drawing turn includes prior prompt as content"); // Submit drawings — should advance to guess phase (not done) for 3 players const tinyImg = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=="; const aTurn2P = waitFor(a, "gartic:turn", () => true, 8000); const bTurn2P = waitFor(b, "gartic:turn", () => true, 8000); const cTurn2P = waitFor(c, "gartic:turn", () => true, 8000); const d1 = await emitAck(a, "gartic:submit", { type: "drawing", data: tinyImg }); if (!d1 || !d1.ok) throw new Error("A drawing submit failed: " + JSON.stringify(d1)); const d2 = await emitAck(b, "gartic:submit", { type: "drawing", data: tinyImg }); if (!d2 || !d2.ok) throw new Error("B drawing submit failed: " + JSON.stringify(d2)); const d3 = await emitAck(c, "gartic:submit", { type: "drawing", data: tinyImg }); if (!d3 || !d3.ok) throw new Error("C drawing submit failed: " + JSON.stringify(d3)); pass("gartic: drawings submitted ok"); // 3 players: totalTurns = 3 -> turnIndex 0 (prompt), 1 (drawing), 2 (guess) const aT2 = await aTurn2P; const bT2 = await bTurn2P; const cT2 = await cTurn2P; if (aT2.task !== "guess" || bT2.task !== "guess" || cT2.task !== "guess") throw new Error("expected guess task, got " + aT2.task + "/" + bT2.task + "/" + cT2.task); pass("gartic: all three got next turn task=guess"); // Submit guesses — book should complete const bookCompleteP = waitFor(a, "gartic:bookComplete", () => true, 10000); const g1 = await emitAck(a, "gartic:submit", { type: "guess", data: "kitty" }); if (!g1 || !g1.ok) throw new Error("A guess submit failed: " + JSON.stringify(g1)); const g2 = await emitAck(b, "gartic:submit", { type: "guess", data: "metal man" }); if (!g2 || !g2.ok) throw new Error("B guess submit failed: " + JSON.stringify(g2)); const g3 = await emitAck(c, "gartic:submit", { type: "guess", data: "plant" }); if (!g3 || !g3.ok) throw new Error("C guess submit failed: " + JSON.stringify(g3)); pass("gartic: guesses submitted ok"); const bookComplete = await bookCompleteP; if (!bookComplete || !Array.isArray(bookComplete.books)) { throw new Error("expected gartic:bookComplete event"); } pass("gartic: gartic:bookComplete fired, books=" + bookComplete.books.length); } catch (e) { fail("gartic flow", e && e.message ? e.message : String(e)); } finally { try { a && a.disconnect(); } catch (_) {} try { b && b.disconnect(); } catch (_) {} try { c && c.disconnect(); } catch (_) {} clearTimeout(killer); console.log("__RESULTS__ " + JSON.stringify(results)); setTimeout(() => process.exit(exitCode), 200); } })();