mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 00:26:26 +00:00
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:
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
+11
-1
@@ -41,13 +41,23 @@ per-file diff commands.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **`jobnet-search` no longer dies over one ad with a null publication date** (#418, the
|
||||
sibling of #416 from the same audit) - `date: job.publicationDate.slice(0, 10)` trusted
|
||||
a TypeScript interface claim (`publicationDate: string`) that nothing validates at
|
||||
runtime: `apiFetch` casts the JSON body, so one `null` threw `TypeError` inside the
|
||||
`jobAds` map and the whole search of a default-ON portal exited 1 as `API_ERROR` - while
|
||||
the neighboring `applicationDeadline` field was already null-guarded with a `1900-01-01`
|
||||
sentinel check. The field is now typed nullable (so the compiler enforces the guard) and
|
||||
degrades per-item to `date: null`, the shape the `seen_jobs.json` contract documents.
|
||||
Pinned by a new case in `search-normalization.test.ts`, verified to fail on the unfixed
|
||||
code with the exact production TypeError.
|
||||
|
||||
- **Placeholder-integrity tests in `python-tests` now skip on forks** (#405) - the dedicated
|
||||
`placeholder-integrity` job already gates on the upstream repo name, but `python-tests` ran
|
||||
`unittest discover` with no such guard, so forks that personalized files via `/setup` failed
|
||||
three sentinel checks permanently. Both test classes now use `@unittest.skipIf` on
|
||||
`GITHUB_REPOSITORY` (defaulting to upstream when unset so local pristine-template runs still
|
||||
execute).
|
||||
|
||||
- **`convert_salary_excel.py` no longer mistakes a title/citation row for the header row**
|
||||
(#414) - header-row detection accepted the first row in the first 10 where *any* cell merely
|
||||
contained a company-pattern word, with no check that the row actually looked like a header. A
|
||||
|
||||
Reference in New Issue
Block a user