import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import { accountProfilePatchSchema, resolveAccountBirthTimeApplicationPatch, } from "../src/lib/account-profile-patch.ts"; const source = readFileSync(new URL("../src/app/api/account/route.ts", import.meta.url), "utf8"); const patchSource = readFileSync(new URL("../src/lib/account-profile-patch.ts", import.meta.url), "utf8"); const reportedStatusMigration = readFileSync( new URL("../supabase/migrations/20260726010000_backfill_reported_birth_time_status.sql", import.meta.url), "utf8", ); const productionMigrationWorkflow = readFileSync( new URL("../../.github/workflows/apply-production-rectification-migrations.yml", import.meta.url), "utf8", ); test("account API reads and returns the server-configured rectification price", () => { assert.match(source, /parseRectificationPriceCredits\(\s*process\.env\.RECTIFICATION_PRICE_CREDITS,?\s*\)/); assert.match(source, /rectificationPriceCredits/); assert.doesNotMatch(source, /RECTIFICATION_PRICE_CREDITS[^\n]*\?\?\s*["']1["']/); }); test("account API separates engine confirmation from an accepted usable chart time", () => { assert.match(source, /hasConfirmedBirthTime:\s*profile\.birth_time_status === "confirmed"/); assert.match(source, /hasUsableBirthTime:\s*\(profile\.birth_time_status === "accepted" \|\| profile\.birth_time_status === "confirmed"\)/); }); test("account API routes staging admins to the configured exact admin host", () => { assert.match(source, /readSelfHostedIdentityConfig\(process\.env\)\.adminOrigin/); assert.match(source, /const adminUrl = isAdmin \? adminEntryUrl\(\) : null/); assert.match(source, /adminUrl,/); assert.doesNotMatch(source, /NEXT_PUBLIC_ADMIN|AUTH_ADMIN_ORIGIN/); }); test("account GET falls back when global birthplace columns are not migrated yet", () => { const getSource = source.slice(source.indexOf("export async function GET"), source.indexOf("export async function PATCH")); assert.match(getSource, /profileError && isMissingProfileColumn\(profileError\)/); assert.match(getSource, /select\("credits,active_birth_time[^"]*timezone_offset(?:,[^"]*)?"\)/); assert.match(getSource, /birth_place_label: undefined/); assert.match(getSource, /timezone_id: undefined/); }); test("account API no longer reads or returns retired rectification cases", () => { assert.doesNotMatch(source, /birth_time_rectification_v4_cases|birth_time_rectification_cases/); assert.doesNotMatch(source, /rectificationCase/); }); test("profile patch schema validates calendar, clock, source requirements, and location bounds", () => { const valid = { name: "岳辰", birth_date: "2000-02-29", reported_birth_time: "05:30", birth_time_source: "family_exact", birth_time_period: null, birth_time_clue: null, uncertainty_before_minutes: 10, uncertainty_after_minutes: 10, country_code: "CN", province_code: "130000", city_code: "130400", district_code: "130406", latitude: 36.420487, longitude: 114.209936, timezone_offset: 8, }; assert.equal(accountProfilePatchSchema.safeParse(valid).success, true); assert.equal(accountProfilePatchSchema.safeParse({ ...valid, birth_date: "2001-02-29" }).success, false); assert.equal(accountProfilePatchSchema.safeParse({ ...valid, reported_birth_time: "24:00" }).success, false); assert.equal(accountProfilePatchSchema.safeParse({ ...valid, uncertainty_before_minutes: 7 }).success, false); assert.equal(accountProfilePatchSchema.safeParse({ ...valid, latitude: 91 }).success, false); assert.equal(accountProfilePatchSchema.safeParse({ reported_birth_time: "05:30" }).success, false); assert.equal(accountProfilePatchSchema.safeParse({ ...valid, longitude: null }).success, false); assert.equal(accountProfilePatchSchema.safeParse({ ...valid, timezone_offset: null, birth_place_label: "New York, New York, United States", birth_place_type: "place", birth_place_provider: "mapbox", birth_place_provider_id: "place.123", timezone_id: "America/New_York", timezone_source: "iana_historical", }).success, true); assert.equal(accountProfilePatchSchema.safeParse({ name: "只改称呼" }).success, true); assert.equal(accountProfilePatchSchema.safeParse({ ...valid, reported_birth_time: null, birth_time_source: "period_only", birth_time_period: null, uncertainty_before_minutes: null, uncertainty_after_minutes: null, }).success, false); assert.equal(accountProfilePatchSchema.safeParse({ ...valid, reported_birth_time: null, birth_time_source: "unknown", birth_time_period: "morning", uncertainty_before_minutes: null, uncertainty_after_minutes: null, }).success, false); }); test("ordinary declaration edits clear stale candidate application but never overwrite confirmed active time", () => { const candidate = { birth_date: "1997-08-08", reported_birth_time: "05:30", birth_time_source: "approximate", birth_time_period: null, birth_time_clue: null, uncertainty_before_minutes: 30, uncertainty_after_minutes: 30, active_birth_time: "05:18", birth_time: "05:18", birth_time_status: "candidate", rectification_case_id: "11111111-1111-4111-8111-111111111111", latitude: 36.420487, longitude: 114.209936, timezone_offset: 8, } as const; const edited = { birth_date: candidate.birth_date, reported_birth_time: "06:10", birth_time_source: candidate.birth_time_source, birth_time_period: null, birth_time_clue: null, uncertainty_before_minutes: 30, uncertainty_after_minutes: 30, } as const; assert.deepEqual(resolveAccountBirthTimeApplicationPatch(candidate, edited), { active_birth_time: null, birth_time_status: "reported", rectification_case_id: null, }); assert.deepEqual(resolveAccountBirthTimeApplicationPatch(candidate, { district_code: "130407", }), { active_birth_time: null, birth_time_status: "reported", rectification_case_id: null, }); for (const coordinatePatch of [ { latitude: 36.420488 }, { longitude: 114.209937 }, { timezone_offset: 9 }, ]) { assert.deepEqual(resolveAccountBirthTimeApplicationPatch(candidate, coordinatePatch), { active_birth_time: null, birth_time_status: "reported", rectification_case_id: null, }); } assert.deepEqual(resolveAccountBirthTimeApplicationPatch({ ...candidate, birth_time_status: "accepted", }, edited), { active_birth_time: null, birth_time_status: "reported", rectification_case_id: null, }); assert.deepEqual(resolveAccountBirthTimeApplicationPatch({ ...candidate, birth_time_status: "confirmed", }, edited), {}); assert.deepEqual(resolveAccountBirthTimeApplicationPatch({ ...candidate, active_birth_time: null, birth_time_status: "reported", }, { timezone_offset: 7 }), { active_birth_time: null, birth_time_status: "reported", rectification_case_id: null, }); assert.deepEqual(resolveAccountBirthTimeApplicationPatch({ ...candidate, active_birth_time: null, birth_time: null, birth_time_status: null, rectification_case_id: null, }, edited), { active_birth_time: null, birth_time_status: "reported", rectification_case_id: null, }); assert.deepEqual(resolveAccountBirthTimeApplicationPatch({ ...candidate, active_birth_time: null, birth_time: null, birth_time_status: null, rectification_case_id: null, reported_birth_time: edited.reported_birth_time, }, edited), { active_birth_time: null, birth_time_status: "reported", rectification_case_id: null, }); assert.deepEqual(resolveAccountBirthTimeApplicationPatch(null, edited), { active_birth_time: null, birth_time_status: "reported", rectification_case_id: null, }); assert.deepEqual(resolveAccountBirthTimeApplicationPatch({ ...candidate, birth_time_status: "accepted", }, edited), { active_birth_time: null, birth_time_status: "reported", rectification_case_id: null, }); assert.deepEqual(resolveAccountBirthTimeApplicationPatch({ ...candidate, birth_time_status: "confirmed", }, edited), {}); assert.deepEqual(resolveAccountBirthTimeApplicationPatch({ ...candidate, active_birth_time: null, birth_time_status: null, reported_birth_time: edited.reported_birth_time, }, edited), {}); }); test("reported birth-time status repair is forward-only and wired into production migration flow", () => { assert.match(reportedStatusMigration, /birth_time_status is null/); assert.match(reportedStatusMigration, /birth_time_status = 'reported'/); assert.match(reportedStatusMigration, /active_birth_time is null/); assert.match(reportedStatusMigration, /rectification_case_id is null/); assert.equal( productionMigrationWorkflow.match(/20260726010000_backfill_reported_birth_time_status\.sql/g)?.length, 2, ); }); test("account PATCH uses the shared validator and never writes client birth_time over account truth", () => { assert.match(source, /accountProfilePatchSchema\.safeParse/); assert.match(source, /resolveAccountBirthTimeApplicationPatch/); assert.match(patchSource, /birthTimeSource[\s\S]*birth_time_status: "reported"/); assert.doesNotMatch(source, /birth_time:\s*nullableString\(payload\.birth_time\)/); assert.match(source, /invalidatesUnconfirmedApplication/); assert.match(patchSource, /"birth_time_status"/); assert.match(patchSource, /"active_birth_time"/); assert.match(source, /latitude,longitude,timezone_offset/); assert.match(source, /birth_place_label,birth_place_type,birth_place_provider,birth_place_provider_id,timezone_id,timezone_source/); assert.match(source, /applyAccountProfileConcurrencyGuards/); assert.match(source, /最新确认结果已保留/); }); test("candidate invalidation compares coordinates and timezone in the conditional write", async () => { const { applyAccountProfileConcurrencyGuards } = await import("../src/lib/account-profile-patch.ts"); const calls: Array<["eq" | "is", string, unknown]> = []; const query = { eq(column: string, value: unknown) { calls.push(["eq", column, value]); return this; }, is(column: string, value: null) { calls.push(["is", column, value]); return this; }, }; const current = { birth_date: "1997-08-08", reported_birth_time: "05:30", birth_time_source: "approximate", birth_time_period: null, birth_time_clue: null, uncertainty_before_minutes: 30, uncertainty_after_minutes: 30, active_birth_time: "05:18", birth_time: "05:18", birth_time_status: "candidate", rectification_case_id: "11111111-1111-4111-8111-111111111111", country_code: "CN", province_code: "130000", city_code: "130400", district_code: "130406", latitude: 36.420487, longitude: 114.209936, timezone_offset: 8, }; applyAccountProfileConcurrencyGuards(query, current); assert.deepEqual(calls.filter((call) => ["latitude", "longitude", "timezone_offset"].includes(call[1])), [ ["eq", "latitude", 36.420487], ["eq", "longitude", 114.209936], ["eq", "timezone_offset", 8], ]); assert.deepEqual(calls.find((call) => call[1] === "active_birth_time"), ["eq", "active_birth_time", "05:18"]); assert.deepEqual(calls.find((call) => call[1] === "birth_time_status"), ["eq", "birth_time_status", "candidate"]); });