Files
Cs2-Mapveto/server/test/dockerAdapter.test.js
T
mregosar e3a1ed4a63 Docker-Warteschlange: Update-Check per RCON statt nur per Logzeile erkennen
Die Zeile 'Starting CS2 Dedicated Server' rutscht nach dem Serverstart
schnell aus den letzten Logzeilen. Der nächste Match-Container wartete
dadurch bis zur Zeitgrenze (20 min). Jetzt gilt ein Container als fertig,
sobald er per RCON antwortet; die Logzeile bleibt als zweite Erkennung.
2026-10-25 22:16:57 +01:00

99 lines
4.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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_PORT, '27100');
assert.equal(env.TV_PORT, '27101');
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, 27102, 'zweiter Container bekommt die nächsten Ports');
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);
});