fix(linkedin-search): accept LinkedIn job URLs with trailing slashes in detail command (#411) (#412)

* fix(linkedin-search): accept LinkedIn job URLs with trailing slashes in detail command (#411)

* docs(changelog): record linkedin-search trailing-slash fix (#411)
This commit is contained in:
Abhinav
2026-09-01 21:35:06 +02:00
committed by GitHub
parent 4c38f7ce4c
commit 6ef295bf7b
3 changed files with 63 additions and 2 deletions
@@ -6,10 +6,10 @@ export interface DetailOpts {
}
/** Accept a raw job ID, a job-view URL, or a job URN. */
function normalizeId(input: string): string | null {
export function normalizeId(input: string): string | null {
const urn = input.match(/urn:li:jobPosting:(\d+)/)
if (urn) return urn[1]
const url = input.match(/-(\d{6,})(?:\?|$)/) || input.match(/\/(\d{6,})(?:\?|$)/)
const url = input.match(/-(\d{6,})(?:[\/?]|$)/) || input.match(/\/(\d{6,})(?:[\/?]|$)/)
if (url) return url[1]
const bare = input.match(/^\d{6,}$/)
if (bare) return input
@@ -1,5 +1,6 @@
import { describe, test, expect } from "bun:test";
import { parseJobCards, parseJobDetail, extractDivContent, minutesToTPR } from "../src/helpers";
import { normalizeId } from "../src/commands/detail";
// 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
@@ -222,3 +223,55 @@ describe("minutesToTPR", () => {
expect(minutesToTPR(-5)).toBeNull();
});
});
describe("normalizeId", () => {
test("extracts ID from raw numeric string", () => {
expect(normalizeId("1234567890")).toBe("1234567890");
});
test("extracts ID from URN", () => {
expect(normalizeId("urn:li:jobPosting:1234567890")).toBe("1234567890");
});
test("extracts ID from simple job view URL without trailing slash", () => {
expect(normalizeId("https://www.linkedin.com/jobs/view/1234567890")).toBe("1234567890");
});
test("extracts ID from simple job view URL with trailing slash", () => {
expect(normalizeId("https://www.linkedin.com/jobs/view/1234567890/")).toBe("1234567890");
});
test("extracts ID from simple job view URL with query parameter", () => {
expect(normalizeId("https://www.linkedin.com/jobs/view/1234567890?refId=abc")).toBe("1234567890");
});
test("extracts ID from simple job view URL with trailing slash and query parameter", () => {
expect(normalizeId("https://www.linkedin.com/jobs/view/1234567890/?refId=abc")).toBe("1234567890");
});
test("extracts ID from slug URL without trailing slash", () => {
expect(normalizeId("https://www.linkedin.com/jobs/view/software-engineer-1234567890")).toBe("1234567890");
});
test("extracts ID from slug URL with trailing slash", () => {
expect(normalizeId("https://www.linkedin.com/jobs/view/software-engineer-1234567890/")).toBe("1234567890");
});
test("extracts ID from slug URL with trailing slash and tracking query params", () => {
expect(
normalizeId("https://www.linkedin.com/jobs/view/software-engineer-at-company-1234567890/?trackingId=xyz&refId=123"),
).toBe("1234567890");
});
test("extracts ID from regional subdomain LinkedIn URL with trailing slash", () => {
expect(normalizeId("https://dk.linkedin.com/jobs/view/data-scientist-9876543210/")).toBe("9876543210");
});
test("returns null for non-job URLs and invalid strings", () => {
expect(normalizeId("https://www.linkedin.com/feed/")).toBeNull();
expect(normalizeId("not-a-url")).toBeNull();
expect(normalizeId("12345")).toBeNull(); // fewer than 6 digits
expect(normalizeId("")).toBeNull();
});
});
+8
View File
@@ -26,6 +26,14 @@ per-file diff commands.
### Fixed
- **`linkedin-search detail` accepts LinkedIn job URLs with trailing slashes** (#411) -
passing a job URL with a trailing slash (e.g., `https://www.linkedin.com/jobs/view/<id>/`
or a slugged variant with or without query strings) failed validation and exited 1 with
`BAD_ID` before any network request because the regex delimiter strictly expected `?`
or end-of-string immediately after the numeric ID. The boundary check now matches
`[\/?]`, correctly extracting IDs from browser-copied URLs, regional subdomains, and
links with tracking parameters. Pinned by eleven new cases in `parsing.test.ts`.
- **The `documents/interview/**` ignore rule no longer claims interview prep is written there**
(#336). `/interview` saves its pack to
`documents/applications/<company>_<role>/interview_prep_<stage>.md`, already ignored by