Files
Jyotisha/frontend/tests/adopted-birth-date.test.ts
T
jesse-uxandClaude Code 4d801e53c3
Independent Staging Quality Gate / validate (push) Successful in 16m28s
Independent Staging Quality Gate / publish (push) Successful in 3m33s
fix: integrate people archive, report reader and western chart corrections
Validate on Linux Node 22 and PostgreSQL 17: 3894 frontend tests and 64 database tests pass, with no removed test names or new failures. Preserve static Home, bounded gzip, assertion-change records and manual acceptance gaps.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-25 20:41:16 +08:00

134 lines
7.6 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import { readFileSync } from "node:fs";
import { effectiveBirthDate } from "../src/lib/effective-birth-date.ts";
import { globalBirthProfileFromAccountRow } from "../src/lib/server-owned-birth-profile.ts";
import { resolveMissingBirthTimezoneOffset } from "../src/lib/birth-profile-timezone.ts";
import { accountProfilePatchSchema } from "../src/lib/account-profile-patch.ts";
import { applyPersistedBirthTime } from "../src/lib/birth-time-intake-model.ts";
import { dailyStarlanguageProfileKey } from "../src/lib/daily-starlanguage.ts";
import { readProfile } from "../src/lib/home-profile.ts";
import { prepareConsultationRoute } from "../src/lib/consultation-route-service.ts";
import { parseReportCandidateRange } from "../src/lib/report-candidate-range.ts";
import { resolveReportBirthTimeSensitivityInput } from "../src/lib/personal-report-route-core.ts";
// Synthetic boundary fixtures, not engine confirmation evidence.
const profile = {
birth_date: "2000-06-15", reported_birth_time: "00:10",
active_birth_date: "2000-06-14", active_birth_time: "23:55",
active_birth_timezone_offset: -5,
birth_time_status: "accepted", latitude: 40, longitude: -74,
timezone_id: "America/New_York", timezone_offset: -4,
};
test("adopted chart date is independent of the declared date", () => {
assert.equal(globalBirthProfileFromAccountRow(profile).date, "2000-06-14");
assert.equal(profile.birth_date, "2000-06-15");
assert.equal(globalBirthProfileFromAccountRow({ ...profile, birth_time_status: "reported" }).date, "2000-06-15");
assert.equal(globalBirthProfileFromAccountRow({ ...profile, active_birth_date: null }).date, "2000-06-15");
});
test("chart library summary uses effective date while its editable profile keeps the declaration", () => {
const editable = readProfile(profile);
assert.equal(effectiveBirthDate(editable), "2000-06-14");
assert.equal(editable.date, "2000-06-15");
assert.equal(effectiveBirthDate({ ...editable, birthTimeStatus: "reported" }), "2000-06-15");
// 原值 / 新值 / 原因:旧面板 record.profile / 人物页 selected.profile / 退役入口,保留有效日期合同。
const component = readFileSync(new URL("../src/components/people/people-page.tsx", import.meta.url), "utf8");
assert.match(component, /effectiveBirthDate\(selected\.profile\) \|\| "出生日期待补全"/);
});
test("adopted chart reuses the persisted candidate calculation offset, not new DST rules", async () => {
const result = await resolveMissingBirthTimezoneOffset(profile, {
useActiveDate: true,
preferredTime: "23:55",
fetchImpl: async () => { throw new Error("must not reinterpret candidate timezone"); },
}) as Record<string, unknown>;
assert.equal(result.timezone_offset, -5);
assert.equal(result.birth_date, "2000-06-15");
assert.equal(globalBirthProfileFromAccountRow(profile).timezoneOffset, -5);
});
test("fixed-offset legacy profiles do not invent timezone rules", async () => {
const fixed = { ...profile, active_birth_date: null, active_birth_timezone_offset: null, timezone_id: null };
assert.equal(await resolveMissingBirthTimezoneOffset(fixed, {
useActiveDate: true,
fetchImpl: async () => { throw new Error("must not fetch"); },
}), fixed);
});
test("cross-day adopted date cannot silently lose its calculation offset", async () => {
await assert.rejects(resolveMissingBirthTimezoneOffset({ ...profile, active_birth_timezone_offset: null }, { useActiveDate: true }));
});
test("same-day adopted tuple must keep its offset while date-less legacy remains usable", async () => {
const partial = { ...profile, active_birth_date: profile.birth_date, active_birth_timezone_offset: null };
assert.throws(() => globalBirthProfileFromAccountRow(partial), /adopted_birth_calculation_incomplete/);
await assert.rejects(resolveMissingBirthTimezoneOffset(partial, { useActiveDate: true }));
const legacy = { ...partial, active_birth_date: null };
assert.equal(globalBirthProfileFromAccountRow(legacy).timezoneOffset, -4);
assert.equal(await resolveMissingBirthTimezoneOffset(legacy, { useActiveDate: true }), legacy);
assert.notEqual(dailyStarlanguageProfileKey(readProfile(partial)), dailyStarlanguageProfileKey(readProfile(legacy)));
});
test("account write hydration clears old adoption even when a new exact declaration is accepted", () => {
const draft = readProfile(profile);
assert.equal(draft.activeDate, "2000-06-14");
const updated = applyPersistedBirthTime(draft, {
status: "accepted", activeTime: "00:10", activeDate: null, activeTimezoneOffset: null,
});
assert.equal(updated.activeDate, undefined);
assert.equal(updated.activeTimezoneOffset, undefined);
assert.equal(updated.date, "2000-06-15");
assert.notEqual(dailyStarlanguageProfileKey(draft), dailyStarlanguageProfileKey(updated));
assert.notEqual(dailyStarlanguageProfileKey(draft), dailyStarlanguageProfileKey({ ...draft, activeDate: "2000-06-15" }));
});
test("new consultation uses the complete adopted tuple and preserves explicit sensitivity intervals", async () => {
const candidate_intervals = [{ start_at: "2000-06-14T23:55", end_at: "2000-06-15T00:11" }];
const range = parseReportCandidateRange({ candidate_intervals });
assert.ok(range);
const prepared = await prepareConsultationRoute({
userId: "synthetic", mode: "verified_chart", reserve: async () => "reserved",
loadProfile: async () => ({ ...profile, name: "Synthetic", birth_time_source: "family_exact", birth_place_label: "Synthetic place" }),
loadCandidateRange: async () => range,
});
assert.equal(prepared.serverChart?.toolInput.day, 14);
assert.equal(prepared.serverChart?.toolInput.hour, 23);
assert.equal(prepared.serverChart?.toolInput.tz, -5);
assert.deepEqual(prepared.serverChart?.toolInput.candidate_range, { start_time: "23:55", end_time: "00:11", candidate_intervals });
assert.deepEqual(resolveReportBirthTimeSensitivityInput(profile, "accepted", "23:55", range).candidate_range,
{ start_time: "23:55", end_time: "00:11", candidate_intervals });
});
test("dated report intervals preserve separate segments and reject malformed calendar boundaries", () => {
const candidate_intervals = [
{ start_at: "2000-06-15T00:00", end_at: "2000-06-15T00:59" },
{ start_at: "2000-06-15T23:00", end_at: "2000-06-15T23:59" },
];
assert.deepEqual(parseReportCandidateRange({ candidate_intervals }), {
startTime: "00:00", endTime: "23:59", candidate_intervals,
});
for (const intervals of [
[], [...candidate_intervals].reverse(),
[{ start_at: "2000-02-30T00:00", end_at: "2000-03-01T00:00" }],
[{ start_at: "2000-06-15T24:00", end_at: "2000-06-16T00:00" }],
[{ start_at: "2000-06-15T01:00", end_at: "2000-06-15T00:00" }],
[candidate_intervals[0], { start_at: "2000-06-15T00:59", end_at: "2000-06-15T01:00" }],
]) assert.equal(parseReportCandidateRange({ candidate_intervals: intervals }), null);
assert.equal(parseReportCandidateRange({ start_time: "23:55", end_time: "00:11" }), null,
"legacy clock-only range must not guess a calendar date");
});
test("shared mapper fails closed if a cross-day adopted calculation loses its offset", () => {
assert.throws(() => globalBirthProfileFromAccountRow({ ...profile, active_birth_timezone_offset: null }), /adopted_birth_calculation_incomplete/);
});
test("ordinary account patch cannot claim adopted date or provenance", () => {
for (const patch of [
{ active_birth_date: "2000-06-14" },
{ active_birth_timezone_offset: -5 },
{ active_birth_provenance: { source: "fake" } },
]) assert.equal(accountProfilePatchSchema.safeParse(patch).success, false);
});