import { describe, expect, it } from 'vitest'; import { composeCfg, composeServerCfg, parseCfg, parseOverlay } from '../../src/lib/server/fleet/config-compose.js'; import { T5_ZOMBIE_MAPS } from '../../src/lib/server/fleet/map-aliases.js'; const BASELINE = [ '// GENERAL', 'set g_inactivity "190"', 'set sv_voice "1"', '', '// GAMEPLAY', 'set g_playerCollision "0"', 'set sv_maprotation "map zombie_theater"' ].join('\n'); describe('parseCfg', () => { it('captures set keys and preserves comments/blanks', () => { const p = parseCfg(BASELINE); expect(p.entries.get('g_inactivity')).toBe(1); expect(p.entries.get('sv_voice')).toBe(2); expect(p.entries.get('sv_maprotation')).toBe(6); expect(p.order[0]).toEqual({ kind: 'raw', line: '// GENERAL' }); expect(p.order[3]).toEqual({ kind: 'raw', line: '' }); }); it('detects CRLF line endings', () => { const crlf = 'set a "1"\r\nset b "2"\r\n'; expect(parseCfg(crlf).eol).toBe('\r\n'); }); }); describe('parseOverlay', () => { it('reads set lines and ignores comments/blanks', () => { const overlay = '// disable AFK\nset g_inactivity "0"\n'; const kv = parseOverlay(overlay); expect(kv.size).toBe(1); expect(kv.get('g_inactivity')).toBe('set g_inactivity "0"'); }); it('handles bare (unquoted) values', () => { const overlay = 'set g_logSync 1'; const kv = parseOverlay(overlay); expect(kv.get('g_logSync')).toBe('set g_logSync 1'); }); it('later duplicate wins within an overlay', () => { const overlay = 'set x "1"\nset x "2"'; expect(parseOverlay(overlay).get('x')).toBe('set x "2"'); }); }); describe('composeCfg', () => { it('replaces matching keys in place', () => { const result = composeCfg(BASELINE, [{ id: 1, text: 'set g_inactivity "0"' }]); expect(result).toContain('set g_inactivity "0"'); expect(result).not.toContain('set g_inactivity "190"'); // Structure preserved: sv_voice should still be at its original spot. const lines = result.split('\n'); expect(lines[1]).toBe('set g_inactivity "0"'); expect(lines[2]).toBe('set sv_voice "1"'); }); it('appends new keys not present in baseline', () => { const result = composeCfg(BASELINE, [{ id: 1, text: 'set new_flag "yes"' }]); expect(result).toContain('set new_flag "yes"'); expect(result).toContain('// --- appended by electric-cherry overlays ---'); }); it('applies multiple overlays deterministically by id', () => { const result = composeCfg(BASELINE, [ { id: 2, text: 'set g_inactivity "60"' }, { id: 1, text: 'set g_inactivity "0"' } ]); // id=2 is later → wins expect(result).toContain('set g_inactivity "60"'); expect(result).not.toContain('set g_inactivity "0"'); }); it('preserves inline comments on the overlay line', () => { const result = composeCfg(BASELINE, [ { id: 1, text: 'set g_inactivity "0" // AFK disabled' } ]); expect(result).toContain('set g_inactivity "0" // AFK disabled'); }); it('is a no-op when no overlays are given', () => { const result = composeCfg(BASELINE, []); expect(result.trim()).toBe(BASELINE.trim()); }); }); describe('automatic map selection', () => { it.each(T5_ZOMBIE_MAPS)('selects $friendly without a map overlay', ({ canonical }) => { const result = composeServerCfg(BASELINE, [], 't5', canonical); expect(result).toContain(`set sv_maprotation "map ${canonical}"`); expect(result.match(/^set sv_maprotation /gm)).toHaveLength(1); expect(result).toContain('set sv_voice "1"'); }); it('overrides legacy map-choice overlays while keeping other settings', () => { const result = composeServerCfg(BASELINE, [{ id: 5, text: 'set sv_maprotation "map zombie_moon"\nset g_inactivity "0"' }], 't5', 'zombie_cosmodrome'); expect(result).toContain('set sv_maprotation "map zombie_cosmodrome"'); expect(result).not.toContain('map zombie_moon'); expect(result).toContain('set g_inactivity "0"'); }); it('appends the rotation when absent and preserves CRLF', () => { expect(composeServerCfg('set sv_voice "1"\r\n', [], 't5', 'zombietron')) .toBe('set sv_voice "1"\r\n// Map selected by Electric Cherry\r\nset sv_maprotation "map zombietron"\r\n'); }); it('removes conflicting duplicate assignments regardless of case or command form', () => { const result = composeServerCfg([ '// keep this comment', 'set sv_maprotation "map zombie_theater"', 'seta SV_MAPROTATION "map zombie_moon"', 'sv_mapRotation "map zombie_pentagon"' ].join('\n'), [], 't5', 'zombie_cod5_sumpf'); expect(result).toBe('// keep this comment\nset sv_maprotation "map zombie_cod5_sumpf"\n'); }); it.each([ [null, null], ['t5', null], ['t5', 'custom_map'], ['t6', 'zombie_theater'] ])('preserves existing behavior for unsupported game/map %s/%s', (game, map) => { expect(composeServerCfg(BASELINE, [], game, map)).toBe(composeCfg(BASELINE, [])); }); });