mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 00:26:26 +00:00
parseIntFlag used bare parseInt, so --jobage 0.5 truncated to 0, failed the jobage > 0 guard in search.ts, and posted_within_days was silently omitted from the outbound request while the CLI exited 0. Numeric flags now accept whole numbers >= 1 only, mirroring the Danish CLIs' z.coerce.number().int().min(1) contract, and reject everything else with the stderr-JSON BAD_ARG error. Five new validation cases, each verified to fail on the unfixed code.
This commit is contained in:
@@ -112,9 +112,16 @@ best-effort, no SLA. Override with FREEHIRE_API_URL to use a self-hosted backend
|
||||
`
|
||||
|
||||
function parseIntFlag(name: string, raw: string | boolean | string[]): number | null {
|
||||
const val = parseInt(raw as string, 10)
|
||||
if (isNaN(val)) {
|
||||
process.stderr.write(JSON.stringify({ error: `--${name} must be a number, got "${raw}"`, code: "BAD_ARG" }) + "\n")
|
||||
// Number(), not parseInt(): parseInt truncates, so "--jobage 0.5" became 0,
|
||||
// which fails search.ts's `jobage > 0` guard and silently drops
|
||||
// posted_within_days from the outbound request while exiting 0 (#373).
|
||||
// Whole numbers >= 1 only — the Danish CLIs' z.coerce.number().int().min(1)
|
||||
// contract; 0 is rejected rather than kept as a "no filter" alias.
|
||||
const val = typeof raw === "string" ? Number(raw.trim()) : NaN
|
||||
if (!Number.isInteger(val) || val < 1) {
|
||||
process.stderr.write(
|
||||
JSON.stringify({ error: `--${name} must be a whole number of at least 1, got "${raw}"`, code: "BAD_ARG" }) + "\n",
|
||||
)
|
||||
return null
|
||||
}
|
||||
return val
|
||||
|
||||
@@ -25,6 +25,32 @@ describe("freehire CLI flag validation", () => {
|
||||
});
|
||||
}
|
||||
|
||||
// Fractional values must be rejected, not truncated: parseInt("0.5") is 0,
|
||||
// and jobage 0 fails search.ts's `> 0` guard, so posted_within_days is
|
||||
// silently omitted from the outbound request while the CLI exits 0 —
|
||||
// the discarded-filter failure the UNKNOWN_FLAG guard exists to prevent (#373).
|
||||
for (const name of ["jobage", "page", "limit"]) {
|
||||
test(`--${name} fractional exits 1 with BAD_ARG instead of truncating`, async () => {
|
||||
const result = await runCLI(["search", `--${name}`, "1.5"]);
|
||||
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("--jobage 0.5 (truncates to 0 on master, dropping the freshness filter) exits 1 with BAD_ARG", async () => {
|
||||
const result = await runCLI(["search", "--jobage", "0.5"]);
|
||||
expect(result.exitCode).not.toBe(0);
|
||||
expect(parsedStderr(result.stderr).code).toBe("BAD_ARG");
|
||||
});
|
||||
|
||||
test("--jobage 0 exits 1 with BAD_ARG (0 silently disables the filter, like the Danish CLIs' min(1))", async () => {
|
||||
const result = await runCLI(["search", "--jobage", "0"]);
|
||||
expect(result.exitCode).not.toBe(0);
|
||||
expect(parsedStderr(result.stderr).code).toBe("BAD_ARG");
|
||||
});
|
||||
|
||||
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");
|
||||
|
||||
@@ -13,6 +13,20 @@ per-file diff commands.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- **`freehire-search` fractional numeric flags no longer silently change the query** (#373) -
|
||||
`parseIntFlag` used bare `parseInt`, so a fractional value was truncated instead of
|
||||
rejected: `--jobage 0.5` became `0`, failed the `jobage > 0` guard, and the
|
||||
`posted_within_days` freshness filter was silently omitted from the outbound request
|
||||
while the CLI exited 0 - on a default-ON `/scrape` portal, exactly the
|
||||
discarded-filter failure the CLI's own `UNKNOWN_FLAG` guard documents. Numeric flags
|
||||
(`--jobage`/`--page`/`--limit`) now accept whole numbers >= 1 only, mirroring the
|
||||
Danish CLIs' `z.coerce.number().int().min(1)` contract, and reject everything else
|
||||
with the stderr-JSON `BAD_ARG` error. The sibling of #371 (`linkedin-search`), which
|
||||
remains with its reporter. Pinned by five new cases in `cli-flag-validation.test.ts`,
|
||||
each verified to fail on the unfixed code.
|
||||
|
||||
### Added
|
||||
|
||||
- **pypdf ATS text-layer fallback** - `/apply` Step 5d and `tools/verify_pdf.py` extract the CV PDF text layer with **pypdf** first (BSD, `pip install pypdf`) so Windows machines without Poppler still get a mechanical parseability check. Poppler `pdftotext -layout -enc UTF-8` remains the fallback; if both are missing the check still degrades to a visual keyword review. No extra cache or installer. `05-cv-templates.md` `framework_version` 1.4.2 → 1.4.3.
|
||||
|
||||
Reference in New Issue
Block a user