import { test } from 'node:test'; import assert from 'node:assert/strict'; import { DockerServerAdapter } from '../src/servers/dockerAdapter.js'; const options = { bin: 'docker', image: 'joedwards32/cs2:latest', host: '192.168.178.111', rconHost: '192.168.178.111', portRangeStart: 27100, portRangeEnd: 27139, gslt: '', volume: 'cs2-mapveto-gamefiles', extraArgs: '', startTimeoutMinutes: 1, }; const secrets = { rconPassword: 'Rcon123', serverPassword: 'Pw123', tvPassword: 'Tv123' }; /** Simuliertes Docker: merkt sich Aufrufe; Container gelten als "Update fertig", sobald freigegeben. */ function fakeDocker() { const calls = []; const updated = new Set(); const run = async (args) => { calls.push(args); const [cmd] = args; const name = args.at(-1); if (cmd === 'inspect') return 'running'; if (cmd === 'logs') return updated.has(name) ? 'Success! App \'730\' already up to date.\nStarting CS2 Dedicated Server' : 'Checking for available updates...'; return ''; }; return { run, calls, finishUpdate: (name) => updated.add(name) }; } const envOf = (args) => Object.fromEntries(args.flatMap((a, i) => (args[i - 1] === '-e' ? [a.split(/=(.*)/s).slice(0, 2)] : []))); test('Ohne GSLT startet der Container im LAN-Modus, mit GSLT mit Token', async () => { const lan = fakeDocker(); const a = new DockerServerAdapter(options, { getUsedPorts: () => new Set(), runDocker: lan.run }); a.isReachable = async () => false; const r = await a.start({ matchId: 'abc_DEF-1', mode: 'wingman', secrets }); lan.finishUpdate(r.handle.container); const runArgs = lan.calls.find((c) => c[0] === 'run'); const env = envOf(runArgs); assert.equal(env.CS2_LAN, '1'); assert.equal(env.SRCDS_TOKEN, undefined); assert.equal(env.CS2_GAMEMODE, '2'); assert.equal(env.CS2_MAXPLAYERS, '6', '4 Spieler + CSTV + Reserve'); assert.equal(env.CS2_ADDITIONAL_ARGS, '-allow_round_backup_files +sv_deltaticks_enforce 0', 'Rundensicherung für Rollback, keine Trennung wegen Delta-Ticks'); assert.equal(env.CS2_PORT, '27100'); assert.equal(env.TV_PORT, '27200'); assert.ok(runArgs.includes('cs2-mapveto-gamefiles:/home/steam/cs2-dedicated')); assert.equal(r.host, '192.168.178.111'); const tok = fakeDocker(); const b = new DockerServerAdapter({ ...options, gslt: 'TOKEN1' }, { getUsedPorts: () => new Set(), runDocker: tok.run }); b.isReachable = async () => false; const r2 = await b.start({ matchId: 'x', mode: 'competitive', secrets }); tok.finishUpdate(r2.handle.container); const env2 = envOf(tok.calls.find((c) => c[0] === 'run')); assert.equal(env2.SRCDS_TOKEN, 'TOKEN1'); assert.equal(env2.CS2_LAN, '0'); }); test('Zweiter Container startet erst, wenn der erste seinen Update-Check abgeschlossen hat', async () => { const d = fakeDocker(); const used = new Set(); const a = new DockerServerAdapter(options, { getUsedPorts: () => used, runDocker: d.run }); // Wartezeit zwischen Prüfungen für den Test verkürzen const original = a.waitForUpdateCheck.bind(a); a.waitForUpdateCheck = (handle) => original(handle, { pollMs: 10 }); a.isReachable = async () => false; const first = await a.start({ matchId: 'm1', mode: 'competitive', secrets }); used.add(first.port).add(first.tvPort); let secondDone = false; const second = a.start({ matchId: 'm2', mode: 'competitive', secrets }).then((r) => { secondDone = true; return r; }); await new Promise((r) => setTimeout(r, 60)); assert.equal(secondDone, false, 'zweiter Start wartet noch'); assert.equal(d.calls.filter((c) => c[0] === 'run').length, 1); d.finishUpdate(first.handle.container); const r2 = await second; assert.equal(d.calls.filter((c) => c[0] === 'run').length, 2); assert.equal(r2.port, 27101, 'zweiter Container bekommt den nächsten Port'); d.finishUpdate(r2.handle.container); }); test('Warteschlange gibt frei, sobald der Server per RCON antwortet – auch ohne Startzeile im Log', async () => { const d = fakeDocker(); const used = new Set(); const a = new DockerServerAdapter(options, { getUsedPorts: () => used, runDocker: d.run }); const original = a.waitForUpdateCheck.bind(a); a.waitForUpdateCheck = (handle) => original(handle, { pollMs: 10 }); const reachable = new Set(); a.isReachable = async (handle) => reachable.has(handle.container); const first = await a.start({ matchId: 'r1', mode: 'wingman', secrets }); used.add(first.port).add(first.tvPort); const second = a.start({ matchId: 'r2', mode: 'wingman', secrets }); await new Promise((r) => setTimeout(r, 50)); assert.equal(d.calls.filter((c) => c[0] === 'run').length, 1, 'wartet noch'); reachable.add(first.handle.container); // Log ohne Startzeile, aber RCON antwortet const r2 = await second; assert.equal(d.calls.filter((c) => c[0] === 'run').length, 2); reachable.add(r2.handle.container); });