Die Testdatei enthielt Spielernamen und SteamID eines echten Accounts. Sie ist jetzt durch eine Kopie mit Platzhaltern ersetzt, der Parser-Test prüft weiterhin echte CS2-Logzeilen.
135 lines
6.2 KiB
JavaScript
135 lines
6.2 KiB
JavaScript
import { test } from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import fs from 'node:fs';
|
||
import path from 'node:path';
|
||
import { fileURLToPath } from 'node:url';
|
||
import { applyLogLines, initialLiveState, seriesScore, splitLogBody } from '../src/liveScore.js';
|
||
|
||
const here = path.dirname(fileURLToPath(import.meta.url));
|
||
|
||
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', warmupEnded: true });
|
||
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', warmupEnded: true });
|
||
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('Seitenzuordnung nach dem Rundenende hat Vorrang, veraltete Zeilen zu Rundenbeginn zählen nicht', () => {
|
||
let s = initialLiveState({ map: 'de_nuke', ctTeam: 'B', warmupEnded: true });
|
||
const playing = (ct, t) => [`${ts}MatchStatus: Team playing "CT": ${ct}`, `${ts}MatchStatus: Team playing "TERRORIST": ${t}`];
|
||
({ state: s } = applyLogLines(s, [
|
||
// Veto sagt Rot auf CT, tatsächlich steht Blau auf CT – das Log gewinnt
|
||
`${ts}World triggered "Match_Start" on "de_nuke"`,
|
||
...playing('MaxLAN Blau', 'MaxLAN Rot'),
|
||
round('CT', 1, 0),
|
||
...playing('MaxLAN Blau', 'MaxLAN Rot'),
|
||
`${ts}World triggered "Round_End"`,
|
||
// veraltete Zeile zu Beginn der nächsten Runde (darf nichts ändern)
|
||
...playing('MaxLAN Rot', 'MaxLAN Blau'),
|
||
round('T', 1, 1),
|
||
...playing('MaxLAN Blau', 'MaxLAN Rot'),
|
||
`${ts}World triggered "Round_End"`,
|
||
], ctx));
|
||
assert.deepEqual(s.score, { A: 1, B: 1 });
|
||
assert.deepEqual(s.rounds.map((r) => r.winner), ['A', 'B']);
|
||
});
|
||
|
||
test('Echte, anonymisierte Logzeilen eines CS2-Servers (Wingman, 4 Runden, Halbzeit nach 2)', () => {
|
||
const lines = fs.readFileSync(path.join(here, 'fixtures', 'wingman-anonym.log'), 'utf8').split(/\r?\n/).filter(Boolean);
|
||
const warmupEnd = lines.findIndex((l) => l.includes('command "mp_warmup_end"'));
|
||
const ctx = { teamNames: { A: 'MaxLAN Blau', B: 'MaxLAN Rot' }, maxRounds: 4, overtimeRounds: 6 };
|
||
|
||
const check = (s) => {
|
||
assert.equal(s.status, 'finished');
|
||
assert.equal(s.winner, 'A');
|
||
assert.deepEqual(s.score, { A: 3, B: 1 });
|
||
assert.deepEqual(s.rounds.map((r) => r.winner), ['B', 'A', 'A', 'A']);
|
||
assert.ok(s.rounds.every((r) => r.reason === 'elimination'));
|
||
};
|
||
|
||
// a) wie vom Server in Paketen geschickt
|
||
let s = initialLiveState({ map: 'de_inferno', ctTeam: 'A' });
|
||
({ state: s } = applyLogLines(s, lines.slice(0, warmupEnd), ctx));
|
||
assert.equal(s.status, 'warmup');
|
||
s.warmupEnded = true; // Organisator hat "Aufwärmphase beenden" geklickt
|
||
({ state: s } = applyLogLines(s, lines.slice(warmupEnd), ctx));
|
||
check(s);
|
||
|
||
// b) jede Zeile einzeln (ungünstigste Paketgrenzen)
|
||
let t = initialLiveState({ map: 'de_inferno', ctTeam: 'A', warmupEnded: true });
|
||
for (const line of lines.slice(warmupEnd)) ({ state: t } = applyLogLines(t, [line], ctx));
|
||
check(t);
|
||
});
|
||
|
||
test('Ohne "Team playing": Halbzeitwechsel nach Regelwerk', () => {
|
||
let s = initialLiveState({ map: 'de_inferno', ctTeam: 'B', warmupEnded: true });
|
||
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', warmupEnded: true });
|
||
({ 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', warmupEnded: true });
|
||
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']);
|
||
});
|
||
|
||
test('Match_Start vor dem Ende der Aufwärmphase startet keine Zählung', () => {
|
||
let s = initialLiveState({ map: 'de_inferno', ctTeam: 'A' });
|
||
const start = `${ts}World triggered "Match_Start" on "de_inferno"`;
|
||
({ state: s } = applyLogLines(s, [start], ctx));
|
||
assert.equal(s.status, 'warmup');
|
||
s.warmupEnded = true;
|
||
const { state, events } = applyLogLines(s, [start], ctx);
|
||
assert.equal(state.status, 'live');
|
||
assert.equal(events[0].type, 'match_start');
|
||
});
|