/* Дымовой тест M1: сервер должен быть запущен с DEV_HTTP=1, SQLITE_PATH=:memory:. `pnpm --filter @idle/server smoke` */ import WebSocket from 'ws'; const BASE = 'http://localhost:3000'; const WS = 'ws://localhost:3000/ws'; function connect(onMsg) { const ws = new WebSocket(WS); ws.on('open', () => ws.send(JSON.stringify({ t: 'hello', v: 2 }))); ws.on('message', (d) => onMsg(JSON.parse(String(d)))); return ws; } // 1. welcome + полный снапшот const first = []; await new Promise((resolve, reject) => { const ws = connect((m) => { first.push(m); if (first.length >= 2) { ws.close(); resolve(); } }); ws.on('error', reject); setTimeout(() => reject(new Error('timeout waiting welcome+snapshot')), 5000); }); const [welcome, snap] = first; console.log('welcome:', JSON.stringify({ t: welcome.t, v: welcome.v, tickMs: welcome.tickMs })); if (welcome.t !== 'welcome' || welcome.v !== 2) throw new Error(`bad welcome: ${JSON.stringify(welcome)}`); if (snap.t !== 'snapshot' || snap.nodes.length !== 7) throw new Error(`bad snapshot: ${snap.t}, nodes=${snap.nodes?.length}`); console.log(`snapshot: деревьев=${snap.nodes.length}, зрителей=${snap.avatars.length}`); // 2. команда через dev-эндпоинт const res = await fetch(`${BASE}/dev/command`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name: 'Тестер', text: '!рубить' }), }); if (!res.ok) throw new Error(`dev command failed: ${res.status} ${await res.text()}`); console.log('dev command: accepted'); // 3. ждём дельту: аватар «Тестер» должен заспавниться и пойти к дереву const delta = await new Promise((resolve, reject) => { const ws = connect((m) => { if (m.t === 'delta' && m.avatars?.some((a) => a.name === 'Тестер')) { ws.close(); resolve(m); } }); ws.on('error', reject); setTimeout(() => reject(new Error('no delta with Тестер')), 8000); }); const tester = delta.avatars.find((a) => a.name === 'Тестер'); console.log(`delta: ${tester.name} moving=${tester.moving} action=${tester.action} target=(${tester.tx},${tester.ty})`); console.log(`events: ${(delta.events ?? []).map((e) => e.k).join(',') || '—'}`); if (!tester.moving || tester.action !== 'idle') throw new Error('Тестер должен идти к дереву (idle + moving)'); // 4. неверная версия протокола — close 4001 const closeCode = await new Promise((resolve) => { const ws = new WebSocket(WS); ws.on('open', () => ws.send(JSON.stringify({ t: 'hello', v: 999 }))); ws.on('close', (code) => resolve(code)); setTimeout(() => resolve('no-close'), 3000); }); if (closeCode !== 4001) throw new Error(`expected close 4001, got ${closeCode}`); console.log('WS smoke: OK');