From f4ce410345d856d3c34a95e84791646391f48f95 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Oct 2026 03:46:47 +0000 Subject: [PATCH] Workers P2: Engine-Rauchtest (19 anklickbar, 150 s Ereignisfeed) Co-Authored-By: Claude Sonnet 5.5 --- tests/unit/workersSmoke.test.mjs | 118 +++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 tests/unit/workersSmoke.test.mjs diff --git a/tests/unit/workersSmoke.test.mjs b/tests/unit/workersSmoke.test.mjs new file mode 100644 index 0000000..7de7ace --- /dev/null +++ b/tests/unit/workersSmoke.test.mjs @@ -0,0 +1,118 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { createEngineSandbox } from './helpers/engineVm.mjs' +import { makeState, ev, IDS } from './helpers/workersFixtures.mjs' +import { loadModule } from './load.mjs' + +// Rauchtest der Engine ohne Browser (Muster: entwuerfe/smoke.js): 19 Figuren sind anklickbar, 150 simulierte Sekunden +// mit allen 26 Ereignisarten und einem Stoß von 120 Übergaben laufen ohne Wurf und beruhigen sich danach wieder. + +const { EVENT_KINDS } = await loadModule('src/lib/workersEvents.js') + +// Eine Art alle 5 s ab Sekunde 3, in dieser Reihenfolge: [Art, Arbeiter, Zusatzfelder] +const PLAN = [ + ['gelesen', 'kim'], + ['fund', 'rita'], + ['doppelt', 'arne'], + ['uebergabe', 'rita', { to: 'bea' }], + ['bewertet', 'bea'], + ['top3', 'bea'], + ['botlead', 'arne'], + ['entwurf', 'max'], + ['pult', 'clara'], + ['gesendet', 'max'], + ['eingang', 'nora'], + ['sortiert', 'nora'], + ['bau_start', 'bob'], + ['bau_fertig', 'bob'], + ['pruefung_zurueck', 'paul', { to: 'bob', meta: { round: 1 } }], + ['pruefung_ok', 'paul'], + ['vorschau_link', 'max'], + ['angebot', 'theo'], + ['deal', 'system'], + ['gesperrt', 'system'], + ['budget_warnung', 'otto'], + ['budget_stopp', 'otto'], + ['pause', 'system'], + ['fehler', 'ben'], + ['auftrag', 'otto', { to: 'kim' }], + ['leerlauf', 'pia'], +] +const FIRST_SEC = 3 +const EVERY_SEC = 5 +const BURST_SEC = 100 +const BURST_SIZE = 120 + +// Die Ereignisse für Sekunde `sec`: höchstens eine Art aus dem Plan, ab Sekunde 100 zusätzlich der Stoß (einmalig) +function scripted(sec) { + const out = [] + const slot = (sec - FIRST_SEC) / EVERY_SEC + if (Number.isInteger(slot) && slot >= 0 && slot < PLAN.length) { + const [kind, who, extra] = PLAN[slot] + out.push(ev(kind, who, extra)) + } + if (sec === BURST_SEC) { + for (let i = 0; i < BURST_SIZE; i++) out.push(ev('uebergabe', IDS[i % IDS.length], { to: IDS[(i + 4) % IDS.length] })) + } + return out +} + +const OFF = { kim: false, pia: false, finn: false } + +test('alle 19 Worker sind anklickbar (Auswahl liefert die richtige workerId)', async () => { + const sb = await createEngineSandbox() + const picked = [] + const h = sb.mountTower(sb.canvas, sb.opts({ onSelect: (id) => picked.push(id) })) + h.setState(makeState()) + for (const id of IDS) { + const p = h.locate(id) + assert.ok(p, id) + sb.click(p.x, p.y) + assert.equal(picked.at(-1), id) + } + assert.deepEqual([...new Set(picked)].sort(), [...IDS].sort()) + h.destroy() +}) + +test('150 s mit allen 26 Ereignisarten: kein Wurf, alle endlich und im Bild, danach ruhig', async () => { + const sb = await createEngineSandbox() + const h = sb.mountTower(sb.canvas, sb.opts({ onSelect() {} })) + h.setState(makeState({ connected: OFF })) + const fed = [] + for (let s = 0; s < 150; s++) { + const evs = scripted(s) + if (evs.length) { + fed.push(...evs) + h.pushEvents(evs) + } + if (s % 15 === 0) { + h.setState(makeState({ connected: OFF, kpis: { pultOpen: s % 7, dealsMonth: 3 + Math.floor(s / 40), botLeads: 112 + s } })) + } + sb.tick(1) + // alle zwei Sekunden ein Raster von Klicks über die ganze Leinwand + if (s % 2 === 0) { + for (let x = 10; x < 960; x += 40) { + for (let y = 10; y < 768; y += 40) sb.click(x, y) + } + } + } + // der Plan deckt die geschlossene Liste vollständig ab, der Stoß kam genau einmal + assert.equal(PLAN.length, 26) + assert.deepEqual([...new Set(fed.map((e) => e.kind))].sort(), [...EVENT_KINDS].sort()) + assert.equal(fed.length, PLAN.length + BURST_SIZE) + assert.equal(fed.filter((e) => e.kind === 'uebergabe').length, BURST_SIZE + 1) + // danach 90 ruhige Sekunden: Warteschlangen, Aufzug und Effekte laufen leer + for (let s = 0; s < 90; s++) sb.tick(1) + const dbg = h.debug() + assert.equal(dbg.pending, 0) + assert.equal(dbg.lift, 'idle') + assert.equal(dbg.fx, 0) + for (const a of dbg.agents) { + assert.ok(Number.isFinite(a.x) && Number.isFinite(a.y) && a.x > -30 && a.x < 350 && a.floor >= 0 && a.floor <= 9, a.id) + } + assert.ok(sb.counts.fillRect > 100000) + assert.ok(sb.counts.fillText > 1000) + assert.ok(sb.seenTexts.has('noch nicht angeschlossen')) + h.destroy() + assert.equal(Object.values(sb.listeners).flat().length, 0) +})