From 2017521d2eaae17958e251bffd3d1bd5fa457249 Mon Sep 17 00:00:00 2001 From: Rudy Mizrahi Celekli <47457359+rudycelekli@users.noreply.github.com> Date: Sun, 18 Oct 2026 00:57:43 -0400 Subject: [PATCH] test(portal-clis): isolate valid flag controls from live services (#581) * test(portal-clis): isolate valid flag controls from live services The LinkedIn and Freehire positive controls spawned live search CLIs and only checked for the absence of BAD_ARG, so attempted portal requests still left a green suite. Use loopback responses and assert successful output plus the constructed freshness, pagination and result-limit contract. LinkedIn cards are verbatim excerpts from portapulito's actual captured response in PR #153; fixture provenance and original hashes are retained. Four Danish validation suites already stop at required-argument guards and need no change. This isolates these controls rather than claiming a whole-CI egress policy. * docs(changelog): record fix for #581 --- .../cli/tests/cli-flag-validation.test.ts | 32 +++++++++++++--- .../cli/tests/cli-flag-validation.test.ts | 28 ++++++++++---- .../cli/tests/fixtures/search-cards.json | 1 + .../tests/fixtures/search-cards.provenance.md | 20 ++++++++++ .../linkedin-search/cli/tests/helpers.ts | 37 ++++++++++++++++++- CHANGELOG.md | 2 + 6 files changed, 104 insertions(+), 16 deletions(-) create mode 100644 .agents/skills/linkedin-search/cli/tests/fixtures/search-cards.json create mode 100644 .agents/skills/linkedin-search/cli/tests/fixtures/search-cards.provenance.md diff --git a/.agents/skills/freehire-search/cli/tests/cli-flag-validation.test.ts b/.agents/skills/freehire-search/cli/tests/cli-flag-validation.test.ts index a84e8eaa..f296e160 100644 --- a/.agents/skills/freehire-search/cli/tests/cli-flag-validation.test.ts +++ b/.agents/skills/freehire-search/cli/tests/cli-flag-validation.test.ts @@ -1,9 +1,8 @@ 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. +// Invalid flags fail before any request. Valid-flag controls run against a +// loopback fixture and assert the requested filters, not just an absent error. function parsedStderr(stderr: string): { error?: string; code?: string } { try { @@ -51,9 +50,30 @@ describe("freehire CLI flag validation", () => { 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"); + test("valid integers construct the requested search offline", async () => { + 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: [], meta: { total: 0 } }); + }, + }); + try { + const result = await runCLI(["search", "--jobage", "7", "--page", "1", "--limit", "1"], + { FREEHIRE_API_URL: server.url.toString() }); + expect(result.exitCode).toBe(0); + expect(result.stderr).toBe(""); + expect(requests).toHaveLength(1); + expect(requests[0].pathname).toBe("/api/v1/agent/jobs/search"); + expect(requests[0].searchParams.get("posted_within_days")).toBe("7"); + expect(requests[0].searchParams.get("limit")).toBe("1"); + expect(requests[0].searchParams.get("offset")).toBe("0"); + expect(JSON.parse(result.stdout)).toEqual({ meta: { count: 0, page: 1, total: 0 }, results: [] }); + } finally { + server.stop(true); + } }); }); diff --git a/.agents/skills/linkedin-search/cli/tests/cli-flag-validation.test.ts b/.agents/skills/linkedin-search/cli/tests/cli-flag-validation.test.ts index af96989f..b7de7894 100644 --- a/.agents/skills/linkedin-search/cli/tests/cli-flag-validation.test.ts +++ b/.agents/skills/linkedin-search/cli/tests/cli-flag-validation.test.ts @@ -1,5 +1,5 @@ import { describe, test, expect } from "bun:test"; -import { runCLI } from "./helpers"; +import { runCLI, runSearchFixture } from "./helpers"; const LOCATION = "Copenhagen, Denmark"; @@ -28,9 +28,15 @@ describe("LinkedIn CLI flag validation", () => { }); test("valid integer passes validation", async () => { - const result = await runCLI(["search", "-l", LOCATION, "--jobage", "7", "--limit", "1"]); - const err = parsedStderr(result.stderr); - expect(err.code).not.toBe("BAD_ARG"); + const { result, requests } = await runSearchFixture(["search", "-l", LOCATION, "--jobage", "7", "--limit", "1"]); + expect(result.exitCode).toBe(0); + expect(result.stderr).toBe(""); + expect(requests).toHaveLength(1); + expect(requests[0].searchParams.get("location")).toBe(LOCATION); + expect(requests[0].searchParams.get("f_TPR")).toBe("r604800"); + const output = JSON.parse(result.stdout); + expect(output.meta.count).toBe(1); + expect(output.results.map((job: { id: string }) => job.id)).toEqual(["4425498329"]); }); // Fractional values must be rejected, not truncated: parseInt("0.5") is 0, @@ -126,12 +132,18 @@ describe("LinkedIn CLI flag validation", () => { expect(err.code).toBe("NO_LOCATION"); }); - test("all valid flags produce no BAD_ARG", async () => { - const result = await runCLI([ + test("all valid flags construct the requested search offline", async () => { + const { result, requests } = await runSearchFixture([ "search", "-l", LOCATION, "--jobage", "7", "--page", "1", "--limit", "5", ]); - const err = parsedStderr(result.stderr); - expect(err.code).not.toBe("BAD_ARG"); + expect(result.exitCode).toBe(0); + expect(result.stderr).toBe(""); + expect(requests).toHaveLength(1); + expect(requests[0].searchParams.get("f_TPR")).toBe("r604800"); + expect(requests[0].searchParams.get("start")).toBe("0"); + const output = JSON.parse(result.stdout); + expect(output.meta).toEqual({ count: 2, page: 1 }); + expect(output.results.map((job: { id: string }) => job.id)).toEqual(["4425498329", "4425494976"]); }); }); diff --git a/.agents/skills/linkedin-search/cli/tests/fixtures/search-cards.json b/.agents/skills/linkedin-search/cli/tests/fixtures/search-cards.json new file mode 100644 index 00000000..04f2848d --- /dev/null +++ b/.agents/skills/linkedin-search/cli/tests/fixtures/search-cards.json @@ -0,0 +1 @@ +"
...
` now expects the text, plus two new cases; the HTML case fails on master. +- **Valid portal flag tests stay offline** (#581) - success controls use local fixtures instead of depending on live job portals or their credentials. + - **`job_key.py` no longer gives two postings one key when a title or company is partly non-Latin** (`tools/job_key.py`, `tests/test_job_key.py`) - the hash fallbacks from #487 and #502 fired only when the slug was completely empty, so a mixed-script name kept its