mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-11-08 07:57:40 +00:00
fix(jobnet-search): filter search by the ESCO occupation that occupations prints (#649)
* fix(jobnet-search): filter search by the ESCO occupation that occupations prints SKILL.md and the CLI README told users to pass the conceptUriDa UUID from `occupations` to `search --occupation-area` / `--occupation-group`. Those flags take the numeric area and group codes (10000, 10060), so a UUID there returned a successful search with zero ads. The API filters one occupation through its `occupations` parameter, which returns the 830 nurse ads for the sygeplejerske UUID. Add `search --occupation`, sent as `occupations`, accepting the full conceptUriDa or its trailing UUID, and point the docs at it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * changelog: place the #649 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:
5 files changed
+107
-4
No files matched your search
@@ -0,0 +1,76 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { buildSearchParams, occupationId } from "../src/commands/search";
|
||||
|
||||
// The identifier `occupations` documents and prints, copied unchanged.
|
||||
async function documentedConceptUri(): Promise<string> {
|
||||
const readme = (await Bun.file(join(import.meta.dir, "../README.md")).text()).replace(/\r\n/g, "\n");
|
||||
const section = readme.split("## `occupations`")[1].split("\n---")[0];
|
||||
return JSON.parse(section.match(/```json\n([\s\S]*?)```/)![1])[0].conceptUriDa;
|
||||
}
|
||||
|
||||
async function searchRequest(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({ jobAds: [], searchFacets: {}, totalJobAdCount: 0, searchString: null });
|
||||
},
|
||||
});
|
||||
const dir = mkdtempSync(join(tmpdir(), "jobnet-occupation-filter-"));
|
||||
const preload = join(dir, "fetch.ts");
|
||||
writeFileSync(preload, `
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = (input, init) => {
|
||||
const url = new URL(input instanceof Request ? input.url : String(input));
|
||||
if (url.protocol === "data:") return originalFetch(input, init);
|
||||
if (url.origin !== "https://jobnet.dk" || url.pathname !== "/bff/FindJob/Search") {
|
||||
throw new Error("Unexpected search fixture request");
|
||||
}
|
||||
return originalFetch(${JSON.stringify(server.url.origin)} + url.pathname + url.search, init);
|
||||
};
|
||||
`);
|
||||
try {
|
||||
const proc = Bun.spawn([
|
||||
process.execPath, "run", "--preload", preload,
|
||||
join(import.meta.dir, "../src/cli.ts"), "search", ...args,
|
||||
], { stdout: "pipe", stderr: "pipe" });
|
||||
const [stderr, exitCode] = await Promise.all([new Response(proc.stderr).text(), proc.exited]);
|
||||
expect(stderr).toBe("");
|
||||
expect(exitCode).toBe(0);
|
||||
expect(requests).toHaveLength(1);
|
||||
return requests[0].searchParams;
|
||||
} finally {
|
||||
server.stop(true);
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
test("search --occupation sends the conceptUriDa printed by occupations as the occupations filter", async () => {
|
||||
const uri = await documentedConceptUri();
|
||||
const uuid = uri.split("/").at(-1)!;
|
||||
const params = await searchRequest(["--occupation", uri]);
|
||||
expect(params.get("occupations")).toBe(uuid);
|
||||
// Never the area/group filters, which take numeric codes and match no ad for a UUID.
|
||||
expect(params.get("occupationAreas")).toBeNull();
|
||||
expect(params.get("occupationGroups")).toBeNull();
|
||||
});
|
||||
|
||||
test("search --occupation accepts the bare UUID", async () => {
|
||||
const uuid = (await documentedConceptUri()).split("/").at(-1)!;
|
||||
expect((await searchRequest(["--occupation", uuid])).get("occupations")).toBe(uuid);
|
||||
});
|
||||
|
||||
test("occupationId keeps a UUID and takes the last segment of a URI", () => {
|
||||
const uuid = "56f5d45c-ce48-43ac-9720-34831a1c8302";
|
||||
expect(occupationId(uuid)).toBe(uuid);
|
||||
expect(occupationId(`http://data.star.dk/esco/occupation/${uuid}`)).toBe(uuid);
|
||||
expect(occupationId(` http://data.star.dk/esco/occupation/${uuid}/ `)).toBe(uuid);
|
||||
expect(
|
||||
buildSearchParams({ page: 1, "per-page": 10, order: "PublicationDate", radius: 50, occupation: uuid }).occupations,
|
||||
).toBe(uuid);
|
||||
});
|
||||
Reference in new issue
Block a user