mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 08:36:25 +00:00
feat(linkedin-search): add --jobage-minutes for sub-day freshness windows (#302)
jobageToTPR() only emits whole-day f_TPR windows, so a search can't be restricted to postings from the last N minutes. LinkedIn's f_TPR filters server-side down to one-second granularity (confirmed empirically), so this is a pure window-construction change via a new minutesToTPR() helper - no HTML parsing changes needed. --jobage-minutes and --jobage both express a freshness window; passing both is rejected with CONFLICTING_AGE_FLAGS rather than one silently overriding the other.
This commit is contained in:
@@ -47,6 +47,48 @@ describe("LinkedIn CLI flag validation", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("--jobage-minutes validation", () => {
|
||||
test("non-numeric string exits 1 with BAD_ARG", async () => {
|
||||
const result = await runCLI(["search", "-l", LOCATION, "--jobage-minutes", "foo"]);
|
||||
expect(result.exitCode).not.toBe(0);
|
||||
const err = parsedStderr(result.stderr);
|
||||
expect(err.code).toBe("BAD_ARG");
|
||||
expect(err.error).toMatch(/jobage-minutes/);
|
||||
});
|
||||
|
||||
test("zero exits 1 with BAD_ARG", async () => {
|
||||
const result = await runCLI(["search", "-l", LOCATION, "--jobage-minutes", "0"]);
|
||||
expect(result.exitCode).not.toBe(0);
|
||||
const err = parsedStderr(result.stderr);
|
||||
expect(err.code).toBe("BAD_ARG");
|
||||
expect(err.error).toMatch(/jobage-minutes/);
|
||||
});
|
||||
|
||||
test("negative value is parsed as a missing value and exits 1 with BAD_ARG", async () => {
|
||||
// parseFlags in cli.ts treats a next-token starting with "-" as absent
|
||||
// (`next.startsWith("-")` → flag becomes boolean `true`), and there is no
|
||||
// `--flag=value` syntax. So "-5" never reaches --jobage-minutes as a value;
|
||||
// parseInt("true") is NaN, and BAD_ARG comes from the NaN branch, not the
|
||||
// `v <= 0` guard. Negatives are unreachable through the CLI as currently parsed.
|
||||
const result = await runCLI(["search", "-l", LOCATION, "--jobage-minutes", "-5"]);
|
||||
expect(result.exitCode).not.toBe(0);
|
||||
const err = parsedStderr(result.stderr);
|
||||
expect(err.code).toBe("BAD_ARG");
|
||||
expect(err.error).toMatch(/jobage-minutes/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("--jobage / --jobage-minutes conflict", () => {
|
||||
test("both set exits 1 with CONFLICTING_AGE_FLAGS", async () => {
|
||||
const result = await runCLI([
|
||||
"search", "-l", LOCATION, "--jobage", "7", "--jobage-minutes", "30",
|
||||
]);
|
||||
expect(result.exitCode).not.toBe(0);
|
||||
const err = parsedStderr(result.stderr);
|
||||
expect(err.code).toBe("CONFLICTING_AGE_FLAGS");
|
||||
});
|
||||
});
|
||||
|
||||
describe("--page NaN validation", () => {
|
||||
test("non-numeric string exits 1 with BAD_ARG", async () => {
|
||||
const result = await runCLI(["search", "-l", LOCATION, "--page", "abc"]);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { parseJobCards, parseJobDetail, extractDivContent } from "../src/helpers";
|
||||
import { parseJobCards, parseJobDetail, extractDivContent, minutesToTPR } from "../src/helpers";
|
||||
|
||||
// Minimal search-card markup: parseJobCards splits on the job-posting URN and
|
||||
// needs an id, a base-search-card__title, and a full-link. Everything else is
|
||||
@@ -111,3 +111,16 @@ describe("extractDivContent", () => {
|
||||
expect(job.description).toContain("We are hiring!");
|
||||
});
|
||||
});
|
||||
|
||||
describe("minutesToTPR", () => {
|
||||
test("converts minutes to an f_TPR seconds window", () => {
|
||||
expect(minutesToTPR(30)).toBe("r1800");
|
||||
expect(minutesToTPR(1)).toBe("r60");
|
||||
expect(minutesToTPR(1440)).toBe("r86400"); // matches jobageToTPR(1)
|
||||
});
|
||||
|
||||
test("returns null for non-positive input", () => {
|
||||
expect(minutesToTPR(0)).toBeNull();
|
||||
expect(minutesToTPR(-5)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,4 +39,23 @@ describe("runSearch", () => {
|
||||
expect(code).toBe(0);
|
||||
expect(JSON.parse(stdout).results).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("--jobage-minutes 30 constructs f_TPR=r1800 in the request URL", async () => {
|
||||
let capturedUrl = "";
|
||||
globalThis.fetch = (async (input: RequestInfo | URL) => {
|
||||
capturedUrl = typeof input === "string" ? input : input.toString();
|
||||
return new Response("");
|
||||
}) as typeof fetch;
|
||||
|
||||
const code = await runSearch({
|
||||
location: "Remote",
|
||||
jobage: 9999,
|
||||
jobageMinutes: 30,
|
||||
page: 1,
|
||||
format: "json",
|
||||
});
|
||||
|
||||
expect(code).toBe(0);
|
||||
expect(capturedUrl).toContain("f_TPR=r1800");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user