Files
Cs2-Mapveto/server/test/rounds.test.js
SPP UserandClaude Haiku 5.5 a4218c47d1 Rollback: Runden aus CS2-Sicherungen zurückspielen (nur Admin)
Beim Konfigurieren aktiviert CS2 Mapveto die Rundensicherung des Servers
(mp_backup_round_auto, Präfix mit Match-ID). Im Admin-Modus lassen sich
die Sicherungen der laufenden Map auflisten und zurückspielen. Der
Live-Stand wird auf die Runden bis zur Sicherung gekürzt. Organisatoren
haben keinen Zugang zu den Rollback-Routen.

Sicherheit: Dateinamen werden gegen Match-ID und Muster geprüft, damit
keine fremden Dateien oder Befehle in die RCON-Zeile gelangen.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-25 23:24:49 +01:00

55 lines
2.6 KiB
JavaScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { backupPrefix, describeBackup, isValidBackupFile, parseBackupFiles, pointsForMap, trimLiveState } from '../src/rounds.js';
const id = 'Ab_9-xY';
const file = (map, round, s1, s2) => `${backupPrefix(id)}_2026-10-25_18-30-00_${map}_round${round}_score_${s1}_${s2}.txt`;
test('Gültige Sicherungsdateien nur für dieses Match', () => {
assert.equal(isValidBackupFile(file('de_inferno', 3, 2, 1), id), true);
assert.equal(isValidBackupFile(file('de_inferno', 3, 2, 1), 'anderes'), false, 'fremdes Match');
assert.equal(isValidBackupFile(`${backupPrefix(id)}_../../etc/passwd.txt`, id), false, 'Pfad');
assert.equal(isValidBackupFile(`${backupPrefix(id)}_x;quit_round1_score_0_0.txt`, id), false, 'Befehl');
assert.equal(isValidBackupFile(`${backupPrefix(id)}_de_inferno.txt`, id), false, 'ohne Runde');
assert.equal(isValidBackupFile(undefined, id), false);
});
test('Liste aus einer RCON-Antwort auslesen, fremde Dateien ignorieren', () => {
const text = [
'Backups:',
file('de_inferno', 1, 1, 0),
file('de_inferno', 2, 1, 1),
'round_backup_fremd_2026_de_nuke_round4_score_3_1.txt',
file('de_nuke', 1, 1, 0),
].join('\n');
const found = parseBackupFiles(text, id);
assert.equal(found.length, 3);
assert.deepEqual(found[0], { file: file('de_inferno', 1, 1, 0), map: 'de_inferno', round: 1, score: { s1: 1, s2: 0 } });
});
test('Nur die Map der laufenden Serie, neueste Runde zuerst', () => {
const all = parseBackupFiles([file('de_inferno', 1, 1, 0), file('de_inferno', 3, 2, 1), file('de_nuke', 2, 1, 1)].join('\n'), id);
const points = pointsForMap(all, 'de_inferno');
assert.deepEqual(points.map((p) => p.round), [3, 1]);
});
test('Stand nach Rollback: Runden bis zur Sicherung, Punkte neu gerechnet', () => {
const state = {
map: 'de_inferno', status: 'live', warmupEnded: true, score: { A: 3, B: 2 }, winner: 'A', pendingRound: null,
rounds: [
{ number: 1, winner: 'A' }, { number: 2, winner: 'B' }, { number: 3, winner: 'A' },
{ number: 4, winner: 'B' }, { number: 5, winner: 'A' },
],
};
const back = trimLiveState(state, 3);
assert.deepEqual(back.rounds.map((r) => r.number), [1, 2, 3]);
assert.deepEqual(back.score, { A: 2, B: 1 });
assert.equal(back.winner, null);
assert.equal(back.status, 'live');
assert.equal(back.warmupEnded, true, 'Zustand bleibt erhalten');
});
test('Beschreibung eines Dateinamens', () => {
assert.deepEqual(describeBackup('nichts.txt'), { file: 'nichts.txt', map: null, round: null, score: null });
});