Files
Jyotisha/frontend/tests/birth-time-evidence.test.ts
T
2026-07-18 18:40:23 +08:00

131 lines
4.8 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import {
candidateResultSchema,
lifeEventSchema,
withConfirmedCandidate,
} from "../src/lib/birth-time-evidence.ts";
import { journeySnapshotSchema } from "../src/lib/birth-time-journey.ts";
import { evidenceDraftSchema } from "../src/lib/birth-time-journey-turn.ts";
const baseEvent = {
id: "5cb071d6-6d99-46be-85dc-a9bf59ef6ac5",
domain: "career",
} as const;
test("life event dates must match their declared precision", () => {
assert.equal(lifeEventSchema.safeParse({ ...baseEvent, precision: "year", date: "2011" }).success, true);
assert.equal(lifeEventSchema.safeParse({ ...baseEvent, precision: "month", date: "2011-09" }).success, true);
assert.equal(lifeEventSchema.safeParse({ ...baseEvent, precision: "day", date: "2011-09-24" }).success, true);
assert.equal(lifeEventSchema.safeParse({ ...baseEvent, precision: "year", date: "2011-09" }).success, false);
assert.equal(lifeEventSchema.safeParse({ ...baseEvent, precision: "month", date: "2011" }).success, false);
assert.equal(lifeEventSchema.safeParse({ ...baseEvent, precision: "day", date: "2011" }).success, false);
assert.equal(lifeEventSchema.safeParse({ ...baseEvent, precision: "year", date: "2011.5" }).success, false);
});
test("life event day precision rejects impossible calendar dates", () => {
assert.equal(lifeEventSchema.safeParse({ ...baseEvent, precision: "day", date: "2024-02-29" }).success, true);
assert.equal(lifeEventSchema.safeParse({ ...baseEvent, precision: "day", date: "2023-02-29" }).success, false);
assert.equal(lifeEventSchema.safeParse({ ...baseEvent, precision: "day", date: "2023-04-31" }).success, false);
});
test("evidence drafts carry one server-selected event and no confirmed events", () => {
const parsed = evidenceDraftSchema.parse({
draftId: baseEvent.id,
questionId: "baseline_career_1",
domain: "career",
precision: "month",
date: "2019-07",
status: "draft",
needsReview: false,
});
assert.equal(parsed.draftId, baseEvent.id);
assert.equal(parsed.questionId, "baseline_career_1");
assert.equal(parsed.status, "draft");
assert.equal("events" in parsed, false);
assert.equal("id" in parsed, false);
});
test("incomplete draft fields remain nullable and review-only", () => {
const parsed = evidenceDraftSchema.parse({
draftId: baseEvent.id,
questionId: "adaptive_career_1",
domain: "career",
precision: null,
date: null,
status: "draft",
needsReview: true,
});
assert.equal(parsed.precision, null);
assert.equal(parsed.date, null);
assert.equal(parsed.needsReview, true);
});
test("evidence drafts reject extra confirmed-event payloads", () => {
assert.equal(evidenceDraftSchema.safeParse({
draftId: baseEvent.id,
questionId: "baseline_career_1",
domain: "career",
precision: "year",
date: "2019",
status: "draft",
needsReview: false,
events: [{ ...baseEvent, precision: "year", date: "2019" }],
}).success, false);
});
const candidateBase = {
resultId: "8c48d5a8-cf2a-43a5-90f9-e39a726de265",
confidence: "high",
canApply: true,
winningSegment: {
startTime: "14:22",
endTime: "14:26",
representativeTime: "14:24",
widthMinutes: 5,
},
eventCount: 4,
domainCount: 3,
topScore: 18,
secondScore: 12,
marginPercent: 50,
reasons: ["One segment has consistent evidence."],
evidence: [],
algorithmVersion: "birth-time-event-scoring-v1",
} as const;
test("high candidate results require every deterministic safety gate", () => {
assert.equal(candidateResultSchema.safeParse(candidateBase).success, true);
assert.equal(candidateResultSchema.safeParse({ ...candidateBase, eventCount: 3 }).success, false);
assert.equal(candidateResultSchema.safeParse({ ...candidateBase, domainCount: 2 }).success, false);
assert.equal(candidateResultSchema.safeParse({ ...candidateBase, canApply: false, winningSegment: null }).success, false);
assert.equal(candidateResultSchema.safeParse({
...candidateBase,
winningSegment: { ...candidateBase.winningSegment, widthMinutes: 6 },
}).success, false);
assert.equal(candidateResultSchema.safeParse({ ...candidateBase, marginPercent: 19 }).success, false);
});
test("confirmation rejects a high result forged below the deterministic gates", () => {
const candidate = candidateResultSchema.parse(candidateBase);
const forged = { ...candidate, eventCount: 3 };
const snapshot = journeySnapshotSchema.parse({
state: "confirming",
assistantIntent: "confirm_candidate_time",
input: "candidate_confirmation",
route: "rectification",
confidence: "high",
canApply: true,
activeTime: null,
reportedRange: { label: "14:22—14:26", startTime: "14:22", endTime: "14:26" },
});
assert.throws(
() => withConfirmedCandidate(snapshot, forged, "14:24"),
{ name: "CandidateConfirmationError" },
);
});