import { test } from 'node:test'; import assert from 'node:assert/strict'; import net from 'node:net'; import { decodePackets, encodePacket, parseStatus, runRcon } from '../src/servers/rcon.js'; import { buildMapCommands, sanitizeConsoleText } from '../src/servers/cs2Config.js'; /** Minimaler Source-RCON-Server für Tests. */ function fakeRconServer(password, onCommand) { const server = net.createServer((socket) => { let buffer = Buffer.alloc(0); socket.on('data', (chunk) => { buffer = Buffer.concat([buffer, chunk]); const { packets, rest } = decodePackets(buffer); buffer = rest; for (const p of packets) { if (p.type === 3) { socket.write(encodePacket(p.body === password ? p.id : -1, 2, '')); } else { socket.write(encodePacket(p.id, 0, onCommand(p.body))); } } }); }); return new Promise((resolve) => server.listen(0, '127.0.0.1', () => resolve(server))); } test('Paket-Kodierung ist umkehrbar', () => { const buf = Buffer.concat([encodePacket(7, 2, 'status'), encodePacket(8, 2, 'mp_warmup_end')]); const { packets, rest } = decodePackets(buf); assert.deepEqual(packets.map((p) => [p.id, p.body]), [[7, 'status'], [8, 'mp_warmup_end']]); assert.equal(rest.length, 0); const partial = decodePackets(buf.subarray(0, 10)); assert.equal(partial.packets.length, 0); }); test('RCON-Client authentifiziert und führt Befehle aus', async () => { const seen = []; const server = await fakeRconServer('geheim', (cmd) => { seen.push(cmd); return cmd === 'status' ? 'map : de_mirage\nplayers : 3 humans, 0 bots (10 max)' : ''; }); const { port } = server.address(); try { const [out] = await runRcon({ host: '127.0.0.1', port, password: 'geheim' }, ['status', 'mp_warmup_end']); assert.deepEqual(parseStatus(out), { map: 'de_mirage', playerCount: 3 }); assert.deepEqual(seen, ['status', 'mp_warmup_end']); await assert.rejects(runRcon({ host: '127.0.0.1', port, password: 'falsch' }, ['status']), /abgelehnt/); } finally { server.close(); } }); test('Konsolentexte können keine Befehle einschleusen', () => { assert.equal(sanitizeConsoleText('Team"; rcon_password x'), 'Team rcon_password x'); const { beforeMapChange, afterMapChange } = buildMapCommands({ match: { team_a_name: 'Alpha";quit', team_b_name: 'Bravo' }, pool: { mode: 'wingman', gameType: 0, gameMode: 2 }, seriesMap: { map: 'de_nuke', ctTeam: 'B' }, secrets: { serverPassword: 'abc', tvPassword: 'def' }, settings: { tvEnabled: true, tvDelaySeconds: 90, maxRoundsWingman: 16, maxRoundsCompetitive: 24, overtime: true, extraCommands: [] }, }); assert.ok(beforeMapChange.includes('game_mode 2')); assert.equal(beforeMapChange.at(-1), 'changelevel de_nuke'); assert.ok(afterMapChange.includes('mp_teamname_1 "Bravo"')); assert.ok(afterMapChange.includes('mp_teamname_2 "Alphaquit"')); assert.ok(afterMapChange.includes('mp_maxrounds 16')); });