import assert from "node:assert/strict"; import test from "node:test"; import { libraryProfileFromSubjectRow } from "../src/lib/chart-subject-write.ts"; import { normalizeChartLibraryApiRecord } from "../src/lib/home-cloud-sync.ts"; import { missingOtherProfileStep } from "../src/lib/home-profile.ts"; import { emptyProfile, type ChartLibraryApiRecord, type ChartLibraryRecord, type Profile } from "../src/lib/home-types.ts"; /** Fictional people. These dates and 杭州 are not anyone's birth data. */ const exactRow = { name: "林遥", birth_date: "1990-01-02", reported_birth_time: "08:30", birth_time_source: "family_exact", birth_time_status: "reported", uncertainty_before_minutes: 0, uncertainty_after_minutes: 0, birth_place_label: "杭州", latitude: 30.27, longitude: 120.15, timezone_id: "Asia/Shanghai", timezone_offset: 8, relation: "friend", profile: {}, }; const periodRow = { name: "小舟", birth_date: "1992-03-12", birth_time_source: "period_only", birth_time_period: "morning", declared_window_start: "08:00", declared_window_end: "11:59", relation: "child", profile: {}, }; const nameOnlyRow = { name: "阿宁", relation: "parent", profile: {}, }; function apiRecord(id: string, row: Record, chartRelationship?: Profile["chartRelationship"]): ChartLibraryApiRecord { const profile = libraryProfileFromSubjectRow(row) as ChartLibraryApiRecord["profile"]; return { id, role: "other", profile: chartRelationship ? { ...profile, chartRelationship } : profile, updated_at: "2026-10-27T00:00:00.000Z", }; } function assertEmptyProfileKeys(profile: Profile) { for (const key of Object.keys(emptyProfile) as (keyof typeof emptyProfile)[]) { assert.equal(Object.prototype.hasOwnProperty.call(profile, key), true, String(key)); const sample = emptyProfile[key]; const actual = profile[key]; if (sample === null) { assert.ok(actual === null || typeof actual === "number", String(key)); } else { assert.equal(typeof actual, typeof sample, String(key)); } } } /** Same predicate as pendingOther in use-profile-onboarding.ts when no intake hint is stored. */ function pendingOther(rows: readonly ChartLibraryRecord[]) { return rows.find((row) => { if (row.role === "self") return false; return Boolean(row.profile.name.trim()) && missingOtherProfileStep(row.profile) !== null; }); } test("a saved person from the chart API keeps every profile key and does not crash onboarding", () => { const exact = normalizeChartLibraryApiRecord(apiRecord("other-exact", exactRow)); const period = normalizeChartLibraryApiRecord(apiRecord("other-period", periodRow, "family")); const nameOnly = normalizeChartLibraryApiRecord(apiRecord("other-name", nameOnlyRow)); for (const row of [exact, period, nameOnly]) assertEmptyProfileKeys(row.profile); assert.equal(missingOtherProfileStep(exact.profile), null); assert.equal(missingOtherProfileStep(period.profile), "place"); assert.equal(missingOtherProfileStep(nameOnly.profile), "birth"); assert.equal(period.profile.name, "小舟"); assert.equal(period.profile.date, "1992-03-12"); assert.equal(period.profile.birthTimePeriod, "morning"); assert.equal(period.profile.declaredWindowStart, "08:00"); assert.equal(period.profile.declaredWindowEnd, "11:59"); assert.equal(period.profile.relation, "child"); assert.equal(period.profile.chartRelationship, "family"); assert.equal(period.relationship, "family"); assert.equal(period.profile.birthTimeClue, ""); assert.equal(period.profile.rectificationCaseId, ""); assert.equal(exact.profile.name, "林遥"); assert.equal(exact.profile.date, "1990-01-02"); assert.equal(exact.profile.reportedTime, "08:30"); assert.equal(exact.profile.relation, "friend"); assert.equal(exact.profile.birthPlaceLabel, "杭州"); assert.equal(nameOnly.profile.name, "阿宁"); assert.equal(nameOnly.profile.relation, "parent"); assert.equal(nameOnly.profile.chartRelationship, "other"); const found = pendingOther([exact, period, nameOnly]); assert.equal(found?.id, "other-period"); }); test("the API shape that crashed the home page throws before normalize and not after", () => { const raw = libraryProfileFromSubjectRow(exactRow) as Profile; assert.equal("birthTimeClue" in raw, false); assert.throws(() => missingOtherProfileStep(raw), /length/); const normalized = normalizeChartLibraryApiRecord(apiRecord("other-exact", exactRow)); assert.doesNotThrow(() => pendingOther([normalized])); assert.equal(pendingOther([normalized]), undefined); });