import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import { canUseUnverifiedBirthTime, birthTimeConsultationOptionsCopy, consultationModeForSession, createLatestAccountRequestGuard, createBirthTimeConsultationConsentState, grantBirthTimeConsultationConsent, hasBirthTimeConsultationConsent, parseRectificationPriceCredits, requiresBirthTimeConsent, resolveBirthTimeConsultationRoute, resolveRectificationCardAction, unverifiedBirthTime, } from "../src/lib/birth-time-consultation-consent.ts"; import type { BirthTimeDraft } from "../src/lib/birth-time-intake-model.ts"; const reportedExactTime = { date: "1997-08-08", time: "", reportedTime: "05:30", birthTimeSource: "family_exact", birthTimePeriod: "", birthTimeClue: "", uncertaintyBeforeMinutes: 10, uncertaintyAfterMinutes: 10, birthTimeStatus: "reported", } satisfies BirthTimeDraft; test("an unverified concrete time is usable immediately without a consent gate", () => { const initial = createBirthTimeConsultationConsentState(); assert.equal(canUseUnverifiedBirthTime(reportedExactTime), true); assert.equal(requiresBirthTimeConsent(reportedExactTime), false); assert.equal(hasBirthTimeConsultationConsent(initial, "chat-a"), false); assert.deepEqual(resolveBirthTimeConsultationRoute(reportedExactTime, initial, "chat-a"), { kind: "consult", mode: "unverified_birth_time", time: "05:30", }); const consented = grantBirthTimeConsultationConsent(initial, "chat-a"); assert.equal(hasBirthTimeConsultationConsent(consented, "chat-a"), true); assert.equal(hasBirthTimeConsultationConsent(consented, "chat-b"), false); assert.equal(hasBirthTimeConsultationConsent(initial, "chat-a"), false); }); test("period-only and unknown declarations never pretend to provide an unverified minute", () => { const periodOnly = { ...reportedExactTime, reportedTime: "", birthTimeSource: "period_only", birthTimePeriod: "early_morning", } satisfies BirthTimeDraft; const unknown = { ...reportedExactTime, reportedTime: "", birthTimeSource: "unknown", uncertaintyBeforeMinutes: null, uncertaintyAfterMinutes: null, } satisfies BirthTimeDraft; assert.equal(canUseUnverifiedBirthTime(periodOnly), false); assert.equal(canUseUnverifiedBirthTime(unknown), false); assert.equal(requiresBirthTimeConsent(periodOnly), false); assert.equal(requiresBirthTimeConsent(unknown), false); assert.match(birthTimeConsultationOptionsCopy(periodOnly), /日期与地点.*可选增强/); assert.match(birthTimeConsultationOptionsCopy(unknown), /日期与地点.*可选增强/); assert.doesNotMatch(birthTimeConsultationOptionsCopy(periodOnly), /使用.*原始填报时间|具体原始时间/); assert.doesNotMatch(birthTimeConsultationOptionsCopy(unknown), /使用.*原始填报时间|具体原始时间/); assert.match(birthTimeConsultationOptionsCopy(reportedExactTime), /直接使用你填报的时间.*可选增强/); }); test("the current reported minute wins over an old candidate and never falls back to it", () => { const editedCandidate = { ...reportedExactTime, time: "05:18", reportedTime: "06:10", birthTimeStatus: "candidate", } satisfies BirthTimeDraft; const periodCandidate = { ...editedCandidate, reportedTime: "", birthTimeSource: "period_only", birthTimePeriod: "early_morning", } satisfies BirthTimeDraft; const missingReportedCandidate = { ...editedCandidate, reportedTime: "", birthTimeSource: "approximate", } satisfies BirthTimeDraft; assert.equal(unverifiedBirthTime(editedCandidate), "06:10"); assert.equal(unverifiedBirthTime(periodCandidate), null); assert.equal(unverifiedBirthTime(missingReportedCandidate), null); const page = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8"); assert.match(page, /persistedReportedTime \|\| \(source === "legacy_import" \? time : ""\)/); }); test("period-only users enter minute-free consultation directly without a blocking choice", () => { const periodOnly = { ...reportedExactTime, reportedTime: "", birthTimeSource: "period_only", birthTimePeriod: "early_morning", } satisfies BirthTimeDraft; const initial = createBirthTimeConsultationConsentState(); assert.deepEqual(resolveBirthTimeConsultationRoute(periodOnly, initial, "chat-a"), { kind: "consult", mode: "general_no_birth_time", time: null, }); const general = grantBirthTimeConsultationConsent(initial, "chat-a", "general_no_birth_time"); assert.equal(consultationModeForSession(general, "chat-a"), "general_no_birth_time"); assert.equal(consultationModeForSession(general, "chat-b"), null); assert.deepEqual(resolveBirthTimeConsultationRoute(periodOnly, general, "chat-a"), { kind: "consult", mode: "general_no_birth_time", time: null, }); assert.deepEqual(resolveBirthTimeConsultationRoute(periodOnly, general, "chat-b"), { kind: "consult", mode: "general_no_birth_time", time: null, }); }); test("unverified consent resolves to a chart request and confirmed profiles need no consent", () => { const consented = grantBirthTimeConsultationConsent( createBirthTimeConsultationConsentState(), "chat-a", "unverified_birth_time", ); assert.deepEqual(resolveBirthTimeConsultationRoute(reportedExactTime, consented, "chat-a"), { kind: "consult", mode: "unverified_birth_time", time: "05:30", }); assert.deepEqual(resolveBirthTimeConsultationRoute({ ...reportedExactTime, time: "05:28", birthTimeStatus: "confirmed", }, createBirthTimeConsultationConsentState(), "chat-b"), { kind: "consult", mode: "verified_chart", time: "05:28", }); }); test("a stale minute-free preference cannot suppress the user's current reported time", () => { const general = grantBirthTimeConsultationConsent( createBirthTimeConsultationConsentState(), "chat-a", "general_no_birth_time", ); assert.deepEqual(resolveBirthTimeConsultationRoute(reportedExactTime, general, "chat-a"), { kind: "consult", mode: "unverified_birth_time", time: "05:30", }); }); test("confirmed time does not request unverified-use consent", () => { const confirmed = { ...reportedExactTime, time: "05:28", birthTimeStatus: "confirmed", } satisfies BirthTimeDraft; assert.equal(canUseUnverifiedBirthTime(confirmed), false); assert.equal(requiresBirthTimeConsent(confirmed), false); }); test("card action resumes an existing Agentic session and otherwise starts or revises", () => { assert.equal(resolveRectificationCardAction({ hasRectificationSession: false, hasUsableBirthTime: false }), "start"); assert.equal(resolveRectificationCardAction({ hasRectificationSession: true, hasUsableBirthTime: true }), "resume"); assert.equal(resolveRectificationCardAction({ hasRectificationSession: false, hasUsableBirthTime: true }), "revise"); }); test("fixed rectification price uses a checked default and rejects invalid configured values", () => { assert.equal(parseRectificationPriceCredits(undefined), 1); assert.equal(parseRectificationPriceCredits(" 7 "), 7); for (const invalid of ["", "0", "101", "1.5", "1e1", "free"]) { assert.throws(() => parseRectificationPriceCredits(invalid), /RECTIFICATION_PRICE_CREDITS/); } }); test("account refresh identities reject an older response after a newer case request starts", () => { const guard = createLatestAccountRequestGuard(); const oldCaseRequest = guard.begin(); const newCaseRequest = guard.begin(); assert.equal(guard.isCurrent(oldCaseRequest), false); assert.equal(guard.isCurrent(newCaseRequest), true); }); test("unverified birth time no longer emits a modal or toast gate", () => { const page = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8"); assert.doesNotMatch(page, / { const intake = readFileSync(new URL("../src/components/birth-time-intake.tsx", import.meta.url), "utf8"); const model = readFileSync(new URL("../src/lib/birth-time-intake-model.ts", import.meta.url), "utf8"); assert.match(model, /我知道准确出生时间/); assert.match(intake, /请填写你初始化提供的具体出生时间/); assert.match(intake, /aria-label="选择出生时间"/); assert.doesNotMatch(model, /我不确定准确时间/); assert.doesNotMatch(intake, /source === "family_exact" \|\| source === "approximate"/); assert.doesNotMatch(intake, /请选择最接近的时间范围/); assert.doesNotMatch(intake, /完全不清楚,跳过出生时间/); assert.doesNotMatch(intake, /生时校正以后需要时再做/); }); test("homepage and profile result copy use the source-aware consultation options", () => { const page = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8"); const intake = readFileSync(new URL("../src/components/birth-time-intake.tsx", import.meta.url), "utf8"); assert.match(page, /birthTimeConsultationOptionsCopy\(profileDraft\)/); assert.doesNotMatch(page, /birthTimeConsultationOptionsCopy\(profile\)/); assert.match(intake, /birthTimeConsultationOptionsCopy\(value\)/); });