fix(jobnet-search): map the 1900-01-01 deadline sentinel to null in detail

search guards the API's undisclosed-deadline sentinel and a test pins
it; detail dumped the raw response, so the same field for the same job
behaved two ways, and an undisclosed deadline stored via detail read as
126 years expired - /rank's sweep would retire the job on sight. All
three output formats now flow through prepareDetail. Review finding F33
(2026-08-19).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mads Lorentzen
2026-08-19 20:50:55 +02:00
co-authored by Claude Opus 5
parent 07cec1f227
commit bcba687fbf
3 changed files with 55 additions and 4 deletions
@@ -59,6 +59,23 @@ export interface DetailApiResponse {
user: string | null
}
/**
* Normalize a raw detail response before any output format sees it.
*
* The API's "deadline not disclosed" sentinel is 1900-01-01 (it arrives with
* isApplicationDeadlineASAP / an applicationDeadlineStatus of NotDisclosed).
* The search command already maps that sentinel to null; detail must agree,
* or an undisclosed deadline reads as 126 years expired and /rank's expiry
* sweep retires the job the moment it is stored.
*/
export function prepareDetail(data: DetailApiResponse): DetailApiResponse {
const deadline = data.application.deadlineDate
if (deadline && deadline.startsWith("1900-01-01")) {
data.application.deadlineDate = null
}
return data
}
export const detail = defineCommand({
name: "detail",
description: "Full detail for a single job ad",
@@ -77,9 +94,10 @@ export const detail = defineCommand({
}
try {
const data = await apiFetch<DetailApiResponse>(
`/FindJob/JobAdDetails/${id}`,
{ incrementViews: "false" }
const data = prepareDetail(
await apiFetch<DetailApiResponse>(`/FindJob/JobAdDetails/${id}`, {
incrementViews: "false",
}),
)
if (signal.aborted) return
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { formatDetailPlain, type DetailApiResponse } from "../src/commands/detail";
import { formatDetailPlain, prepareDetail, type DetailApiResponse } from "../src/commands/detail";
function detail(overrides: Partial<DetailApiResponse> = {}): DetailApiResponse {
return {
@@ -93,3 +93,29 @@ describe("formatDetailPlain", () => {
expect(formatted).not.toContain("Apply:");
});
});
describe("prepareDetail deadline sentinel", () => {
// The API's "deadline not disclosed" sentinel is 1900-01-01 (paired with
// isApplicationDeadlineASAP / applicationDeadlineStatus). search maps it to
// null and has a test pinning that; detail dumped the raw response, so an
// undisclosed deadline read as 126 years expired and /rank's sweep would
// retire the job instantly (review finding F33, 2026-08-19).
test("maps the 1900-01-01 undisclosed sentinel to null", () => {
const data = detail();
data.application.deadlineDate = "1900-01-01T00:00:00+01:00";
expect(prepareDetail(data).application.deadlineDate).toBeNull();
});
test("keeps a real deadline unchanged", () => {
const data = detail();
data.application.deadlineDate = "2026-09-01T00:00:00+02:00";
expect(prepareDetail(data).application.deadlineDate).toBe("2026-09-01T00:00:00+02:00");
});
test("keeps a null deadline null", () => {
const data = detail();
data.application.deadlineDate = null;
expect(prepareDetail(data).application.deadlineDate).toBeNull();
});
});