From b569cf7825c35951375f7aa8028f52764ca24c87 Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Mon, 27 Jul 2026 22:01:21 +0800 Subject: [PATCH] fix(onboarding): normalize postgres birth dates --- frontend/src/lib/onboarding-post.ts | 11 ++++++++--- frontend/tests/onboarding-route.test.ts | 13 +++++++++++++ progress.md | 6 ++++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/frontend/src/lib/onboarding-post.ts b/frontend/src/lib/onboarding-post.ts index 7534e444..8d5711d8 100644 --- a/frontend/src/lib/onboarding-post.ts +++ b/frontend/src/lib/onboarding-post.ts @@ -9,6 +9,7 @@ import { parseOnboardingText, } from "./onboarding-payload.ts"; import { + formatBirthDate, isDeclaredBirthProfileComplete, type BirthTimeSource, type BirthTimeStatus, @@ -17,7 +18,7 @@ import { export type OnboardingProfileRow = { readonly id: string; readonly name: string | null; - readonly birth_date: string | null; + readonly birth_date: string | Date | null; readonly birth_time: string | null; readonly reported_birth_time: string | null; readonly active_birth_time: string | null; @@ -84,6 +85,10 @@ type OnboardingPostDependencies = { const DEFAULT_GENERATION_TIMEOUT_MS = 18_000; +function normalizeBirthDate(value: string | Date | null): string { + return value instanceof Date ? formatBirthDate(value) : value ?? ""; +} + function hasCompleteBirthProfile(profile: OnboardingProfileRow): boolean { const persistedTime = profile.active_birth_time || profile.birth_time || ""; const knownSources: readonly BirthTimeSource[] = [ @@ -98,7 +103,7 @@ function hasCompleteBirthProfile(profile: OnboardingProfileRow): boolean { ?? (persistedTime ? "confirmed" : ""); const clock = (value: string | null) => value ? value.slice(0, 5) : ""; const birthDraft = { - date: profile.birth_date ?? "", + date: normalizeBirthDate(profile.birth_date), time: clock(persistedTime), reportedTime: clock(profile.reported_birth_time) || (source === "legacy_import" ? clock(persistedTime) : ""), @@ -168,7 +173,7 @@ export function createOnboardingPost(dependencies: OnboardingPostDependencies): const identity = createOnboardingCacheIdentity({ name: profile.name, - birthDate: profile.birth_date, + birthDate: normalizeBirthDate(profile.birth_date), birthTime: profile.birth_time, reportedBirthTime: profile.reported_birth_time, activeBirthTime: profile.active_birth_time, diff --git a/frontend/tests/onboarding-route.test.ts b/frontend/tests/onboarding-route.test.ts index d52eb014..6e316d9d 100644 --- a/frontend/tests/onboarding-route.test.ts +++ b/frontend/tests/onboarding-route.test.ts @@ -100,6 +100,19 @@ test("slow Agent generation is aborted and a terminal fallback is cached before }); }); +test("PostgreSQL Date birth_date is normalized before onboarding validation", async () => { + const repository = new StatefulOnboardingProfileRepository(completeProfileRow({ + birth_date: new Date(1990, 5, 15), + })); + const post = createPost(repository, async () => generatedText(payloadA)); + + const response = await post(); + const body = await responseBody(response); + + assert.equal(response.status, 200); + assert.deepEqual(body, { ...payloadA, source: "agent" }); +}); + test("period-only birth declaration can generate the home starter questions without a concrete minute", async () => { const repository = new StatefulOnboardingProfileRepository({ ...completeProfileRow({ diff --git a/progress.md b/progress.md index 880939f0..b8d420d1 100644 --- a/progress.md +++ b/progress.md @@ -991,3 +991,9 @@ - 2026-07-27 staging 测试账户:确认 luna@copse.life 原为 0 用户/0 credential/0 会话;仅在 staging 通过 Better Auth createUser 创建,复核 user=1、credential=1、password_hashed=true,密码登录 HTTP 200 且 Set-Cookie=true;未直接写密码 SQL、未触发 production、临时文件已清理。 - 2026-07-27 admin 跟进:按最新要求将 self-hosted admin 改为仅邮箱密码登录,OTP 插件不挂载 admin;普通账号仍由服务端持久化 admin 角色门禁拒绝。聚焦单测 11/11、identity 36/36、集成 1/1、staging 契约 20/20、ESLint、build、diff check 全绿,skipped/todo=0;本地 admin Host `/login`=200、密码表单存在、OTP/注册/忘记密码入口不存在。反向关闭 admin password 后集成测试按预期在 admin 登录 400!=200 变红,恢复后 1/1 全绿。 - 2026-07-27 staging 管理员授权:经用户明确授权,仅在 staging 将 luna@copse.life 从 user 提升为 user,admin;事务输出 BEGIN/DO/COMMIT,复核 user_count=1、keeps_user_role=true、has_admin_role=true,production 未操作。 +- 2026-07-27 onboarding staging 回归诊断:21:34(Asia/Taipei)三次 POST `/api/onboarding` 均触发 `TypeError: dateString.match is not a function`;已证明 self-hosted `pg` 将 `profiles.birth_date` 返回为 `Date`,而 onboarding 按字符串传入 date-fns。 +- 用户在获知根因文件超出原 allowlist 后明确要求“需要修复、请继续”,据此仅扩展 `frontend/src/lib/onboarding-post.ts` 与对应 onboarding 回归测试;仍只发布 staging,不触发 production。 +- 修复顺序:先增加 PostgreSQL Date 红灯测试,再复用已有 `Date -> yyyy-MM-dd` 正规化方式,跑全套验收后同一 SHA 进入 main/staging;最大风险是全局日期语义漂移,因此不改共享 pg parser。 +- onboarding PostgreSQL Date 回归测试已完成红→绿:修复前 8/9,新增用例报 `TypeError: dateString.match is not a function`;边界正规化后 9/9、skipped/todo=0。实现复用 `formatBirthDate`,同时用于完整性校验和 onboarding 缓存身份,不改共享 pg parser。 +- 修复验证完成:单元/路由 45/45、PostgreSQL 认证集成 1/1、staging 契约 20/20;ESLint、`npm run build`、`git diff --check` 全绿,skipped/todo=0。onboarding 测试另在 UTC、Asia/Taipei、America/Los_Angeles 三时区各 9/9,避免 date-only 时区偏移。 +- 变更边界复核仅有 `frontend/src/lib/onboarding-post.ts`、`frontend/tests/onboarding-route.test.ts`、`PROGRESS.md`;package/lockfile/migration/deploy/workflow 均无 diff。