src/index.ts currently has zero test coverage of its commander action handlers — all testing happens at the scenario/runner layer. This came up in #360: we wanted to test that authorization --file <path> rejects an invalid settings file, but there's no way to exercise the real codepath without either spawning a child process or duplicating the merge logic in the test (which can drift).
Proposed approach
Refactor src/index.ts so the Command can be constructed without auto-parsing:
// src/index.ts
export function createProgram(): Command { /* register all subcommands */ }
// only parse when run as the bin entrypoint
if (require.main === module) createProgram().parse();
Tests then drive it in-process:
const exit = vi.spyOn(process, 'exit').mockImplementation(((c) => {
throw new Error(`exit:${c}`);
}) as never);
const stderr = vi.spyOn(console, 'error').mockImplementation(() => {});
vi.mock('./runner/authorization-server'); // stub the network layer
await expect(
createProgram().exitOverride().parseAsync(
['node', 'x', 'authorization', '--file', tmpInvalidJson]
)
).rejects.toThrow('exit:1');
expect(stderr.mock.calls.join('\n')).toContain("Invalid settings file");
Why not spawn the binary
execa against dist/ is the strongest guarantee but is slow (process per case) and depends on a build step. Programmatic .parseAsync() is fast enough to cover every flag/validation path and matches how commander's own suite tests itself.
Scope
Related: #360, #225
src/index.tscurrently has zero test coverage of its commander action handlers — all testing happens at the scenario/runner layer. This came up in #360: we wanted to test thatauthorization --file <path>rejects an invalid settings file, but there's no way to exercise the real codepath without either spawning a child process or duplicating the merge logic in the test (which can drift).Proposed approach
Refactor
src/index.tsso theCommandcan be constructed without auto-parsing:Tests then drive it in-process:
Why not spawn the binary
execaagainstdist/is the strongest guarantee but is slow (process per case) and depends on a build step. Programmatic.parseAsync()is fast enough to cover every flag/validation path and matches how commander's own suite tests itself.Scope
createProgram(); gateprogram.parse()onrequire.main === module(or move to abin.tsshim)process.exit/console.errorspies, temp-file fixture writerauthorization --file(valid file, missingurl, unknown key, bad JSON, neither--urlnor--file)server/clientflag validation as neededRelated: #360, #225