import { test } from 'node:test'; import assert from 'node:assert/strict'; import { applyLogLines, initialLiveState, seriesScore, splitLogBody } from '../src/liveScore.js'; const ctx = { teamNames: { A: 'MaxLAN Blau', B: 'MaxLAN Rot' }, maxRounds: 24, overtimeRounds: 6 }; const ts = '10/25/2026 - 18:00:00.000 - '; function round(winnerSide, ct, t, notice) { const n = notice ?? (winnerSide === 'CT' ? 'CTs_Win' : 'Terrorists_Win'); const side = winnerSide === 'CT' ? 'CT' : 'TERRORIST'; return `${ts}Team "${side}" triggered "SFUI_Notice_${n}" (CT "${ct}") (T "${t}")`; } test('Logzeilen ergeben Spielstand, Rundenverlauf und Gründe', () => { let state = initialLiveState({ map: 'de_mirage', ctTeam: 'A' }); const lines = [ `${ts}World triggered "Match_Start" on "de_mirage"`, round('T', 0, 1, 'Target_Bombed'), round('CT', 1, 1, 'Bomb_Defused'), round('CT', 2, 1, 'Target_Saved'), ]; const { state: next, events } = applyLogLines(state, lines, ctx); state = next; assert.equal(state.status, 'live'); assert.deepEqual(state.score, { A: 2, B: 1 }); assert.deepEqual(state.rounds.map((r) => [r.winner, r.reason]), [['B', 'bomb'], ['A', 'defuse'], ['A', 'time']]); assert.equal(events.filter((e) => e.type === 'round').length, 3); }); test('Doppelt gesendete Zeilen werden nur einmal gezählt', () => { const state = initialLiveState({ map: 'de_nuke', ctTeam: 'A' }); const line = round('CT', 1, 0); const { state: s } = applyLogLines(state, [line, line, line], ctx); assert.equal(s.rounds.length, 1); assert.deepEqual(s.score, { A: 1, B: 0 }); }); test('Seitenwechsel über "Team playing"-Zeilen', () => { let s = initialLiveState({ map: 'de_nuke', ctTeam: 'A' }); ({ state: s } = applyLogLines(s, [ `${ts}MatchStatus: Team playing "CT": MaxLAN Blau`, `${ts}MatchStatus: Team playing "TERRORIST": MaxLAN Rot`, `${ts}World triggered "Match_Start" on "de_nuke"`, round('CT', 1, 0), // Halbzeit: Rot ist jetzt CT. In den Seitenwerten steht der Stand der Teams auf der jeweiligen Seite. `${ts}MatchStatus: Team playing "CT": MaxLAN Rot`, `${ts}MatchStatus: Team playing "TERRORIST": MaxLAN Blau`, round('CT', 1, 1), ], ctx)); assert.deepEqual(s.score, { A: 1, B: 1 }); assert.deepEqual(s.rounds.map((r) => r.winner), ['A', 'B']); }); test('Ohne "Team playing": Halbzeitwechsel nach Regelwerk', () => { let s = initialLiveState({ map: 'de_inferno', ctTeam: 'B' }); const small = { ...ctx, maxRounds: 4 }; ({ state: s } = applyLogLines(s, [ `${ts}World triggered "Match_Start" on "de_inferno"`, round('CT', 1, 0), // B gewinnt als CT round('CT', 2, 0), // B gewinnt als CT -> Halbzeit, B ist jetzt T round('T', 0, 3), // B gewinnt als T: CT-Seite (A) hat 0, T-Seite (B) hat 3 ], small)); assert.deepEqual(s.score, { A: 0, B: 3 }); assert.deepEqual(s.rounds.map((r) => r.winner), ['B', 'B', 'B']); }); test('Game Over beendet die Map, Aufwärmrunden vor Match_Start zählen nicht', () => { let s = initialLiveState({ map: 'de_dust2', ctTeam: 'A' }); ({ state: s } = applyLogLines(s, [ round('T', 0, 1), // Aufwärm-/Messerrunde `${ts}World triggered "Match_Start" on "de_dust2"`, round('CT', 1, 0), `${ts}Game Over: competitive mg_active de_dust2 score 1:0 after 2 min`, round('T', 1, 1), // nach Spielende ignoriert ], ctx)); assert.equal(s.status, 'finished'); assert.equal(s.winner, 'A'); assert.deepEqual(s.score, { A: 1, B: 0 }); }); test('Zeilen einer anderen Map werden ignoriert, Serienstand wird gezählt', () => { const s = initialLiveState({ map: 'de_dust2', ctTeam: 'A' }); const { state } = applyLogLines(s, [`${ts}Game Over: competitive mg_active de_nuke score 13:2 after 30 min`], ctx); assert.equal(state.status, 'warmup'); assert.deepEqual(seriesScore([{ winner: 'A' }, { winner: 'B' }, { winner: 'A' }, { winner: null }]), { A: 2, B: 1 }); assert.deepEqual(splitLogBody('a\r\n\r\nb\n'), ['a', 'b']); });