mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-11-02 13:12:50 +00:00
* 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
73 lines
2.3 KiB
TypeScript
73 lines
2.3 KiB
TypeScript
import { join } from "path";
|
|
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
|
|
const CLI_PATH = join(import.meta.dir, "../src/cli.ts");
|
|
|
|
export interface CLIResult {
|
|
stdout: string;
|
|
stderr: string;
|
|
exitCode: number;
|
|
}
|
|
|
|
export async function runCLI(args: string[], preload?: string): Promise<CLIResult> {
|
|
const proc = Bun.spawn(["bun", "run", ...(preload ? ["--preload", preload] : []), CLI_PATH, ...args], {
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const [stdout, stderr, exitCode] = await Promise.all([
|
|
new Response(proc.stdout).text(),
|
|
new Response(proc.stderr).text(),
|
|
proc.exited,
|
|
]);
|
|
|
|
return { stdout: stdout.trim(), stderr: stderr.trim(), exitCode };
|
|
}
|
|
|
|
/** Run valid search controls through the real CLI with a saved, offline response. */
|
|
export async function runSearchFixture(args: string[]): Promise<{ result: CLIResult; requests: URL[] }> {
|
|
const requests: URL[] = [];
|
|
const server = Bun.serve({
|
|
hostname: "127.0.0.1",
|
|
port: 0,
|
|
async fetch(request) {
|
|
requests.push(new URL(request.url));
|
|
return new Response(await Bun.file(join(import.meta.dir, "fixtures/search-cards.json")).json());
|
|
},
|
|
});
|
|
const dir = mkdtempSync(join(tmpdir(), "linkedin-valid-flags-"));
|
|
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.origin !== "https://www.linkedin.com" || url.pathname !== "/jobs-guest/jobs/api/seeMoreJobPostings/search") {
|
|
throw new Error("Unexpected search fixture request");
|
|
}
|
|
return originalFetch(${JSON.stringify(server.url.origin)} + url.pathname + url.search, init);
|
|
};
|
|
`);
|
|
try {
|
|
return { result: await runCLI(args, preload), requests };
|
|
} finally {
|
|
server.stop(true);
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
export function parseJSON<T = unknown>(result: CLIResult): T {
|
|
if (result.exitCode !== 0) {
|
|
throw new Error(
|
|
`CLI exited with code ${result.exitCode}. stderr: ${result.stderr}`
|
|
);
|
|
}
|
|
try {
|
|
return JSON.parse(result.stdout) as T;
|
|
} catch {
|
|
throw new Error(
|
|
`Failed to parse JSON. stdout: ${result.stdout}\nstderr: ${result.stderr}`
|
|
);
|
|
}
|
|
}
|