fix(jobnet-search): degrade a null publicationDate to a null date instead of crashing the search (#418) (#419)

date: job.publicationDate.slice(0, 10) trusted a TypeScript interface
claim nothing validates at runtime: apiFetch casts the JSON body, so one
ad with a null publication date threw TypeError inside the jobAds map
and the whole search exited 1 as API_ERROR. The neighboring
applicationDeadline field was already null-guarded with a 1900-01-01
sentinel. publicationDate is now typed nullable so the compiler enforces
the guard, and the ad degrades per-item to date: null. New test verified
to fail on the unfixed code with the exact production TypeError.
This commit is contained in:
Ayobami Adegoke
2026-09-03 19:33:54 +02:00
committed by GitHub
parent 6f0178a8a1
commit ba9b1d8370
3 changed files with 38 additions and 4 deletions
@@ -18,7 +18,11 @@ export interface JobAdRaw {
postalCode: number | null
postalDistrictName: string | null
country: string
publicationDate: string
// A TypeScript claim is not runtime validation: apiFetch casts the JSON
// body, so a null here arrives typed as string and .slice() throws,
// killing the whole search as API_ERROR (#418). Typed nullable so the
// compiler enforces the guard below.
publicationDate: string | null
applicationDeadline: string | null
applicationDeadlineStatus: string | null
workHourPartTime: boolean
@@ -102,7 +106,7 @@ export function createSearchOutput(data: SearchApiResponse, flags: SearchFlags)
isFavorite: job.isFavorite,
company: job.hiringOrgName,
location: job.postalDistrictName ?? job.municipality ?? null,
date: job.publicationDate.slice(0, 10),
date: job.publicationDate ? job.publicationDate.slice(0, 10) : null,
deadline: job.applicationDeadline && !job.applicationDeadline.startsWith("1900-01-01")
? job.applicationDeadline.slice(0, 10)
: null,
@@ -211,7 +215,7 @@ type JobAdResult = {
occupation: string | null
municipality: string | null
postalCode: number | null
publicationDate: string
publicationDate: string | null
applicationDeadline: string | null
}
@@ -161,3 +161,23 @@ describe("Jobnet search normalization", () => {
expect(output.results[1].deadline).toBe("2026-08-01");
});
});
describe("Jobnet null publicationDate degradation", () => {
// publicationDate: string was a TypeScript claim, not runtime validation -
// apiFetch casts the JSON body, so one ad with a null publication date
// threw TypeError from .slice() inside the jobAds map and killed the whole
// search as API_ERROR (#418). The neighboring applicationDeadline field is
// already guarded (null check + 1900-01-01 sentinel); this pins the same
// per-item degradation for publicationDate: date null, no throw.
test("an ad with a null publicationDate yields date: null instead of crashing the search", () => {
const data = apiResponse();
data.jobAds[0].publicationDate = null;
// The shared fixture flags carry limit: 1, which would slice off the
// second ad; lift the limit so the survives-alongside assertion is real.
const output = createSearchOutput(data, { ...flags, limit: undefined });
expect(output.results[0].date).toBeNull();
expect(output.results[1].date).toBe("2026-07-02");
});
});