Map-Pools: Overpass, Vertigo und Cache für Competitive, Map-Auswahl im Admin, Veto-Ablauf passend zur Map-Anzahl

This commit is contained in:
mregosar committed 2026-10-29 21:03:29 +01:00
1 parent e3834bb071
commit 2d65634d6e
13 files changed
+459 -45

No files matched your search

+33
View File
@@ -574,3 +574,36 @@ test('Messer-Server: nur Admin startet, Startseite sieht laufende, Ports zählen
await close();
}
});
test('Map-Pools im Admin: Auswahl gilt für neue Matches, Veto passt sich an, laufende Matches behalten ihre Maps', async () => {
const { app, call, close } = await setup({ adminPassword: 'admin-geheim' });
try {
const token = (await call('POST', '/admin/login', { body: { password: 'admin-geheim' } })).body.token;
const before = (await call('POST', '/matches', { body: { teamA: 'Vorher A', teamB: 'Vorher B', mode: 'competitive', format: 'bo3' } })).body;
const view = (await call('GET', '/admin/map-pools', { token })).body;
const comp = view.pools.find((p) => p.mode === 'competitive');
assert.ok(['de_overpass', 'de_vertigo', 'de_cache'].every((id) => comp.maps.some((m) => m.id === id && m.active)));
// Nur 5 Maps: BO5 geht noch, BO3 hat dann 2 Bans
const five = ['de_cache', 'de_dust2', 'de_inferno', 'de_mirage', 'de_nuke'];
const saved = await call('PUT', '/admin/map-pools', { token, body: { mode: 'competitive', maps: five } });
assert.equal(saved.status, 200, JSON.stringify(saved.body));
const pool = saved.body.pools.find((p) => p.mode === 'competitive');
assert.equal(pool.maps.filter((m) => m.active).length, 5);
assert.deepEqual(pool.formats.bo3.flow, ['ban:X', 'ban:Y', 'pick:X', 'side:Y', 'pick:Y', 'side:X', 'decider', 'side:X']);
// Neue Matches bekommen die neue Auswahl, das alte behält seine 10 Maps
const after = (await call('POST', '/matches', { body: { teamA: 'Nachher A', teamB: 'Nachher B', mode: 'competitive', format: 'bo3' } })).body;
assert.equal(JSON.parse(app.service.getMatch(after.id).maps).length, 5);
assert.equal(JSON.parse(app.service.getMatch(before.id).maps).length, 10);
// Öffentliche Konfiguration (Match erstellen) zeigt die neuen Formate
const cfg = (await call('GET', '/config')).body;
assert.equal(cfg.pools.find((p) => p.mode === 'competitive').maps.length, 5);
// Ungültig: zu wenige oder unbekannte Maps
assert.equal((await call('PUT', '/admin/map-pools', { token, body: { mode: 'competitive', maps: ['de_cache'] } })).status, 400);
assert.equal((await call('PUT', '/admin/map-pools', { token, body: { mode: 'competitive', maps: ['de_cache', 'de_foo'] } })).status, 400);
assert.equal((await call('PUT', '/admin/map-pools', { body: { mode: 'competitive', maps: five } })).status, 401);
} finally {
await close();
}
});
+43 -6
View File
@@ -2,11 +2,14 @@ import { test } from 'node:test';
import assert from 'node:assert/strict';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { computeVetoState, resolveAction, validateFlow } from '../src/veto.js';
import { buildMapPools, loadMapPools } from '../src/mapPools.js';
import { computeVetoState, generateFlow, resolveAction, validateFlow } from '../src/veto.js';
import { buildMapPools, loadMapPools, setActiveMaps } from '../src/mapPools.js';
const here = path.dirname(fileURLToPath(import.meta.url));
const pools = loadMapPools(path.join(here, '..', 'config', 'map-pools.json'));
// Die klassischen 7 Competitive-Maps: dafür gilt der fest hinterlegte Ablauf
const SEVEN = ['de_ancient', 'de_anubis', 'de_dust2', 'de_inferno', 'de_mirage', 'de_nuke', 'de_train'];
const competitive7 = () => setActiveMaps(structuredClone(pools.competitive), SEVEN);
test('alle ausgelieferten Abläufe sind gültig, Wingman-BO5 ist nicht unterstützt', () => {
assert.equal(pools.competitive.formats.bo1.supported, true);
@@ -37,8 +40,42 @@ test('ungültiger Ablauf in der Konfiguration deaktiviert nur dieses Format', ()
},
});
assert.equal(custom.mini.formats.bo1.supported, true);
assert.equal(custom.mini.formats.bo3.supported, false);
assert.match(custom.mini.formats.bo5.reason, /Kein Veto-Ablauf/);
assert.equal(custom.mini.formats.bo1.generated, false, 'passender fester Ablauf wird genommen');
// Ungültiger fester Ablauf: die Anwendung erzeugt einen passenden
assert.equal(custom.mini.formats.bo3.supported, true);
assert.equal(custom.mini.formats.bo3.generated, true);
assert.match(custom.mini.formats.bo5.reason, /mindestens 5 Maps/);
});
test('Erzeugte Abläufe sind für jede Map-Anzahl gültig', () => {
for (let n = 2; n <= 12; n += 1) {
for (const format of ['bo1', 'bo3', 'bo5']) {
const flow = generateFlow(n, format);
const need = { bo1: 2, bo3: 3, bo5: 5 }[format];
if (n < need) {
assert.equal(flow, null, `${format} mit ${n} Maps`);
continue;
}
assert.deepEqual(validateFlow(flow, n, format), [], `${format} mit ${n} Maps: ${flow.join(' ')}`);
}
}
// Bans und Picks wechseln sich ab, die Seite der Entscheidungsmap wählt das Team, das nicht zuletzt dran war
assert.deepEqual(generateFlow(5, 'bo3'), ['ban:X', 'ban:Y', 'pick:X', 'side:Y', 'pick:Y', 'side:X', 'decider', 'side:X']);
assert.deepEqual(generateFlow(3, 'bo1'), ['ban:X', 'ban:Y', 'decider', 'side:X']);
});
test('Aktive Maps wählen: Formate passen sich an, Reihenfolge bleibt wie im Katalog', () => {
const pool = structuredClone(pools.competitive);
setActiveMaps(pool, ['de_vertigo', 'de_cache', 'de_mirage', 'de_unbekannt']);
assert.deepEqual(pool.maps.map((m) => m.id), ['de_cache', 'de_mirage', 'de_vertigo']);
assert.equal(pool.formats.bo1.supported, true);
assert.equal(pool.formats.bo3.supported, true);
assert.equal(pool.formats.bo5.supported, false);
// 10 Maps (alle): kein fester Ablauf passt, es wird erzeugt
assert.equal(pools.competitive.maps.length, 10);
assert.equal(pools.competitive.formats.bo3.generated, true);
// 7 klassische Maps: fester Ablauf aus der Konfiguration
assert.equal(competitive7().formats.bo3.generated, false);
});
function play(pool, format, firstTeam, choices) {
@@ -52,7 +89,7 @@ function play(pool, format, firstTeam, choices) {
}
test('BO3 Competitive: kompletter Ablauf mit Seitenwahl und Entscheidungsmap', () => {
const state = play(pools.competitive, 'bo3', 'B', [
const state = play(competitive7(), 'bo3', 'B', [
{ map: 'de_train' }, // B bannt
{ map: 'de_nuke' }, // A bannt
{ map: 'de_mirage' }, // B pickt
@@ -82,7 +119,7 @@ test('BO1 Wingman: Entscheidungsmap wird automatisch übernommen', () => {
});
test('falsches Team, veraltete Schritte und vergebene Maps werden abgelehnt', () => {
const pool = pools.competitive;
const pool = competitive7();
const ctx = { maps: pool.maps, flow: pool.formats.bo1.flow, firstTeam: 'A', actions: [] };
assert.throws(() => resolveAction(ctx, { team: 'B', stepIndex: 0, map: 'de_nuke' }), { code: 'not_your_turn' });
assert.throws(() => resolveAction(ctx, { team: 'A', stepIndex: 1, map: 'de_nuke' }), { code: 'stale_step' });