Files
Jyotisha/frontend/tests/birth-time-consultation-consent.test.ts
T
Jesse_Chen f3946eaa6f
Staging Backend Quality Gate / validate (push) Failing after 9m57s
Staging Backend Quality Gate / publish (push) Has been skipped
chore: retire legacy rectification runtime
2026-08-07 12:55:32 +08:00

229 lines
9.1 KiB
TypeScript

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, /<BirthTimeSoftNotice|setBirthTimeSoftNotice/);
assert.doesNotMatch(page, /role="alertdialog"[\s\S]{0,300}出生时间还没有完成校正/);
});
test("birth time intake starts with two simple choices and keeps rectification optional", () => {
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(model, /我不确定准确时间/);
assert.doesNotMatch(intake, /source === "family_exact" \|\| source === "approximate"/);
assert.match(intake, /请选择最接近的时间范围/);
assert.match(intake, /完全不清楚,跳过出生时间/);
assert.match(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\)/);
});