fix(freehire-search): reject detail URLs on other hosts instead of asking the API for their last segment (#622) (#623)

* fix(freehire-search): reject detail URLs on other hosts instead of asking the API for their last segment

normalizeSlug took the segment after "/jobs/" from ANY string, so a
Greenhouse apply link - which is what a freehire search result's own url
field carries - was reduced to its last path segment and detail
requested /api/v1/jobs/1, then reported NOT_FOUND for a URL that was
never a freehire posting. Every other portal CLI rejects an off-host
detail URL with BAD_ID (#447, #430, #499); freehire was the last one
trusting the path.

Parse URLs for real: freehire.me (apex or any subdomain, scheme optional)
or the host of FREEHIRE_API_URL for a self-hosted instance, plus a
/jobs/<slug> path, yields the slug; other hosts, look-alike and userinfo
hosts, and non-job freehire pages exit 1 with BAD_ID before any request.
Bare slugs are unchanged.

Five new normalizeSlug cases and a handler-level test that pastes the
search fixture's own apply URL into detail with fetch stubbed; the
off-host cases fail on master.

* changelog: place the #623 entry under [Unreleased]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Mads Lorentzen <madslorentzen_17@hotmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-10-19 19:20:06 +02:00
1 parent 84808eebc1
commit 447fec0302
5 files changed
+112 -4

No files matched your search

@@ -235,3 +235,20 @@ describe("runDetail (mocked fetch)", () => {
expect(JSON.parse(err).code).toBe("NOT_FOUND");
});
});
describe("detail input on the real handler path", () => {
test("a search result's own apply URL is rejected with BAD_ID before any request", async () => {
// Every freehire search result carries the employer's apply link in `url`
// (the fixture's is a Greenhouse board). Pasting it back into detail used
// to request /api/v1/jobs/1 and report NOT_FOUND for a URL that was never
// a freehire posting.
const mock = mockFetch(404, { error: "not found" });
const err = captureStderr();
const code = await runDetail({ id: job().url, format: "json" });
expect(code).toBe(1);
expect(mock.url()).toBe("");
expect(JSON.parse(err.get())).toMatchObject({ code: "BAD_ID" });
});
});