mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 00:26:26 +00:00
fix(jobbank-search): degrade an unparseable pubDate to a null date instead of crashing the search (#416) (#417)
new Date(<unparseable>) yields an Invalid Date whose toISOString()
throws RangeError, and normalizeSearchItem runs inside an unguarded
items.map(), so one malformed RSS item killed the entire search with
{"error": "Invalid Date", "code": "API_ERROR"} and exit 1. The
un-CDATA'd fallback capture in parseRssItems can deliver exactly such a
value. An unparseable pubDate now degrades to the same shape as an
absent one (posted "", date null); every other item survives. Three
new cases pin the malformed shapes, each failing on the unfixed code.
This commit is contained in:
@@ -5,7 +5,13 @@ import { rssFetch, fetchWithUA, writeError, parseRssDescription, extractJobIdFro
|
|||||||
export function normalizeSearchItem(item: RssItem): Record<string, unknown> {
|
export function normalizeSearchItem(item: RssItem): Record<string, unknown> {
|
||||||
const parsed = parseRssDescription(item.description)
|
const parsed = parseRssDescription(item.description)
|
||||||
const id = extractJobIdFromUrl(item.link)
|
const id = extractJobIdFromUrl(item.link)
|
||||||
const posted = item.pubDate ? new Date(item.pubDate).toISOString() : ""
|
// Guard the parse: new Date(<unparseable>) is an Invalid Date whose
|
||||||
|
// toISOString() throws RangeError, and this runs inside an unguarded
|
||||||
|
// items.map() - one bad feed item would kill the whole search as
|
||||||
|
// API_ERROR (#416). An unparseable pubDate degrades to the same shape
|
||||||
|
// as an absent one: posted "", date null.
|
||||||
|
const parsedDate = item.pubDate ? new Date(item.pubDate) : null
|
||||||
|
const posted = parsedDate && !Number.isNaN(parsedDate.getTime()) ? parsedDate.toISOString() : ""
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
title: item.title,
|
title: item.title,
|
||||||
|
|||||||
@@ -27,6 +27,21 @@ describe("Jobbank search normalization", () => {
|
|||||||
expect(result.date).toBeNull();
|
expect(result.date).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// A present-but-unparseable pubDate must degrade to the same null-date shape
|
||||||
|
// as an absent one, never throw: toISOString() on an Invalid Date raises
|
||||||
|
// RangeError, and normalizeSearchItem runs inside an unguarded items.map(),
|
||||||
|
// so one bad feed item killed the whole search as API_ERROR (#416). The
|
||||||
|
// un-CDATA'd fallback capture in parseRssItems can deliver exactly such a
|
||||||
|
// value.
|
||||||
|
for (const bad of ["date unavailable", "2026-09-02T08:00:00+02:00x", "I går"]) {
|
||||||
|
test(`emits a null date instead of throwing on unparseable pubDate ${JSON.stringify(bad)}`, () => {
|
||||||
|
const result = normalizeSearchItem({ ...rssItem(), pubDate: bad });
|
||||||
|
|
||||||
|
expect(result.posted).toBe("");
|
||||||
|
expect(result.date).toBeNull();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
test("keeps the native fields alongside the contract date (additive)", () => {
|
test("keeps the native fields alongside the contract date (additive)", () => {
|
||||||
const result = normalizeSearchItem(rssItem());
|
const result = normalizeSearchItem(rssItem());
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,16 @@ per-file diff commands.
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- **`jobbank-search` no longer dies over one malformed feed date** (#416) - `new Date()`
|
||||||
|
on a present-but-unparseable `pubDate` yields an Invalid Date whose `toISOString()`
|
||||||
|
throws `RangeError`, and `normalizeSearchItem` runs inside an unguarded `items.map()`,
|
||||||
|
so a single bad RSS item killed the entire search with `{"error": "Invalid Date",
|
||||||
|
"code": "API_ERROR"}` and exit 1 - a whole default-ON portal lost to one item, with
|
||||||
|
the error pointing at the API. The un-CDATA'd fallback capture in `parseRssItems` can
|
||||||
|
deliver exactly such a value. An unparseable `pubDate` now degrades to the same shape
|
||||||
|
as an absent one (`posted` empty, `date: null`, per the `seen_jobs.json` contract that
|
||||||
|
#391 put this field on), and every other item survives. Pinned by three new cases in
|
||||||
|
`search-normalization.test.ts`, each verified to fail on the unfixed code.
|
||||||
- **`linkedin-search` rejects fractional numeric flags instead of silently changing
|
- **`linkedin-search` rejects fractional numeric flags instead of silently changing
|
||||||
the query** (#371) - bare `parseInt` truncated values before validation, so
|
the query** (#371) - bare `parseInt` truncated values before validation, so
|
||||||
`--jobage 0.5` became `0` and silently omitted LinkedIn's `f_TPR` freshness filter
|
`--jobage 0.5` became `0` and silently omitted LinkedIn's `f_TPR` freshness filter
|
||||||
|
|||||||
Reference in New Issue
Block a user