Skip to content

test: programmatic CLI harness for src/index.ts action handlers #363

Description

@pcarleton

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

  • Extract createProgram(); gate program.parse() on require.main === module (or move to a bin.ts shim)
  • Shared test helpers: process.exit / console.error spies, temp-file fixture writer
  • Initial coverage: authorization --file (valid file, missing url, unknown key, bad JSON, neither --url nor --file)
  • Follow-on: extend to server / client flag validation as needed

Related: #360, #225

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions