feat(soak): CLI parsing + config precedence
parseArgs pulls --scenario/--rooms/--watch/etc from argv, mergeConfig layers scenarioDefaults → env → CLI so CLI flags always win. 12 Vitest unit tests cover both parse happy/edge paths and the 4-way merge precedence matrix. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
102
tests/soak/tests/config.test.ts
Normal file
102
tests/soak/tests/config.test.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { parseArgs, mergeConfig } from '../config';
|
||||
|
||||
describe('parseArgs', () => {
|
||||
it('parses --scenario and numeric flags', () => {
|
||||
const r = parseArgs(['--scenario=populate', '--rooms=4', '--games-per-room=10']);
|
||||
expect(r.scenario).toBe('populate');
|
||||
expect(r.rooms).toBe(4);
|
||||
expect(r.gamesPerRoom).toBe(10);
|
||||
});
|
||||
|
||||
it('parses watch mode', () => {
|
||||
const r = parseArgs(['--scenario=populate', '--watch=none']);
|
||||
expect(r.watch).toBe('none');
|
||||
});
|
||||
|
||||
it('rejects unknown watch mode', () => {
|
||||
expect(() => parseArgs(['--scenario=populate', '--watch=bogus'])).toThrow();
|
||||
});
|
||||
|
||||
it('--list sets listOnly', () => {
|
||||
const r = parseArgs(['--list']);
|
||||
expect(r.listOnly).toBe(true);
|
||||
});
|
||||
|
||||
it('--dry-run sets dryRun', () => {
|
||||
const r = parseArgs(['--scenario=populate', '--dry-run']);
|
||||
expect(r.dryRun).toBe(true);
|
||||
});
|
||||
|
||||
it('parses --accounts, --cpus-per-room, --dashboard-port, --target, --run-id, --holes', () => {
|
||||
const r = parseArgs([
|
||||
'--scenario=stress',
|
||||
'--accounts=8',
|
||||
'--cpus-per-room=2',
|
||||
'--dashboard-port=7777',
|
||||
'--target=http://localhost:8000',
|
||||
'--run-id=test-1',
|
||||
'--holes=1',
|
||||
]);
|
||||
expect(r.accounts).toBe(8);
|
||||
expect(r.cpusPerRoom).toBe(2);
|
||||
expect(r.dashboardPort).toBe(7777);
|
||||
expect(r.target).toBe('http://localhost:8000');
|
||||
expect(r.runId).toBe('test-1');
|
||||
expect(r.holes).toBe(1);
|
||||
});
|
||||
|
||||
it('throws on non-numeric integer flag', () => {
|
||||
expect(() => parseArgs(['--rooms=four'])).toThrow(/Invalid integer/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mergeConfig', () => {
|
||||
it('CLI flags override scenario defaults', () => {
|
||||
const cfg = mergeConfig(
|
||||
{ gamesPerRoom: 20 },
|
||||
{},
|
||||
{ gamesPerRoom: 5, holes: 9 },
|
||||
);
|
||||
expect(cfg.gamesPerRoom).toBe(20);
|
||||
expect(cfg.holes).toBe(9);
|
||||
});
|
||||
|
||||
it('env overrides scenario defaults but CLI overrides env', () => {
|
||||
const cfg = mergeConfig(
|
||||
{ holes: 5 }, // CLI
|
||||
{ SOAK_HOLES: '3' }, // env
|
||||
{ holes: 9 }, // defaults
|
||||
);
|
||||
expect(cfg.holes).toBe(5); // CLI wins
|
||||
});
|
||||
|
||||
it('env overrides scenario defaults when CLI is absent', () => {
|
||||
const cfg = mergeConfig(
|
||||
{}, // no CLI
|
||||
{ SOAK_HOLES: '3' }, // env
|
||||
{ holes: 9 }, // defaults
|
||||
);
|
||||
expect(cfg.holes).toBe(3); // env wins over defaults
|
||||
});
|
||||
|
||||
it('scenario defaults fill in unset values', () => {
|
||||
const cfg = mergeConfig(
|
||||
{},
|
||||
{},
|
||||
{ gamesPerRoom: 3, holes: 9 },
|
||||
);
|
||||
expect(cfg.gamesPerRoom).toBe(3);
|
||||
expect(cfg.holes).toBe(9);
|
||||
});
|
||||
|
||||
it('env numeric keys are parsed to integers', () => {
|
||||
const cfg = mergeConfig(
|
||||
{},
|
||||
{ SOAK_ROOMS: '4', SOAK_ACCOUNTS: '16' },
|
||||
{},
|
||||
);
|
||||
expect(cfg.rooms).toBe(4);
|
||||
expect(cfg.accounts).toBe(16);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user