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'; const here = path.dirname(fileURLToPath(import.meta.url)); const pools = loadMapPools(path.join(here, '..', 'config', 'map-pools.json')); test('alle ausgelieferten Abläufe sind gültig, Wingman-BO5 ist nicht unterstützt', () => { assert.equal(pools.competitive.formats.bo1.supported, true); assert.equal(pools.competitive.formats.bo3.supported, true); assert.equal(pools.competitive.formats.bo5.supported, true); assert.equal(pools.wingman.formats.bo1.supported, true); assert.equal(pools.wingman.formats.bo3.supported, true); assert.equal(pools.wingman.formats.bo5.supported, false); }); test('validateFlow erkennt unpassende Abläufe', () => { assert.deepEqual(validateFlow(['ban:X', 'ban:Y', 'decider'], 3, 'bo1'), []); assert.ok(validateFlow(['ban:X', 'decider'], 3, 'bo1').length > 0, 'zu wenige Bans'); assert.ok(validateFlow(['ban:X', 'ban:Y', 'decider'], 3, 'bo3').length > 0, 'fehlende Picks'); assert.ok(validateFlow(['side:X', 'ban:Y', 'ban:X', 'decider'], 3, 'bo1').length > 0, 'Seitenwahl ohne Pick'); assert.ok(validateFlow(['ban:X', 'decider', 'ban:Y'], 3, 'bo1').length > 0, 'Ban nach Entscheidungsmap'); assert.ok(validateFlow(['foo:X'], 1, 'bo1').length > 0, 'unbekannter Schritt'); }); test('ungültiger Ablauf in der Konfiguration deaktiviert nur dieses Format', () => { const custom = buildMapPools({ pools: { mini: { label: 'Mini', teamSize: 2, gameType: 0, gameMode: 2, maps: [{ id: 'de_a', name: 'A' }, { id: 'de_b', name: 'B' }, { id: 'de_c', name: 'C' }], flows: { bo1: ['ban:X', 'ban:Y', 'decider'], bo3: ['pick:X', 'decider'] }, }, }, }); 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/); }); function play(pool, format, firstTeam, choices) { const ctx = { maps: pool.maps, flow: pool.formats[format].flow, firstTeam, actions: [] }; for (const choice of choices) { const state = computeVetoState(ctx); const added = resolveAction(ctx, { team: state.current.team, stepIndex: state.current.index, ...choice }); ctx.actions = [...ctx.actions, ...added]; } return computeVetoState(ctx); } test('BO3 Competitive: kompletter Ablauf mit Seitenwahl und Entscheidungsmap', () => { const state = play(pools.competitive, 'bo3', 'B', [ { map: 'de_train' }, // B bannt { map: 'de_nuke' }, // A bannt { map: 'de_mirage' }, // B pickt { side: 'CT' }, // A wählt Seite auf Mirage { map: 'de_inferno' }, // A pickt { side: 'T' }, // B wählt Seite auf Inferno { map: 'de_dust2' }, // B bannt { map: 'de_anubis' }, // A bannt -> Ancient bleibt { side: 'CT' }, // B (X) wählt Seite auf der Entscheidungsmap ]); assert.equal(state.done, true); assert.deepEqual(state.series.map((s) => s.map), ['de_mirage', 'de_inferno', 'de_ancient']); assert.equal(state.series[0].pickedBy, 'B'); assert.deepEqual(state.series[0].startSides, { A: 'CT', B: 'T' }); assert.deepEqual(state.series[1].startSides, { B: 'T', A: 'CT' }); assert.equal(state.series[2].pickedBy, null); assert.deepEqual(state.series[2].startSides, { B: 'CT', A: 'T' }); assert.equal(state.maps.find((m) => m.id === 'de_ancient').status, 'decider'); }); test('BO1 Wingman: Entscheidungsmap wird automatisch übernommen', () => { const state = play(pools.wingman, 'bo1', 'A', [{ map: 'de_inferno' }, { map: 'de_nuke' }, { map: 'de_overpass' }]); assert.equal(state.done, false); assert.equal(state.current.type, 'side'); assert.equal(state.current.team, 'B'); assert.equal(state.current.map, 'de_vertigo'); }); test('falsches Team, veraltete Schritte und vergebene Maps werden abgelehnt', () => { const pool = pools.competitive; 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' }); assert.throws(() => resolveAction(ctx, { team: 'A', stepIndex: 0, map: 'de_cache' }), { code: 'invalid_map' }); ctx.actions = resolveAction(ctx, { team: 'A', stepIndex: 0, map: 'de_nuke' }); assert.throws(() => resolveAction(ctx, { team: 'B', stepIndex: 1, map: 'de_nuke' }), { code: 'map_unavailable' }); assert.throws(() => resolveAction(ctx, { team: 'A', stepIndex: 0, map: 'de_train' }), { code: 'stale_step' }); });