mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-11-02 13:12:50 +00:00
fix(freehire-search): reject --description-format on detail instead of ignoring it (#648)
* fix(freehire-search): reject --description-format on detail instead of ignoring it The flag was on detail's known-flag list, but runDetail never reads it and always prints the detail endpoint's HTML body stripped to plain text, so detail <slug> --description-format markdown passed the unknown-flag guard, exited 0 and printed text. Drop it from detail's list so it fails with UNKNOWN_FLAG before any request; search keeps the flag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * changelog: place the #648 entry under [Unreleased] Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Mads Lorentzen <madslorentzen_17@hotmail.com>
This commit is contained in:
3 files changed
+84
-2
No files matched your search
@@ -131,13 +131,15 @@ function parseIntFlag(name: string, raw: string | boolean | string[]): number |
|
|||||||
|
|
||||||
// Long-form flag names each command accepts (parseFlags resolves the short
|
// Long-form flag names each command accepts (parseFlags resolves the short
|
||||||
// aliases q/n to these before validation). "help"/"h" pass so `search --help`
|
// aliases q/n to these before validation). "help"/"h" pass so `search --help`
|
||||||
// still prints usage.
|
// still prints usage. detail takes no --description-format: it always prints
|
||||||
|
// the detail endpoint's HTML body stripped to text, so accepting the flag
|
||||||
|
// there ignored it silently.
|
||||||
const KNOWN_FLAGS: Record<string, Set<string>> = {
|
const KNOWN_FLAGS: Record<string, Set<string>> = {
|
||||||
search: new Set([
|
search: new Set([
|
||||||
"query", "category", "city", "company", "country", "facet", "format", "jobage", "limit",
|
"query", "category", "city", "company", "country", "facet", "format", "jobage", "limit",
|
||||||
"page", "region", "remote", "seniority", "skill", "description-format", "no-description", "help", "h",
|
"page", "region", "remote", "seniority", "skill", "description-format", "no-description", "help", "h",
|
||||||
]),
|
]),
|
||||||
detail: new Set(["format", "description-format", "help", "h"]),
|
detail: new Set(["format", "help", "h"]),
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main(): Promise<number> {
|
async function main(): Promise<number> {
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { describe, expect, test } from "bun:test";
|
||||||
|
import { runCLI } from "./helpers";
|
||||||
|
|
||||||
|
// `detail` listed --description-format among its known flags, but runDetail
|
||||||
|
// never reads it: the detail endpoint's HTML body is always stripped to plain
|
||||||
|
// text. So `detail <slug> --description-format markdown` passed the
|
||||||
|
// unknown-flag guard, exited 0 and printed plain text - the silently ignored
|
||||||
|
// flag that guard exists to prevent. The CLI runs against a loopback API
|
||||||
|
// through its supported FREEHIRE_API_URL override; requests are counted.
|
||||||
|
const JOB = {
|
||||||
|
public_slug: "backend-engineer-acme-ab12cd34",
|
||||||
|
source: "greenhouse",
|
||||||
|
external_id: "1",
|
||||||
|
url: "https://boards.example/acme/1",
|
||||||
|
title: "Backend Engineer",
|
||||||
|
company: "Acme",
|
||||||
|
company_slug: "acme",
|
||||||
|
location: "Berlin, Germany",
|
||||||
|
description: "<p>Build things</p><ul><li>Go</li><li>Postgres</li></ul>",
|
||||||
|
skills: ["go"],
|
||||||
|
regions: ["eu"],
|
||||||
|
countries: ["DE"],
|
||||||
|
cities: ["Berlin"],
|
||||||
|
posted_at: "2026-10-01T00:00:00Z",
|
||||||
|
created_at: "2026-10-01T00:00:00Z",
|
||||||
|
enrichment: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
async function detail(args: string[]) {
|
||||||
|
const requests: URL[] = [];
|
||||||
|
const server = Bun.serve({
|
||||||
|
hostname: "127.0.0.1",
|
||||||
|
port: 0,
|
||||||
|
fetch(request) {
|
||||||
|
requests.push(new URL(request.url));
|
||||||
|
return Response.json({ data: JOB });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const result = await runCLI(["detail", JOB.public_slug, ...args], { FREEHIRE_API_URL: server.url.origin });
|
||||||
|
return { result, requests };
|
||||||
|
} finally {
|
||||||
|
server.stop(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("detail flags", () => {
|
||||||
|
for (const value of ["markdown", "text", "html"]) {
|
||||||
|
test(`--description-format ${value} is rejected as an unknown detail flag before any request`, async () => {
|
||||||
|
const { result, requests } = await detail(["--description-format", value]);
|
||||||
|
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("--description-format");
|
||||||
|
expect(requests).toHaveLength(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test("--format plain still prints the detail", async () => {
|
||||||
|
const { result, requests } = await detail(["--format", "plain"]);
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(requests).toHaveLength(1);
|
||||||
|
expect(requests[0].pathname).toBe(`/api/v1/jobs/${JOB.public_slug}`);
|
||||||
|
expect(result.stdout).toContain("Backend Engineer");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("search still validates its own --description-format", async () => {
|
||||||
|
const result = await runCLI(["search", "--description-format", "tekst"]);
|
||||||
|
expect(JSON.parse(result.stderr).code).toBe("BAD_ARG");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -30,6 +30,14 @@ per-file diff commands.
|
|||||||
handler-level test that pastes the search fixture's own apply URL into `detail` with fetch
|
handler-level test that pastes the search fixture's own apply URL into `detail` with fetch
|
||||||
stubbed; the off-host cases fail on master.
|
stubbed; the off-host cases fail on master.
|
||||||
|
|
||||||
|
- **`freehire-search detail` rejects `--description-format` instead of ignoring it**
|
||||||
|
(#648, `.agents/skills/freehire-search/cli/src/cli.ts`, `tests/detail-flags.test.ts`) - the flag
|
||||||
|
was on `detail`'s known-flag list, but `runDetail` never reads it and always prints the detail
|
||||||
|
endpoint's HTML body stripped to plain text. So `detail <slug> --description-format markdown`
|
||||||
|
passed the unknown-flag guard, exited 0 and printed text, which is the silently ignored flag
|
||||||
|
that guard exists to prevent. `detail` now answers it with `UNKNOWN_FLAG` before any request;
|
||||||
|
`search`, where the flag is documented and used, is unchanged.
|
||||||
|
|
||||||
## [1.7.3] - 2026-10-19
|
## [1.7.3] - 2026-10-19
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
Reference in new issue
Block a user