import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import { applyBirthTimeDraftPatch, applyPersistedBirthTime, assistantIntentCopy, birthTimeDisplayState, birthTimeDraftReadyHint, birthTimePersistenceValues, birthTimeSourceDefaults, birthTimeSourceOptions, describeBirthTimeDraft, formatBirthDate, isDeclaredBirthProfileComplete, isBirthTimeReadyForConsultation, isBirthTimeDraftReady, normalizePersistedBirthDate, parseBirthDate, type BirthTimeDraft, } from "../src/lib/birth-time-intake-model.ts"; const emptyDraft: BirthTimeDraft = { date: "1993-04-17", time: "", reportedTime: "", birthTimeSource: "", birthTimePeriod: "", birthTimeClue: "", uncertaintyBeforeMinutes: null, uncertaintyAfterMinutes: null, birthTimeStatus: "", }; test("birth time intake requires only the fields selected by the source", () => { const hospital = { ...emptyDraft, birthTimeSource: "hospital_record", reportedTime: "08:16", uncertaintyBeforeMinutes: 2, uncertaintyAfterMinutes: 2, } satisfies BirthTimeDraft; const period = { ...emptyDraft, birthTimeSource: "period_only", birthTimePeriod: "evening", } satisfies BirthTimeDraft; const incompleteApproximate = { ...emptyDraft, birthTimeSource: "approximate", reportedTime: "14:30", } satisfies BirthTimeDraft; assert.equal(isBirthTimeDraftReady(hospital), true); assert.equal(isBirthTimeDraftReady(period), true); assert.equal(isBirthTimeDraftReady(incompleteApproximate), true); assert.equal(isBirthTimeDraftReady({ ...emptyDraft, birthTimeSource: "unknown" }), true); }); test("family exact intake defaults and persists as reported 0/0 without engine confirmation", () => { assert.deepEqual(birthTimeSourceDefaults.family_exact, { birthTimePeriod: "", birthTimeClue: "", uncertaintyBeforeMinutes: 0, uncertaintyAfterMinutes: 0, }); const declaredExact = { ...emptyDraft, reportedTime: "05:00", birthTimeSource: "family_exact", uncertaintyBeforeMinutes: 0, uncertaintyAfterMinutes: 0, birthTimeStatus: "reported", } satisfies BirthTimeDraft; assert.equal(isBirthTimeDraftReady(declaredExact), true); assert.equal(isBirthTimeReadyForConsultation(declaredExact), false); assert.deepEqual(birthTimePersistenceValues(declaredExact), { reported_birth_time: "05:00", birth_time_source: "family_exact", birth_time_period: null, birth_time_clue: null, uncertainty_before_minutes: 0, uncertainty_after_minutes: 0, }); }); test("an unconfirmed candidate working time remains in rectification onboarding", () => { const candidate = { ...emptyDraft, time: "04:53", birthTimeStatus: "candidate", } satisfies BirthTimeDraft; assert.equal(isBirthTimeReadyForConsultation(candidate), false); assert.equal(isBirthTimeReadyForConsultation({ ...candidate, time: "" }), false); assert.equal(isBirthTimeReadyForConsultation({ ...candidate, birthTimeStatus: "rectifying" }), false); assert.equal(isBirthTimeReadyForConsultation({ ...candidate, birthTimeStatus: "confirmed" }), true); }); test("declared birth data completes onboarding without an active or confirmed minute", () => { const declaredExact = { ...emptyDraft, reportedTime: "05:30", birthTimeSource: "family_exact", uncertaintyBeforeMinutes: 0, uncertaintyAfterMinutes: 0, birthTimeStatus: "reported", } satisfies BirthTimeDraft; const declaredPeriod = { ...emptyDraft, birthTimeSource: "period_only", birthTimePeriod: "early_morning", birthTimeStatus: "reported", } satisfies BirthTimeDraft; assert.equal(isDeclaredBirthProfileComplete(declaredExact), true); assert.equal(isBirthTimeReadyForConsultation(declaredExact), false); assert.equal(isDeclaredBirthProfileComplete(declaredPeriod), true); assert.equal(isBirthTimeReadyForConsultation(declaredPeriod), false); }); test("declared completeness validates the actual calendar date, clock, source fields, and bounds", () => { const exact = { ...emptyDraft, birthTimeSource: "family_exact", reportedTime: "05:30", uncertaintyBeforeMinutes: 0, uncertaintyAfterMinutes: 0, } satisfies BirthTimeDraft; assert.equal(isDeclaredBirthProfileComplete({ ...exact, date: "2000-02-29" }), true); assert.equal(isDeclaredBirthProfileComplete({ ...exact, date: "2001-02-29" }), false); assert.equal(isDeclaredBirthProfileComplete({ ...exact, date: "1899-12-31" }), false); assert.equal(isDeclaredBirthProfileComplete({ ...exact, date: "2101-01-01" }), false); assert.equal(isDeclaredBirthProfileComplete({ ...exact, reportedTime: "24:00" }), false); assert.equal(isDeclaredBirthProfileComplete({ ...exact, reportedTime: "5:30" }), false); assert.equal(isDeclaredBirthProfileComplete({ ...exact, uncertaintyBeforeMinutes: 7 }), false); assert.equal(isDeclaredBirthProfileComplete({ ...emptyDraft, birthTimeSource: "hospital_record", reportedTime: "05:30", }), false); assert.equal(isDeclaredBirthProfileComplete({ ...emptyDraft, birthTimeSource: "period_only", birthTimePeriod: "", }), false); assert.equal(isDeclaredBirthProfileComplete({ ...emptyDraft, birthTimeSource: "period_only", birthTimePeriod: "morning", uncertaintyBeforeMinutes: 30, }), false); assert.equal(isDeclaredBirthProfileComplete({ ...emptyDraft, birthTimeSource: "unknown", reportedTime: "05:30", }), false); assert.equal(isDeclaredBirthProfileComplete({ ...emptyDraft, birthTimeSource: "unknown", birthTimeClue: "x".repeat(241), }), false); assert.equal(isDeclaredBirthProfileComplete(exact, { label: "中国 · 河北省 · 邯郸市", lat: 36.62, lon: 114.49, tz: 8, }), true); assert.equal(isDeclaredBirthProfileComplete(exact, { label: "中国 · 河北省 · 邯郸市 · 峰峰矿区", lat: 36.420487, lon: 114.209936, tz: null, timezoneId: "Asia/Shanghai", }), true); assert.equal(isDeclaredBirthProfileComplete(exact, { label: "缺少时区的地点", lat: 36.420487, lon: 114.209936, tz: null, timezoneId: "", }), false); assert.equal(isDeclaredBirthProfileComplete(exact, { label: "越界地点", lat: 91, lon: 114.49, tz: 8, }), false); assert.equal(isDeclaredBirthProfileComplete(exact, null), false); }); test("editing an unconfirmed declaration invalidates an old candidate but preserves confirmed active time", () => { const candidate = { ...emptyDraft, time: "05:18", reportedTime: "05:30", birthTimeSource: "approximate", uncertaintyBeforeMinutes: 30, uncertaintyAfterMinutes: 30, birthTimeStatus: "candidate", } satisfies BirthTimeDraft; assert.deepEqual(applyBirthTimeDraftPatch(candidate, { reportedTime: "06:10" }), { ...candidate, time: "", reportedTime: "06:10", birthTimeStatus: "reported", }); assert.deepEqual(applyBirthTimeDraftPatch(candidate, { uncertaintyBeforeMinutes: 60, uncertaintyAfterMinutes: 60, }), { ...candidate, time: "", uncertaintyBeforeMinutes: 60, uncertaintyAfterMinutes: 60, birthTimeStatus: "reported", }); const confirmed = { ...candidate, birthTimeStatus: "confirmed" } satisfies BirthTimeDraft; assert.deepEqual(applyBirthTimeDraftPatch(confirmed, { reportedTime: "06:10" }), { ...confirmed, reportedTime: "06:10", }); }); test("a persisted candidate working time takes precedence over the reported range", () => { // Given: rectification saved a candidate minute while preserving the user's original period. const candidate = { ...emptyDraft, time: "04:53", birthTimeSource: "period_only", birthTimePeriod: "early_morning", birthTimeStatus: "candidate", } satisfies BirthTimeDraft; // When: a profile surface asks what birth-time state to display. const display = birthTimeDisplayState(candidate); // Then: the candidate minute is primary and the original period remains secondary. assert.deepEqual(display, { kind: "candidate", activeTime: "04:53", reportedLabel: "凌晨 / 清晨(04:00—07:59)", }); }); test("birth time declaration payload cannot write deterministic application fields", () => { const draft = { ...emptyDraft, time: "14:24", reportedTime: "14:30", birthTimeSource: "approximate", uncertaintyBeforeMinutes: 30, uncertaintyAfterMinutes: 30, birthTimeStatus: "confirmed", } satisfies BirthTimeDraft; assert.deepEqual(birthTimePersistenceValues(draft), { reported_birth_time: "14:30", birth_time_source: "approximate", birth_time_period: null, birth_time_clue: null, uncertainty_before_minutes: 30, uncertainty_after_minutes: 30, }); }); test("birth time intake describes uncertainty without claiming false precision", () => { const approximate = { ...emptyDraft, birthTimeSource: "approximate", reportedTime: "14:30", uncertaintyBeforeMinutes: 30, uncertaintyAfterMinutes: 30, } satisfies BirthTimeDraft; assert.equal(describeBirthTimeDraft(approximate), "1993年4月17日,约 14:30"); assert.equal( assistantIntentCopy("present_saved_candidate_range"), "目前只能保存候选范围,还没有足够证据应用到具体分钟。", ); }); test("birth date parsing preserves the local calendar day", () => { const parsed = parseBirthDate("1993-04-17"); assert.equal(parsed?.getFullYear(), 1993); assert.equal(parsed?.getMonth(), 3); assert.equal(parsed?.getDate(), 17); }); test("birth date values round trip leap days and reject invalid input", () => { const leapDay = parseBirthDate("2000-02-29"); assert.equal(leapDay === undefined ? undefined : formatBirthDate(leapDay), "2000-02-29"); assert.equal(parseBirthDate(""), undefined); assert.equal(parseBirthDate("2001-02-29"), undefined); }); test("persisted birth dates normalize database ISO values without accepting invalid dates", () => { assert.equal(normalizePersistedBirthDate(new Date("1997-08-08T00:00:00.000Z")), "1997-08-08"); assert.equal(normalizePersistedBirthDate("1997-08-08T00:00:00.000Z"), "1997-08-08"); assert.equal(normalizePersistedBirthDate("1997-08-08"), "1997-08-08"); assert.equal(normalizePersistedBirthDate(new Date("invalid")), ""); assert.equal(normalizePersistedBirthDate("1997-02-30T00:00:00.000Z"), ""); assert.equal(normalizePersistedBirthDate("1997-08-08junk"), ""); }); test("fresh intake copy stays user-facing and explains the next missing field", () => { assert.equal( birthTimeSourceOptions.find((option) => option.value === "family_exact")?.hint, "按医院记录或家人记得的时间填写,精确到分钟", ); assert.equal( birthTimeSourceOptions.find((option) => option.value === "period_only")?.hint, "只知道大概几点、上午下午,或完全不清楚", ); assert.equal(birthTimeSourceOptions.every((option) => !/引擎确认|初始化填报/.test(option.hint)), true); assert.equal(birthTimeDraftReadyHint({ ...emptyDraft, date: "" }), "请先选择出生日期"); assert.equal(birthTimeDraftReadyHint(emptyDraft), "请选择你对出生时间了解多少"); assert.equal(birthTimeDraftReadyHint({ ...emptyDraft, birthTimeSource: "family_exact", uncertaintyBeforeMinutes: 0, uncertaintyAfterMinutes: 0, }), "请填写时和分"); assert.equal(birthTimeDraftReadyHint({ ...emptyDraft, birthTimeSource: "period_only", }), "请选择一个大致时段"); }); test("fresh intake preserves exact, approximate-period, and unknown-time paths without auto-confirming", () => { const source = readFileSync(new URL("../src/components/birth-time-intake.tsx", import.meta.url), "utf8"); assert.doesNotMatch(source, /引擎确认/); assert.deepEqual( birthTimeSourceOptions.map((option) => option.value), ["family_exact", "period_only"], ); assert.doesNotMatch(source, /已用于当前排盘/); assert.match(source, /birthTimePeriodOptions/); assert.match(source, /birthTimeSource: "unknown"/); assert.match(source, /请选择最接近的时间范围/); assert.match(source, /完全不清楚,跳过出生时间/); assert.match(source, /我可以描述一个时间范围/); assert.match(source, /birthTimeStatus: "reported"/); assert.match(source, /birthTimeConsultationOptionsCopy\(value\)/); }); test("a saved declaration adopts the birth-time truth the account write returned", () => { const exactDeclaration = { ...emptyDraft, birthTimeSource: "family_exact", reportedTime: "05:00", uncertaintyBeforeMinutes: 0, uncertaintyAfterMinutes: 0, birthTimeStatus: "reported", } as const satisfies BirthTimeDraft; // The server accepts a zero-uncertainty exact time as the active minute, so the // submitted draft must not keep claiming the time is still only reported. assert.deepEqual( applyPersistedBirthTime(exactDeclaration, { status: "accepted", activeTime: "05:00" }), { ...exactDeclaration, time: "05:00", birthTimeStatus: "accepted" }, ); assert.deepEqual( applyPersistedBirthTime({ ...exactDeclaration, time: "05:00", birthTimeStatus: "accepted" }, { status: "reported", activeTime: null, }), exactDeclaration, ); assert.deepEqual( applyPersistedBirthTime(exactDeclaration, { status: "accepted", activeTime: "05:00:00" }), { ...exactDeclaration, time: "05:00", birthTimeStatus: "accepted" }, ); // A response without usable birth-time truth must leave the draft untouched. for (const applied of [ undefined, null, {}, { status: "unknown_status", activeTime: "05:00" }, { status: 7, activeTime: "05:00" }, ]) { assert.deepEqual(applyPersistedBirthTime(exactDeclaration, applied), exactDeclaration); } assert.deepEqual( applyPersistedBirthTime(exactDeclaration, { status: "accepted", activeTime: "5:0" }), { ...exactDeclaration, time: "", birthTimeStatus: "accepted" }, ); });