Messer-Server für die Seitenwahl: 5v5/2v2, im Admin startbar, setzen sich nach der Runde selbst zurück
This commit is contained in:
1 parent
0bfac952b6
commit
c6c39b914f
15 files changed
+717
-14
No files matched your search
@@ -545,3 +545,32 @@ test('Archiv: nur abgeschlossene Matches, eigene Liste, automatisch nach Frist',
|
||||
await close();
|
||||
}
|
||||
});
|
||||
test('Messer-Server: nur Admin startet, Startseite sieht laufende, Ports zählen bei der Vergabe mit', async () => {
|
||||
const { app, call, close } = await setup({ adminPassword: 'admin-geheim' });
|
||||
try {
|
||||
assert.equal((await call('POST', '/admin/knife', { body: {} })).status, 401);
|
||||
const token = (await call('POST', '/admin/login', { body: { password: 'admin-geheim' } })).body.token;
|
||||
const info = (await call('GET', '/admin/knife', { token })).body;
|
||||
assert.deepEqual(info.modes.map((m) => m.id).sort(), ['competitive', 'wingman']);
|
||||
const created = await call('POST', '/admin/knife', { token, body: { mode: 'competitive', map: 'de_inferno' } });
|
||||
assert.equal(created.status, 200, JSON.stringify(created.body));
|
||||
|
||||
// Noch nicht bereit: Startseite zeigt ihn nicht
|
||||
assert.equal((await call('GET', '/knife-servers')).body.servers.length, 0);
|
||||
await app.knife.poll();
|
||||
await waitFor(() => app.knife.row(created.body.id).status === 'running', { label: 'Messer-Server bereit' });
|
||||
const pub = (await call('GET', '/knife-servers')).body.servers;
|
||||
assert.equal(pub.length, 1);
|
||||
assert.equal(pub[0].map, 'de_inferno');
|
||||
assert.ok(!JSON.stringify(pub).includes(app.knife.row(created.body.id).rcon_password));
|
||||
|
||||
// Ports des Messer-Servers sind für Matches belegt
|
||||
const row = app.knife.row(created.body.id);
|
||||
assert.ok(app.manager.usedPortsOn(row.host).has(row.port));
|
||||
|
||||
assert.equal((await call('DELETE', `/admin/knife/${created.body.id}`, { token })).status, 200);
|
||||
assert.equal(app.knife.list().length, 0);
|
||||
} finally {
|
||||
await close();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,125 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { loadConfig } from '../src/config.js';
|
||||
import { openDatabase } from '../src/db.js';
|
||||
import { buildKnifeCommands, KnifeServers } from '../src/servers/knifeServers.js';
|
||||
|
||||
const pools = {
|
||||
competitive: { label: 'Competitive 5 gegen 5', gameType: 0, gameMode: 1, teamSize: 5, maps: [{ id: 'de_mirage', name: 'Mirage' }, { id: 'de_inferno', name: 'Inferno' }] },
|
||||
wingman: { label: 'Wingman 2 gegen 2', gameType: 0, gameMode: 2, teamSize: 2, maps: [{ id: 'de_vertigo', name: 'Vertigo' }] },
|
||||
};
|
||||
const quiet = { log() {}, warn() {}, error() {} };
|
||||
|
||||
/** Adapter, dessen Zustand der Test steuert. */
|
||||
function fakeAdapter() {
|
||||
const a = {
|
||||
name: 'docker',
|
||||
starts: [],
|
||||
configures: [],
|
||||
commands: [],
|
||||
stops: 0,
|
||||
state: 'starting',
|
||||
players: null,
|
||||
async start(args) {
|
||||
a.starts.push(args);
|
||||
return { handle: { container: `cs2mv-${args.matchId}`, n: a.starts.length }, host: '10.0.0.5', port: 27015 + a.starts.length - 1, tvPort: 27115 };
|
||||
},
|
||||
async status() { return { state: a.state, playerCount: a.players }; },
|
||||
async configure(handle, plan) { a.configures.push(plan); },
|
||||
async sendCommands(handle, cmds) { a.commands.push(...cmds); },
|
||||
async stop() { a.stops += 1; },
|
||||
};
|
||||
return a;
|
||||
}
|
||||
|
||||
function setup() {
|
||||
const config = loadConfig({ knife: { map: 'de_mirage', warmupSeconds: 60, maxServers: 2 }, docker: { serverNamePrefix: 'MaxLAN' } });
|
||||
const adapter = fakeAdapter();
|
||||
const knife = new KnifeServers({ db: openDatabase(':memory:'), adapter, config, pools, logger: quiet });
|
||||
return { knife, adapter };
|
||||
}
|
||||
const poll = async (knife) => { knife.lastPoll.clear(); await knife.poll(); };
|
||||
|
||||
test('Messerrunde: nur Messer, eine Runde, danach automatisch neues Spiel', () => {
|
||||
const plan = buildKnifeCommands({ map: 'de_inferno', warmupSeconds: 45 });
|
||||
assert.equal(plan.beforeMapChange.at(-1), 'changelevel de_inferno');
|
||||
assert.ok(plan.beforeMapChange.includes('sv_password ""'), 'offen, ohne Passwort');
|
||||
for (const c of ['mp_maxrounds 1', 'mp_match_end_restart 1', 'mp_ct_default_secondary ""', 'mp_startmoney 0', 'mp_warmuptime 45']) {
|
||||
assert.ok(plan.afterMapChange.includes(c), `${c} fehlt`);
|
||||
}
|
||||
});
|
||||
|
||||
test('Start, Konfiguration und Ansicht ohne Geheimnisse', async () => {
|
||||
const { knife, adapter } = setup();
|
||||
const created = await knife.create({});
|
||||
assert.equal(created.name, 'Messer 1');
|
||||
assert.equal(created.status, 'starting');
|
||||
assert.equal(adapter.starts[0].serverName, 'MaxLAN | Messer 1 5v5 (Seitenwahl)');
|
||||
assert.equal(adapter.starts[0].mode, 'competitive');
|
||||
assert.equal(adapter.starts[0].secrets.serverPassword, '');
|
||||
|
||||
adapter.state = 'running';
|
||||
await poll(knife);
|
||||
assert.equal(knife.row('knife1').status, 'running');
|
||||
assert.equal(adapter.configures[0].map, 'de_mirage');
|
||||
|
||||
const [view] = knife.list();
|
||||
assert.equal(view.connect, 'connect 10.0.0.5:27015');
|
||||
assert.ok(!JSON.stringify(view).includes(knife.row('knife1').rcon_password), 'kein RCON-Passwort in der Ansicht');
|
||||
});
|
||||
|
||||
test('Wingman 2v2: eigener Spielmodus, Maps nur aus dem Wingman-Pool', async () => {
|
||||
const { knife, adapter } = setup();
|
||||
const k = await knife.create({ mode: 'wingman' });
|
||||
assert.equal(k.modeShort, '2v2');
|
||||
assert.equal(k.map, 'de_vertigo', 'Standard-Map gibt es im Wingman-Pool nicht, also die erste');
|
||||
assert.equal(adapter.starts[0].mode, 'wingman');
|
||||
assert.equal(adapter.starts[0].serverName, 'MaxLAN | Messer 1 2v2 (Seitenwahl)');
|
||||
adapter.state = 'running';
|
||||
await poll(knife);
|
||||
assert.ok(adapter.configures[0].beforeMapChange.includes('game_mode 2'));
|
||||
await assert.rejects(knife.create({ mode: 'wingman', map: 'de_mirage' }), /gibt es nicht für Wingman/);
|
||||
await assert.rejects(knife.create({ mode: 'deathmatch' }), /Unbekannter Modus/);
|
||||
});
|
||||
|
||||
test('Grenzen: nur Maps aus den Pools, höchstens maxServers', async () => {
|
||||
const { knife } = setup();
|
||||
await assert.rejects(knife.create({ map: 'de_unbekannt' }), /gibt es nicht/);
|
||||
await knife.create({ map: 'de_inferno' });
|
||||
await knife.create({});
|
||||
await assert.rejects(knife.create({}), /Höchstens 2/);
|
||||
// Freie Nummer wird wiederverwendet
|
||||
await knife.remove('knife1');
|
||||
assert.equal((await knife.create({})).name, 'Messer 1');
|
||||
});
|
||||
|
||||
test('Teams gehen runter: Server setzt sich für die nächsten zurück', async () => {
|
||||
const { knife, adapter } = setup();
|
||||
await knife.create({});
|
||||
adapter.state = 'running';
|
||||
await poll(knife);
|
||||
adapter.players = 4;
|
||||
await poll(knife);
|
||||
assert.equal(adapter.commands.length, 0);
|
||||
adapter.players = 0;
|
||||
await poll(knife);
|
||||
assert.deepEqual(adapter.commands, ['mp_warmup_start']);
|
||||
assert.match(knife.row('knife1').message, /zurückgesetzt/);
|
||||
});
|
||||
|
||||
test('Ausfall: Server startet neu, nach zu vielen Ausfällen gibt die App auf', async () => {
|
||||
const { knife, adapter } = setup();
|
||||
await knife.create({});
|
||||
adapter.state = 'failed';
|
||||
await poll(knife);
|
||||
assert.equal(adapter.starts.length, 2, 'nach Ausfall neu gestartet');
|
||||
assert.equal(knife.row('knife1').restarts, 1);
|
||||
for (let i = 0; i < 6; i++) await poll(knife);
|
||||
assert.equal(adapter.starts.length, 6, 'höchstens 5 Neustarts');
|
||||
assert.equal(knife.row('knife1').status, 'failed');
|
||||
// Neustart durch den Admin setzt den Zähler zurück
|
||||
adapter.state = 'starting';
|
||||
await knife.restart('knife1');
|
||||
assert.equal(knife.row('knife1').restarts, 0);
|
||||
assert.equal(adapter.starts.length, 7);
|
||||
});
|
||||
Reference in new issue
Block a user