Refresh Configs with browser editing and source inspection
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { inspectConfig, type ConfigLayer } from '../../src/lib/server/fleet/config-inspect.js';
|
||||
import { composeServerCfg } from '../../src/lib/server/fleet/config-compose.js';
|
||||
import { changeConfigValue, configWarnings } from '../../src/lib/config-workbench.js';
|
||||
|
||||
const base = (text: string): ConfigLayer => ({ text, source: { id: 1, name: 'Base', version: 2, kind: 'baseline' } });
|
||||
const overlay = (id: number, text: string): ConfigLayer => ({ text, source: { id, name: `Overlay ${id}`, version: 3, kind: 'config' } });
|
||||
|
||||
describe('config source inspection', () => {
|
||||
it('traces the baseline and every override in actual ID order', () => {
|
||||
const baseline = base('set g_inactivity "190"\r\nset sv_voice "1"\r\n');
|
||||
const overlays = [overlay(5, 'set g_inactivity "60"'), overlay(2, 'set g_inactivity "0"')];
|
||||
const result = inspectConfig(baseline, overlays, 't5', 'zombie_theater');
|
||||
expect(result.text).toBe(composeServerCfg(baseline.text, overlays.map(o => ({ id: o.source.id!, text: o.text })), 't5', 'zombie_theater'));
|
||||
const setting = result.settings.find(s => s.key === 'g_inactivity')!;
|
||||
expect(setting.value).toBe('"60"');
|
||||
expect(setting.source.id).toBe(5);
|
||||
expect(setting.history.map(h => h.source.id)).toEqual([1, 2, 5]);
|
||||
expect(result.settings.find(s => s.key === 'sv_voice')?.source.id).toBe(1);
|
||||
});
|
||||
|
||||
it('explains duplicates within baselines and within overlays', () => {
|
||||
const result = inspectConfig(base('set x 1\nset x 2'), [overlay(2, 'set x 3\nset x 4')], null, null);
|
||||
expect(result.text).toBe('set x 1\nset x 4\n');
|
||||
expect(result.settings[0].history.map(h => h.value)).toEqual(['1', '2', '3', '4']);
|
||||
expect(result.settings[0].value).toBe('4');
|
||||
expect(result.warnings.filter(w => w.includes('also assigned'))).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('uses final file order for differently cased keys rather than assuming layer order', () => {
|
||||
const result = inspectConfig(base('set X 1'), [overlay(2, 'set x 2'), overlay(3, 'set X 3')], null, null);
|
||||
expect(result.text).toContain('set X 3');
|
||||
expect(result.settings).toHaveLength(1);
|
||||
expect(result.settings[0].value).toBe('2');
|
||||
expect(result.settings[0].source.id).toBe(2);
|
||||
});
|
||||
|
||||
it('traces map overrides across set, seta, and bare assignments', () => {
|
||||
const result = inspectConfig(base('set sv_maprotation "old"\nseta SV_MAPROTATION "older"\nsv_maprotation "last"'), [overlay(2, 'set sv_maprotation "overlay"')], 't5', 'zombie_theater');
|
||||
const setting = result.settings[0];
|
||||
expect(setting.key).toBe('sv_maprotation');
|
||||
expect(setting.value).toBe('"map zombie_theater"');
|
||||
expect(setting.source.kind).toBe('map');
|
||||
expect(setting.history.map(h => h.value)).toEqual(['"old"', '"overlay"', '"older"', '"last"', '"map zombie_theater"']);
|
||||
expect(result.text.match(/set sv_maprotation/g)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('preserves T6 map variants and does not invent map selection for unknown maps', () => {
|
||||
const t6 = inspectConfig(base('set sv_voice 1'), [], 't6', 'zm_transit', 'town');
|
||||
expect(t6.settings.find(s => s.key === 'sv_maprotation')?.value).toContain('execgts zm_standard_town.cfg');
|
||||
const legacy = inspectConfig(base('set sv_maprotation "custom"'), [], 't5', 'unknown');
|
||||
expect(legacy.settings[0].source.kind).toBe('baseline');
|
||||
expect(legacy.text).toBe('set sv_maprotation "custom"\n');
|
||||
});
|
||||
|
||||
it('reports ignored overlay commands while retaining raw baseline commands', () => {
|
||||
const result = inspectConfig(base('exec custom.cfg\nset a 1'), [overlay(2, 'seta a 2\nSET a 3\n// note\nset b 4')], null, null);
|
||||
expect(result.warnings.filter(w => w.includes('ignored'))).toHaveLength(2);
|
||||
expect(result.text).toContain('exec custom.cfg');
|
||||
expect(result.settings.find(s => s.key === 'a')?.value).toBe('1');
|
||||
expect(result.settings.find(s => s.key === 'b')?.source.id).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('config settings editor', () => {
|
||||
it('changes only the selected assignment, preserving CRLF, spacing and raw commands', () => {
|
||||
const text = '// note\r\n set a "1" // original\r\nexec other.cfg\r\nset a 5\n';
|
||||
expect(changeConfigValue(text, 1, '"2" // changed')).toBe('// note\r\n set a "2" // changed\r\nexec other.cfg\r\nset a 5\n');
|
||||
});
|
||||
it('does not silently rewrite unsupported lines and reports missing values', () => {
|
||||
expect(changeConfigValue('seta a 1', 0, '2')).toBe('seta a 1');
|
||||
expect(configWarnings('set a // missing', true)).toContain('Line 1: a has no value.');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user