From 1239394578a870ffabf3dd9efb092b2f85dc45e0 Mon Sep 17 00:00:00 2001 From: Jason Dekarske Date: Fri, 10 Jul 2026 23:21:55 -0700 Subject: [PATCH] test(e2e): multi-client observers replace room-full check --- e2e/e2e_multi_client.cjs | 75 ++++++++++++++++++++++++++++++++++++++++ e2e/e2e_room_full.cjs | 43 ----------------------- 2 files changed, 75 insertions(+), 43 deletions(-) create mode 100644 e2e/e2e_multi_client.cjs delete mode 100644 e2e/e2e_room_full.cjs diff --git a/e2e/e2e_multi_client.cjs b/e2e/e2e_multi_client.cjs new file mode 100644 index 0000000..839a915 --- /dev/null +++ b/e2e/e2e_multi_client.cjs @@ -0,0 +1,75 @@ +const WebSocket = require('ws') + +const room = 'MULTI' + Math.floor(Math.random() * 1000) +const base = 'ws://127.0.0.1:3000/ws?room=' + room + +function connect(name) { + return new Promise((resolve, reject) => { + const ws = new WebSocket(base) + const messages = [] + ws.on('message', (data) => { + const msg = JSON.parse(data.toString()) + messages.push(msg) + console.log(`[${name}]`, msg.type) + if (msg.type === 'joined') { + resolve({ ws, messages }) + } + }) + ws.on('open', () => console.log(`[${name}] open`)) + ws.on('error', (e) => { console.error(`[${name}] error`, e.message); reject(e) }) + ws.on('close', (code) => console.log(`[${name}] close`, code)) + }) +} + +function waitFor(client, pred, timeout = 10000) { + const start = Date.now() + return new Promise((resolve, reject) => { + const check = () => { + if (pred(client.messages)) return resolve(undefined) + if (Date.now() - start > timeout) return reject(new Error('timeout')) + setTimeout(check, 50) + } + check() + }) +} + +;(async () => { + // 3 clients join the same room + const p1 = await connect('p1') + const p2 = await connect('p2') + const p3 = await connect('p3') + + // All 3 receive game_state after join + await waitFor(p1, msgs => msgs.some(m => m.type === 'game_state'), 5000) + await waitFor(p2, msgs => msgs.some(m => m.type === 'game_state'), 5000) + await waitFor(p3, msgs => msgs.some(m => m.type === 'game_state'), 5000) + console.log('All 3 clients received game_state') + + // p1 throws for the current turn_team + const state = p1.messages.find(m => m.type === 'game_state') + const turn = state.turn_team + console.log(`p1 throwing for team ${turn}`) + p1.ws.send(JSON.stringify({ type: 'throw', team: turn, broom_x: 0.5, broom_y: 39, weight: 7, curl: 1, friction: 1.0 })) + + // All 3 clients eventually see trajectory or updated game_state + await waitFor(p1, msgs => msgs.some(m => m.type === 'trajectory'), 15000) + await waitFor(p2, msgs => msgs.some(m => m.type === 'trajectory'), 15000) + await waitFor(p3, msgs => msgs.some(m => m.type === 'trajectory'), 15000) + console.log('All 3 clients received trajectory') + + // All 3 see an updated game_state after the throw + const lastIdx = p1.messages.length - 1 + await waitFor(p1, msgs => msgs.slice(-1)[0]?.type === 'game_state', 15000) + await waitFor(p2, msgs => msgs.slice(-1)[0]?.type === 'game_state', 15000) + await waitFor(p3, msgs => msgs.slice(-1)[0]?.type === 'game_state', 15000) + console.log('All 3 clients received updated game_state after throw') + + console.log('Multi-client observer test passed') + p1.ws.close() + p2.ws.close() + p3.ws.close() + process.exit(0) +})().catch(err => { + console.error(err) + process.exit(1) +}) \ No newline at end of file diff --git a/e2e/e2e_room_full.cjs b/e2e/e2e_room_full.cjs deleted file mode 100644 index 400a587..0000000 --- a/e2e/e2e_room_full.cjs +++ /dev/null @@ -1,43 +0,0 @@ -const WebSocket = require('ws') - -const base = (room) => `ws://127.0.0.1:3000/ws?room=${room}` - -function connect(name, room) { - return new Promise((resolve, reject) => { - const ws = new WebSocket(base(room)) - const messages = [] - ws.on('open', () => resolve({ ws, messages })) - ws.on('message', (data) => messages.push(JSON.parse(data.toString()))) - ws.on('error', reject) - }) -} - -function waitFor(messages, pred, timeout = 10000) { - const start = Date.now() - return new Promise((resolve, reject) => { - const check = () => { - if (pred()) return resolve(undefined) - if (Date.now() - start > timeout) return reject(new Error('timeout')) - setTimeout(check, 50) - } - check() - }) -} - -;(async () => { - const room = 'ROOMFULL' + Math.floor(Math.random() * 1000) - const p1 = await connect('p1', room) - const p2 = await connect('p2', room) - const p3 = await connect('p3', room) - await waitFor(p1.messages, () => p1.messages.some(m => m.type === 'game_state'), 5000) - await waitFor(p2.messages, () => p2.messages.some(m => m.type === 'game_state'), 5000) - await waitFor(p3.messages, () => p3.messages.some(m => m.type === 'game_state'), 5000) - console.log('Multiple observers can watch the same room state') - p1.ws.close() - p2.ws.close() - p3.ws.close() - process.exit(0) -})().catch((e) => { - console.error(e) - process.exit(1) -})