Files
ai-job-search/.agents/skills/freehire-search/cli/tests/parsing.test.ts
T
Sahil Kumar SahuandClaude Opus 5.5 9187f894a5 fix(freehire-search): keep the salary period so a yearly figure isn't read as monthly (#504)
formatSalary printed only the currency and amounts, although freehire's
enrichment records the pay period. `detail software-developer-trainee-codifi-fra635ux`
printed `INR 300000–300000` while the API returns `salary_period: "year"`
for that posting, so a yearly salary screened against a monthly figure
read as twelve times its real monthly value.

Append the period when freehire records one (`INR 300000–300000/year`,
`INR 12000/month`); a record without a period prints exactly as before.
Two new parsing.test.ts cases pin it; both fail on the old formatter.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 20:22:04 +02:00

115 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, test, expect } from "bun:test";
import { cleanHtml, normalizeSlug, toResult, toDetail, type FreehireJob } from "../src/helpers";
function job(overrides: Partial<FreehireJob> = {}): FreehireJob {
return {
public_slug: "backend-engineer-acme-ab12cd34",
source: "greenhouse",
external_id: "acme:1",
url: "https://boards.greenhouse.io/acme/jobs/1",
title: "Backend Engineer",
company: "Acme",
company_slug: "acme",
location: "Berlin, Germany",
description: "<ul><li>Build APIs</li><li>Ship &amp; iterate</li></ul>",
skills: ["go", "kubernetes"],
work_mode: "remote",
regions: ["eu"],
countries: ["de"],
cities: ["Berlin"],
posted_at: "2026-07-06T00:00:00Z",
created_at: "2026-07-06T15:00:00Z",
enrichment: {},
...overrides,
};
}
describe("toResult — reshape into the portal-skill contract", () => {
test("maps public_slug -> id and posted_at -> date", () => {
const r = toResult(job());
expect(r.id).toBe("backend-engineer-acme-ab12cd34");
expect(r.date).toBe("2026-07-06T00:00:00Z");
});
test("carries the required contract fields", () => {
const r = toResult(job());
expect(r).toMatchObject({
title: "Backend Engineer",
company: "Acme",
company_slug: "acme",
location: "Berlin, Germany",
url: "https://boards.greenhouse.io/acme/jobs/1",
});
});
test("missing values are null, not omitted", () => {
const r = toResult(job({ company: "", location: "", posted_at: null, work_mode: undefined }));
expect(r.company).toBeNull();
expect(r.location).toBeNull();
expect(r.date).toBeNull();
expect(r.work_mode).toBeNull();
});
});
describe("toDetail — adds cleaned description + enrichment", () => {
test("strips HTML and decodes entities in the description", () => {
const d = toDetail(job());
expect(d.description).toBe("Build APIs\nShip & iterate");
});
test("surfaces enrichment fields and a formatted salary", () => {
const d = toDetail(
job({
enrichment: { seniority: "senior", category: "backend", employment_type: "full_time", salary_min: 90000, salary_max: 120000, salary_currency: "EUR" },
}),
);
expect(d.seniority).toBe("senior");
expect(d.category).toBe("backend");
expect(d.salary).toBe("EUR 90000–120000");
});
test("keeps the salary period, so a yearly figure is not read as monthly", () => {
const d = toDetail(
job({ enrichment: { salary_min: 300000, salary_max: 300000, salary_currency: "INR", salary_period: "year" } }),
);
expect(d.salary).toBe("INR 300000–300000/year");
});
test("a single-bound salary keeps its period too", () => {
const d = toDetail(job({ enrichment: { salary_min: 12000, salary_currency: "INR", salary_period: "month" } }));
expect(d.salary).toBe("INR 12000/month");
});
test("null enrichment fields when the enrichment object is empty", () => {
const d = toDetail(job({ enrichment: {} }));
expect(d.seniority).toBeNull();
expect(d.salary).toBeNull();
});
});
describe("cleanHtml", () => {
test("preserves paragraph breaks between blocks", () => {
expect(cleanHtml("<p>One</p><p>Two</p>")).toBe("One\nTwo");
});
test("decodes hex numeric entities", () => {
expect(cleanHtml("Caf&#xE9;")).toBe("Café");
});
test("returns null for empty input", () => {
expect(cleanHtml("")).toBeNull();
expect(cleanHtml(null)).toBeNull();
});
});
describe("normalizeSlug", () => {
test("accepts a bare slug", () => {
expect(normalizeSlug("golang-zensar-2bxu6dxm")).toBe("golang-zensar-2bxu6dxm");
});
test("extracts the slug from a /jobs/<slug> URL", () => {
expect(normalizeSlug("https://freehire.me/jobs/golang-zensar-2bxu6dxm")).toBe("golang-zensar-2bxu6dxm");
});
test("rejects a non-slug string", () => {
expect(normalizeSlug("not a slug!")).toBeNull();
expect(normalizeSlug("")).toBeNull();
});
});