mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 16:46:24 +00:00
Silently discarded flags produced silently wrong results: jobdanmark with --query (its real flag is --text) returned all 13,862 jobs as if they matched, exit 0, empty stderr - indistinguishable from a real result set. The four bunli CLIs get an argv preflight built from each command's own options object; linkedin and freehire validate parsed flags against per-command known sets. help/version still pass, and add-portal.md's existing bogus-flag-exits-1 contract now holds for the reference implementations contributors copy. One linkedin pin updated: "--jobage-minutes -5" now fails as UNKNOWN_FLAG (the stray -5 token) rather than BAD_ARG - same loud-failure invariant, earlier gate. Review finding F13 (2026-08-19), decision approved by Mads. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
97 lines
3.7 KiB
TypeScript
97 lines
3.7 KiB
TypeScript
import { describe, test, expect } from "bun:test";
|
|
import { runCLI } from "./helpers";
|
|
|
|
// These assert on validation error codes that are emitted BEFORE any network
|
|
// call (or independently of it), so the suite is network-free: a valid-flag case
|
|
// still runs offline because it only checks the ABSENCE of a validation error.
|
|
|
|
function parsedStderr(stderr: string): { error?: string; code?: string } {
|
|
try {
|
|
return JSON.parse(stderr);
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
describe("freehire CLI flag validation", () => {
|
|
describe("numeric flag validation", () => {
|
|
for (const name of ["jobage", "page", "limit"]) {
|
|
test(`--${name} non-numeric exits 1 with BAD_ARG`, async () => {
|
|
const result = await runCLI(["search", `--${name}`, "foo"]);
|
|
expect(result.exitCode).not.toBe(0);
|
|
const err = parsedStderr(result.stderr);
|
|
expect(err.code).toBe("BAD_ARG");
|
|
expect(err.error).toMatch(new RegExp(name));
|
|
});
|
|
}
|
|
|
|
test("valid integers produce no BAD_ARG", async () => {
|
|
const result = await runCLI(["search", "--jobage", "7", "--page", "1", "--limit", "1"]);
|
|
expect(parsedStderr(result.stderr).code).not.toBe("BAD_ARG");
|
|
});
|
|
});
|
|
|
|
describe("--description-format validation", () => {
|
|
test("an unsupported format exits 1 with BAD_ARG", async () => {
|
|
const result = await runCLI(["search", "--description-format", "tekst"]);
|
|
expect(result.exitCode).not.toBe(0);
|
|
const err = parsedStderr(result.stderr);
|
|
expect(err.code).toBe("BAD_ARG");
|
|
expect(err.error).toMatch(/description-format/);
|
|
});
|
|
});
|
|
|
|
describe("--facet validation", () => {
|
|
test("a facet without '=' exits 1 with BAD_ARG", async () => {
|
|
const result = await runCLI(["search", "--facet", "novalue"]);
|
|
expect(result.exitCode).not.toBe(0);
|
|
expect(parsedStderr(result.stderr).code).toBe("BAD_ARG");
|
|
});
|
|
});
|
|
|
|
describe("detail argument validation", () => {
|
|
test("missing slug exits 1 with NO_ID", async () => {
|
|
const result = await runCLI(["detail"]);
|
|
expect(result.exitCode).not.toBe(0);
|
|
expect(parsedStderr(result.stderr).code).toBe("NO_ID");
|
|
});
|
|
|
|
test("an unparseable slug exits 1 with BAD_ID (no network)", async () => {
|
|
const result = await runCLI(["detail", "not a slug!"]);
|
|
expect(result.exitCode).not.toBe(0);
|
|
expect(parsedStderr(result.stderr).code).toBe("BAD_ID");
|
|
});
|
|
});
|
|
|
|
describe("command dispatch", () => {
|
|
test("unknown command exits 1 with BAD_CMD", async () => {
|
|
const result = await runCLI(["frobnicate"]);
|
|
expect(result.exitCode).not.toBe(0);
|
|
expect(parsedStderr(result.stderr).code).toBe("BAD_CMD");
|
|
});
|
|
|
|
test("no command prints help and exits 1", async () => {
|
|
const result = await runCLI([]);
|
|
expect(result.exitCode).toBe(1);
|
|
expect(result.stdout).toMatch(/USAGE/);
|
|
});
|
|
});
|
|
});
|
|
|
|
|
|
describe("unknown flag rejection", () => {
|
|
// add-portal.md's contract: "a bogus flag or missing required arg exits 1
|
|
// with a JSON error on stderr". A silently discarded flag is worse than an
|
|
// error: on jobdanmark a wrong flag name returned the entire database
|
|
// (13,862 results) as if it matched the query (review finding F13,
|
|
// 2026-08-19). Rejection happens before dispatch, so these are network-free.
|
|
test("a bogus --flag exits 1 with a JSON error instead of being silently discarded", async () => {
|
|
const result = await runCLI(["search", "--query", "test", "--bogus-flag", "xyz"]);
|
|
expect(result.exitCode).toBe(1);
|
|
expect(result.stdout).toBe("");
|
|
const error = JSON.parse(result.stderr);
|
|
expect(error.code).toBe("UNKNOWN_FLAG");
|
|
expect(error.error).toContain("--bogus-flag");
|
|
});
|
|
});
|