Skip to content

[CLAUDE ROUTINE]: Test enhancement — add unit tests for bin/failproofai.mjs CLI dispatch, unknown-flag suggestions, and exit-code contract #255

@NiveditJain

Description

@NiveditJain

Summary

bin/failproofai.mjs is the single entry point every user runs (failproofai, npx failproofai, the post-install hook, the bundled dist/cli.mjs). It parses --hook, --cli, --scope, the subcommand list (policies, login, relay, sync), routes to the right module, and standardises exit codes. We have great coverage of what those modules do once dispatched, but the dispatch layer itself currently has no direct unit test — regressions here ship straight to users (we already lived through this with #47 "clean CLI error handling — no raw stack traces, reject unknown args").

Adding a focused dispatch test suite keeps the CLI's user-facing contract (flag shapes, subcommand routing, exit codes, help/version) tied to a regression test.

Where

  • Entry: bin/failproofai.mjs (~530 lines)
  • Helpers used by entry: src/cli-error.ts, src/hooks/handler.ts (handleHookEvent), src/hooks/manager.ts, src/auth/login.ts, src/relay/{daemon,pid}.ts
  • No matching test file under __tests__/ for bin/

Why this matters

flowchart TB
    USER[failproofai cmd] --> BIN[bin/failproofai.mjs]
    BIN -->|--hook X| H[handler.ts ✅ tested]
    BIN -->|policies| M[manager.ts ✅ tested]
    BIN -->|login| L[auth/login.ts 🟡 partial]
    BIN -->|relay| R[relay/daemon.ts 🟡 partial]
    BIN -->|--version / --help| V[stdout / exit 0 ❓ untested]
    BIN -->|unknown| E[cli-error → exit 1 ❓ untested]
    BIN -->|--cli claude codex| MC[multi-value flag ❓ untested]
Loading

The grey boxes are where users live — and where regressions cause "the CLI prints a stack trace" / "—help silently exits 0 with no output" / "--cli claude codex only registered claude" issues.

This is distinct from:

Why this helps users

  • Locks in the contract: exit code 0 = ok, 1 = user error, 2 = system error. A regression to "always 1" silently breaks scripts that gate on failproofai relay status.
  • Catches typo suggestions: an unknown flag should produce did you mean --scope? (similar to npm, git). A test guarantees this stays.
  • Documents the surface: the test matrix doubles as living documentation for what arg shapes we support.

Proposed enhancement

Create __tests__/bin/failproofai.test.ts:

import { spawnSync } from "node:child_process";
import { join } from "node:path";

const BIN = join(__dirname, "..", "..", "bin", "failproofai.mjs");
const run = (args: string[], env: NodeJS.ProcessEnv = {}) =>
  spawnSync("bun", [BIN, ...args], { env: { ...process.env, ...env }, encoding: "utf8" });

describe("bin/failproofai.mjs dispatch", () => {
  it("--version prints version and exits 0", () => {
    const { stdout, status } = run(["--version"]);
    expect(status).toBe(0);
    expect(stdout).toMatch(/^\d+\.\d+\.\d+/);
  });

  it.each([
    ["--help"],
    ["help"],
    ["policies", "--help"],
  ])("%s prints usage and exits 0", (...args) => {
    const { stdout, status } = run(args);
    expect(status).toBe(0);
    expect(stdout).toMatch(/Usage:/);
  });

  it("unknown flag exits 1 with a helpful message (not a stack trace)", () => {
    const { stderr, status } = run(["--scop", "user"]);
    expect(status).toBe(1);
    expect(stderr).not.toMatch(/^\s+at /m);          // no V8 stack frames
    expect(stderr).toMatch(/unknown.*--scop/i);      // user-friendly
    expect(stderr).toMatch(/did you mean.*--scope/i); // suggestion
  });

  it.each([
    [["--cli", "claude", "codex", "copilot"]],   // space-separated
    [["--cli", "claude", "--cli", "codex", "--cli", "copilot"]], // repeated
  ])("--cli accepts %j", (args) => {
    const { status } = run(["policies", "--list", ...args]);
    expect(status).toBe(0);
  });

  it("--hook PreToolUse routes to handler and propagates its exit code", () => {
    const { status } = run(["--hook", "PreToolUse"], { /* feed empty stdin */ });
    expect([0, 2]).toContain(status); // 0 allow, 2 deny — never 1
  });
});

Use vitest's bun runner via spawnSync; tests stay hermetic by setting HOME to a temp dir for any subcommand that touches ~/.failproofai.

Acceptance criteria

  • New test file __tests__/bin/failproofai.test.ts covering: version, help, unknown-flag suggestion, multi-value --cli parsing (both shapes), --hook exit-code contract, --scope validation, missing required arg.
  • Tests run in under 5 s combined (use a shared tmp HOME, no real network).
  • No existing test mocked away; this is genuine integration coverage of the dispatch layer.
  • CHANGELOG entry under Unreleased → Tests.

Severity

Low — but the cost of regressions here is high (every customer touches the CLI). Cheap insurance against another #47-class issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions