fix(account): answer profile writes with the derived birth-time truth
A zero-uncertainty exact declaration is accepted server-side as the active
minute, but the account write only answered {ok:true}. Every save path then
kept the draft it submitted, so the first consultation after initialization
asked for unverified_birth_time against an accepted profile and was rejected
with mode_changed before billing.
The account route now returns the status and active minute it derived, and
every profile save adopts that result instead of its own local guess.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3893,3 +3893,19 @@
|
||||
- 防复发:滚动容器留了 `padding-bottom` 时,它的 `position: sticky` 子元素永远无法贴到容器可视底边——sticky 受包含块内容盒夹持,调 `bottom` 偏移不解决问题。悬浮在输入框上方的控件应挂在输入框容器上(`bottom: 100%`),而不是挂在滚动容器里,这样才不依赖任何预留高度常量。另:BUG-218 当时已写明“浏览器内的视觉位置未经人工目视确认”,本条正是那句话对应的实际后果——纯源码合同测试能固定 DOM 与属性,固定不了几何位置,涉及定位的改动必须实测。
|
||||
- 相关记录:BUG-218(引入该按钮与锚定逻辑)、BUG-252(曾把它记作 BUG-217 新增,并留下焦点丢失的待跟进项,本轮未处理)
|
||||
- 修复版本:本地未提交候选
|
||||
|
||||
## BUG-264 | 初始化填报准确出生时间后,第一条咨询必定 409:客户端按提交的声明选路,服务端已把它升级为 accepted
|
||||
|
||||
- 状态:resolved(本地修复,待提交与发布)
|
||||
- 首次发现:2026-08-17
|
||||
- 最近更新:2026-08-17
|
||||
- 影响面:`/` 初始化流程的出生时间与出生地点保存、`PATCH /api/account`、`POST /api/consult` 的出生真值校验。
|
||||
- 用户现象:新用户在初始化里选择“我知道准确出生时间”并填到分钟,走完地点一步进入首页,发出第一条咨询即被拒绝,提示“出生时间状态已经变化 / 请刷新后重新选择使用填报时间、一般咨询或先完成校正”。刷新页面后同一条问题可以正常发出。
|
||||
- 触发条件:声明为 `family_exact` 且误差为 0,经账户接口保存后不刷新页面直接发第一条咨询。
|
||||
- 根因:账户写入会在服务端派生出生真值——零误差的准确时间被直接采用为 `active_birth_time` 且状态升级为 `accepted`——但 `PATCH /api/account` 只回 `{ok:true}`,页面又用提交的草稿覆盖 `profile`,草稿里状态仍是 `reported`、`time` 为空。于是客户端按未确认分钟选 `unverified_birth_time`,而咨询接口的未确认分支明确拒绝 `accepted`,在扣点前抛出 `mode_changed` 返回 409。派生规则写在服务端、客户端却各自推断同一件事,是这次不一致的入口。
|
||||
- 修复:`PATCH /api/account` 随写入成功返回它派生的出生真值(状态与当前排盘分钟),新增 `resolveAppliedAccountBirthTime` 负责这一派生;`persistProfile` 返回按该真值对账后的档案,初始化出生时间、初始化地点、账户弹窗保存和默认星盘切换四条保存路径统一采用返回值,不再沿用本地草稿。服务端的真值校验保持严格,不为客户端的过期视图放宽。
|
||||
- 验证:`account-api.test.ts` 锁定派生结果(零误差准确时间→`accepted`+分钟;改为时段声明→回落 `reported`;已确认与 legacy 分钟不被覆盖;纯改名不产生状态);`birth-time-intake.test.ts` 锁定客户端对账(含 `HH:mm:ss`、未知状态和缺字段时不动草稿);`profile-persistence.test.ts` 断言每条保存路径都采用返回档案,禁止回退到 `setProfile(profileDraft)`。`npm test` 1710 项中 1709 通过,唯一失败是并发跑 `database-*` postgres fixture 的既有抖动,单独串行复跑通过;`tsc --noEmit` 与目标文件 ESLint 清洁。未做的验证:**没有在 staging 真实新用户流程里目视复跑一遍**,需要一个未初始化的账户。
|
||||
- 防复发:出生时间状态与当前排盘分钟由服务端唯一派生,客户端只能采用接口返回值;任何新增的档案保存路径都必须消费 `persistProfile` 的返回档案,源码合同测试会拦住用本地草稿覆盖 `profile` 的写法。咨询选路不得从未落库的草稿推导。
|
||||
- 相关记录:BUG-018(首次保存未写入档案状态,同一类真值不一致)、BUG-017(`mode_changed` 的另一入口)
|
||||
- 复发自:无
|
||||
- 修复版本:本地未提交候选
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
accountProfilePatchSchema,
|
||||
applyAccountProfileConcurrencyGuards,
|
||||
resolveAccountBirthTimeApplicationPatch,
|
||||
resolveAppliedAccountBirthTime,
|
||||
} from "@/lib/account-profile-patch";
|
||||
import { optionalBeamAvatarFromProfile } from "@/lib/beam-avatar";
|
||||
import { createAdminSupabaseClient, isAdminUser } from "@/lib/supabase/admin";
|
||||
@@ -271,7 +272,10 @@ export async function PATCH(request: Request) {
|
||||
return NextResponse.json({ error: "暂时无法保存账户资料" }, { status: 500 });
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
birthTime: resolveAppliedAccountBirthTime(currentProfile, applicationPatch),
|
||||
});
|
||||
} catch (error) {
|
||||
if (isSupabaseConfigurationError(error)) {
|
||||
return NextResponse.json({ error: "Supabase 尚未配置", code: "SUPABASE_NOT_CONFIGURED" }, { status: 503 });
|
||||
|
||||
+31
-24
@@ -43,6 +43,7 @@ import {
|
||||
import type { ConsultationEntrypoint } from "@/lib/consultation-entrypoint";
|
||||
import {
|
||||
applyBirthTimeDraftPatch,
|
||||
applyPersistedBirthTime,
|
||||
assistantIntentCopy,
|
||||
birthTimeDisplayState,
|
||||
birthTimePersistenceValues,
|
||||
@@ -2061,9 +2062,9 @@ export default function Home() {
|
||||
}
|
||||
}
|
||||
|
||||
async function persistProfile(nextProfile: Profile) {
|
||||
async function persistProfile(nextProfile: Profile): Promise<Profile> {
|
||||
if (!account) throw new Error("账户尚未加载完成");
|
||||
if (process.env.NODE_ENV === "development" && uiPreview.current) return;
|
||||
if (process.env.NODE_ENV === "development" && uiPreview.current) return nextProfile;
|
||||
const birthPlace = selectedBirthPlace(nextProfile);
|
||||
const response = await fetch("/api/account", {
|
||||
method: "PATCH",
|
||||
@@ -2088,11 +2089,16 @@ export default function Home() {
|
||||
timezone_source: nextProfile.timezoneSource || null,
|
||||
}),
|
||||
});
|
||||
const payload = await response.json().catch(() => null) as {
|
||||
error?: string;
|
||||
birthTime?: unknown;
|
||||
} | null;
|
||||
if (!response.ok) {
|
||||
const payload = await response.json().catch(() => null) as { error?: string } | null;
|
||||
throw new Error(payload?.error || "账户资料暂时无法保存。");
|
||||
}
|
||||
await saveCloudChartProfile({ ...buildSelfChartRecord(nextProfile), updatedAt: timestamp() }).catch(() => null);
|
||||
const savedProfile = applyPersistedBirthTime(nextProfile, payload?.birthTime);
|
||||
await saveCloudChartProfile({ ...buildSelfChartRecord(savedProfile), updatedAt: timestamp() }).catch(() => null);
|
||||
return savedProfile;
|
||||
}
|
||||
|
||||
async function saveOtherChart(event: FormEvent<HTMLFormElement>) {
|
||||
@@ -2154,9 +2160,9 @@ export default function Home() {
|
||||
setProfileSaving(true);
|
||||
setAccountError("");
|
||||
try {
|
||||
await persistProfile(record.profile);
|
||||
setProfile(record.profile);
|
||||
setProfileDraft(record.profile);
|
||||
const savedProfile = await persistProfile(record.profile);
|
||||
setProfile(savedProfile);
|
||||
setProfileDraft(savedProfile);
|
||||
setProfileNotice("已设为当前默认星盘。");
|
||||
} catch (caught) {
|
||||
setAccountError(friendlyError(caught instanceof Error ? caught.message : "默认星盘保存失败"));
|
||||
@@ -2195,17 +2201,17 @@ export default function Home() {
|
||||
setAccountError("");
|
||||
try {
|
||||
const declarationChanged = birthProfileDeclarationChanged(profile, profileDraft);
|
||||
await persistProfile(profileDraft);
|
||||
setProfile(profileDraft);
|
||||
setProfileDraft(profileDraft);
|
||||
const savedProfile = await persistProfile(profileDraft);
|
||||
setProfile(savedProfile);
|
||||
setProfileDraft(savedProfile);
|
||||
setRectificationError("");
|
||||
if (declarationChanged) {
|
||||
setBirthTimeConsultationConsent(createBirthTimeConsultationConsentState());
|
||||
void refreshAccount();
|
||||
}
|
||||
setProfileNotice(profileDraft.birthTimeStatus === "confirmed"
|
||||
setProfileNotice(savedProfile.birthTimeStatus === "confirmed"
|
||||
? "出生资料已保存到云端,可在同一账号的其他设备使用。"
|
||||
: `出生资料已保存。${birthTimeConsultationOptionsCopy(profileDraft)}`);
|
||||
: `出生资料已保存。${birthTimeConsultationOptionsCopy(savedProfile)}`);
|
||||
} catch (caught) {
|
||||
setAccountError(friendlyError(caught instanceof Error ? caught.message : "出生资料保存失败"));
|
||||
} finally {
|
||||
@@ -2220,13 +2226,13 @@ export default function Home() {
|
||||
setProfileSaving(true);
|
||||
setAccountError("");
|
||||
try {
|
||||
await persistProfile(nextProfile);
|
||||
setProfile(nextProfile);
|
||||
setProfileDraft(nextProfile);
|
||||
setStartGreeting(createStartGreeting(nextProfile.name));
|
||||
const savedProfile = await persistProfile(nextProfile);
|
||||
setProfile(savedProfile);
|
||||
setProfileDraft(savedProfile);
|
||||
setStartGreeting(createStartGreeting(savedProfile.name));
|
||||
setDraft("");
|
||||
setPresetMessageLength(0);
|
||||
const nextStep = missingProfileStep(nextProfile);
|
||||
const nextStep = missingProfileStep(savedProfile);
|
||||
if (nextStep) setOnboardingStep(nextStep);
|
||||
else setOnboardingJustCompleted(false);
|
||||
} catch (caught) {
|
||||
@@ -2243,11 +2249,12 @@ export default function Home() {
|
||||
setBirthTimeAssessmentPhase("saving_profile");
|
||||
setAccountError("");
|
||||
try {
|
||||
await persistProfile(profileDraft);
|
||||
const savedProfile = await persistProfile(profileDraft);
|
||||
birthTimeRevisionPending.current = false;
|
||||
setProfile(profileDraft);
|
||||
setProfile(savedProfile);
|
||||
setProfileDraft(savedProfile);
|
||||
setPresetMessageLength(0);
|
||||
const nextStep = missingProfileStep(profileDraft);
|
||||
const nextStep = missingProfileStep(savedProfile);
|
||||
if (nextStep) setOnboardingStep(nextStep);
|
||||
else setOnboardingJustCompleted(false);
|
||||
} catch (caught) {
|
||||
@@ -2272,10 +2279,10 @@ export default function Home() {
|
||||
setBirthTimeAssessmentPhase("entering_home");
|
||||
setAccountError("");
|
||||
try {
|
||||
await persistProfile(profileDraft);
|
||||
setProfile(profileDraft);
|
||||
setProfileDraft(profileDraft);
|
||||
setStartGreeting(createStartGreeting(profileDraft.name));
|
||||
const savedProfile = await persistProfile(profileDraft);
|
||||
setProfile(savedProfile);
|
||||
setProfileDraft(savedProfile);
|
||||
setStartGreeting(createStartGreeting(savedProfile.name));
|
||||
setPresetMessageLength(0);
|
||||
setOnboardingJustCompleted(false);
|
||||
} catch (caught) {
|
||||
|
||||
@@ -304,3 +304,30 @@ export function resolveAccountBirthTimeApplicationPatch(
|
||||
rectification_case_id: null,
|
||||
};
|
||||
}
|
||||
|
||||
export type AppliedAccountBirthTime = Readonly<{
|
||||
status: string | null;
|
||||
activeTime: string | null;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* The birth-time truth the account owns after a successful write. Status and
|
||||
* active minute are derived server-side, so a caller that keeps the declaration
|
||||
* it submitted would consult under a mode the server no longer accepts.
|
||||
*/
|
||||
export function resolveAppliedAccountBirthTime(
|
||||
current: AccountBirthTimeState | null,
|
||||
applicationPatch: AccountBirthTimeApplicationPatch,
|
||||
): AppliedAccountBirthTime {
|
||||
const activeTime = normalizeApplicableBirthClock(
|
||||
applicationPatch.active_birth_time !== undefined
|
||||
? applicationPatch.active_birth_time
|
||||
: current?.active_birth_time ?? current?.birth_time,
|
||||
);
|
||||
return Object.freeze({
|
||||
status: applicationPatch.birth_time_status
|
||||
?? current?.birth_time_status
|
||||
?? (activeTime ? "confirmed" : null),
|
||||
activeTime,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -326,6 +326,31 @@ export function birthTimePersistenceValues(draft: BirthTimeDraft) {
|
||||
};
|
||||
}
|
||||
|
||||
const persistedBirthTimeStatuses = [
|
||||
"reported", "assessing", "rectifying", "candidate", "accepted", "confirmed",
|
||||
] as const satisfies readonly Exclude<BirthTimeStatus, "">[];
|
||||
|
||||
/**
|
||||
* Reconciles a submitted declaration with the birth-time truth the account write
|
||||
* returned. The server decides whether a declaration is already usable as the
|
||||
* active minute, so consultation mode must never be derived from the draft alone.
|
||||
*/
|
||||
export function applyPersistedBirthTime<T extends BirthTimeDraft>(
|
||||
draft: T,
|
||||
applied: unknown,
|
||||
): T {
|
||||
if (applied === null || typeof applied !== "object") return draft;
|
||||
const { status, activeTime } = applied as { status?: unknown; activeTime?: unknown };
|
||||
const persistedStatus = persistedBirthTimeStatuses.find((candidate) => candidate === status);
|
||||
if (!persistedStatus) return draft;
|
||||
const clock = typeof activeTime === "string" ? activeTime.slice(0, 5) : "";
|
||||
return {
|
||||
...draft,
|
||||
time: isBirthClockTime(clock) ? clock : "",
|
||||
birthTimeStatus: persistedStatus,
|
||||
};
|
||||
}
|
||||
|
||||
export function describeBirthTimeDraft(draft: BirthTimeDraft) {
|
||||
const [year, month, day] = draft.date.split("-").map(Number);
|
||||
const date = `${year}年${month}月${day}日`;
|
||||
|
||||
@@ -4,6 +4,7 @@ import test from "node:test";
|
||||
import {
|
||||
accountProfilePatchSchema,
|
||||
resolveAccountBirthTimeApplicationPatch,
|
||||
resolveAppliedAccountBirthTime,
|
||||
} from "../src/lib/account-profile-patch.ts";
|
||||
|
||||
const source = readFileSync(new URL("../src/app/api/account/route.ts", import.meta.url), "utf8");
|
||||
@@ -288,6 +289,90 @@ test("zero-uncertainty family exact time becomes an accepted usable chart time",
|
||||
}), {});
|
||||
});
|
||||
|
||||
test("account PATCH answers with the birth-time truth it derived server-side", () => {
|
||||
const exactDeclaration = {
|
||||
birth_date: "1997-08-08",
|
||||
reported_birth_time: "05:00",
|
||||
birth_time_source: "family_exact",
|
||||
birth_time_period: null,
|
||||
birth_time_clue: null,
|
||||
uncertainty_before_minutes: 0,
|
||||
uncertainty_after_minutes: 0,
|
||||
} as const;
|
||||
const reportedExactProfile = {
|
||||
...exactDeclaration,
|
||||
reported_birth_time: "05:00:00",
|
||||
active_birth_time: null,
|
||||
birth_time: null,
|
||||
birth_time_status: "reported",
|
||||
rectification_case_id: null,
|
||||
} as const;
|
||||
|
||||
// A client that keeps the declaration it submitted would consult as
|
||||
// unverified_birth_time and be rejected by the consultation truth check.
|
||||
assert.deepEqual(
|
||||
resolveAppliedAccountBirthTime(
|
||||
null,
|
||||
resolveAccountBirthTimeApplicationPatch(null, exactDeclaration),
|
||||
),
|
||||
{ status: "accepted", activeTime: "05:00" },
|
||||
);
|
||||
assert.deepEqual(
|
||||
resolveAppliedAccountBirthTime(
|
||||
reportedExactProfile,
|
||||
resolveAccountBirthTimeApplicationPatch(reportedExactProfile, exactDeclaration),
|
||||
),
|
||||
{ status: "accepted", activeTime: "05:00" },
|
||||
);
|
||||
const periodDeclaration = {
|
||||
...exactDeclaration,
|
||||
reported_birth_time: null,
|
||||
birth_time_source: "period_only",
|
||||
birth_time_period: "early_morning",
|
||||
uncertainty_before_minutes: null,
|
||||
uncertainty_after_minutes: null,
|
||||
} as const;
|
||||
assert.deepEqual(
|
||||
resolveAppliedAccountBirthTime(
|
||||
reportedExactProfile,
|
||||
resolveAccountBirthTimeApplicationPatch(reportedExactProfile, periodDeclaration),
|
||||
),
|
||||
{ status: "reported", activeTime: null },
|
||||
);
|
||||
|
||||
// An untouched application keeps the stored truth, including a legacy confirmed minute.
|
||||
const confirmedProfile = {
|
||||
...reportedExactProfile,
|
||||
active_birth_time: "05:18:00",
|
||||
birth_time_status: "confirmed",
|
||||
} as const;
|
||||
assert.deepEqual(
|
||||
resolveAppliedAccountBirthTime(
|
||||
confirmedProfile,
|
||||
resolveAccountBirthTimeApplicationPatch(confirmedProfile, { district_code: "130407" }),
|
||||
),
|
||||
{ status: "confirmed", activeTime: "05:18" },
|
||||
);
|
||||
const legacyProfile = {
|
||||
...reportedExactProfile,
|
||||
birth_time: "05:18:00",
|
||||
birth_time_status: null,
|
||||
} as const;
|
||||
assert.deepEqual(
|
||||
resolveAppliedAccountBirthTime(
|
||||
legacyProfile,
|
||||
resolveAccountBirthTimeApplicationPatch(legacyProfile, { district_code: "130407" }),
|
||||
),
|
||||
{ status: "confirmed", activeTime: "05:18" },
|
||||
);
|
||||
assert.deepEqual(
|
||||
resolveAppliedAccountBirthTime(null, resolveAccountBirthTimeApplicationPatch(null, { name: "岳辰" })),
|
||||
{ status: null, activeTime: null },
|
||||
);
|
||||
|
||||
assert.match(source, /birthTime: resolveAppliedAccountBirthTime\(currentProfile, applicationPatch\)/);
|
||||
});
|
||||
|
||||
test("only a strict family exact zero-uncertainty declaration is auto-accepted", () => {
|
||||
const base = {
|
||||
birth_date: "1997-08-08",
|
||||
|
||||
@@ -225,7 +225,7 @@ 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.match(page, /birthTimeConsultationOptionsCopy\(savedProfile\)/);
|
||||
assert.doesNotMatch(page, /birthTimeConsultationOptionsCopy\(profile\)/);
|
||||
assert.match(intake, /birthTimeConsultationOptionsCopy\(value\)/);
|
||||
});
|
||||
|
||||
@@ -150,7 +150,7 @@ test("terminal CJK copy stays intact while homepage candidates remain unconfirme
|
||||
const pageSource = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8");
|
||||
|
||||
assert.match(candidateResultSource, /候选范围已保留,但当前证据不足以将具体分钟写入当前排盘时间。补充经历后可重新评估。/);
|
||||
assert.match(pageSource, /`出生资料已保存。\$\{birthTimeConsultationOptionsCopy\(profileDraft\)\}`/);
|
||||
assert.match(pageSource, /`出生资料已保存。\$\{birthTimeConsultationOptionsCopy\(savedProfile\)\}`/);
|
||||
assert.match(pageSource, /<ConversationalBirthTimeRectification/);
|
||||
assert.doesNotMatch(pageSource, /当前使用候选时间排盘/);
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
import {
|
||||
applyBirthTimeDraftPatch,
|
||||
applyPersistedBirthTime,
|
||||
assistantIntentCopy,
|
||||
birthTimeDisplayState,
|
||||
birthTimeDraftReadyHint,
|
||||
@@ -350,3 +351,47 @@ test("fresh intake preserves exact, approximate-period, and unknown-time paths w
|
||||
assert.match(source, /birthTimeStatus: "reported"/);
|
||||
assert.match(source, /birthTimeConsultationOptionsCopy\(value\)/);
|
||||
});
|
||||
|
||||
test("a saved declaration adopts the birth-time truth the account write returned", () => {
|
||||
const exactDeclaration = {
|
||||
...emptyDraft,
|
||||
birthTimeSource: "family_exact",
|
||||
reportedTime: "05:00",
|
||||
uncertaintyBeforeMinutes: 0,
|
||||
uncertaintyAfterMinutes: 0,
|
||||
birthTimeStatus: "reported",
|
||||
} as const satisfies BirthTimeDraft;
|
||||
|
||||
// The server accepts a zero-uncertainty exact time as the active minute, so the
|
||||
// submitted draft must not keep claiming the time is still only reported.
|
||||
assert.deepEqual(
|
||||
applyPersistedBirthTime(exactDeclaration, { status: "accepted", activeTime: "05:00" }),
|
||||
{ ...exactDeclaration, time: "05:00", birthTimeStatus: "accepted" },
|
||||
);
|
||||
assert.deepEqual(
|
||||
applyPersistedBirthTime({ ...exactDeclaration, time: "05:00", birthTimeStatus: "accepted" }, {
|
||||
status: "reported",
|
||||
activeTime: null,
|
||||
}),
|
||||
exactDeclaration,
|
||||
);
|
||||
assert.deepEqual(
|
||||
applyPersistedBirthTime(exactDeclaration, { status: "accepted", activeTime: "05:00:00" }),
|
||||
{ ...exactDeclaration, time: "05:00", birthTimeStatus: "accepted" },
|
||||
);
|
||||
|
||||
// A response without usable birth-time truth must leave the draft untouched.
|
||||
for (const applied of [
|
||||
undefined,
|
||||
null,
|
||||
{},
|
||||
{ status: "unknown_status", activeTime: "05:00" },
|
||||
{ status: 7, activeTime: "05:00" },
|
||||
]) {
|
||||
assert.deepEqual(applyPersistedBirthTime(exactDeclaration, applied), exactDeclaration);
|
||||
}
|
||||
assert.deepEqual(
|
||||
applyPersistedBirthTime(exactDeclaration, { status: "accepted", activeTime: "5:0" }),
|
||||
{ ...exactDeclaration, time: "", birthTimeStatus: "accepted" },
|
||||
);
|
||||
});
|
||||
|
||||
@@ -8,6 +8,24 @@ test("upserts a missing profile when saving account details", () => {
|
||||
assert.match(source, /credentials:\s*"same-origin"/);
|
||||
});
|
||||
|
||||
test("every profile save adopts the birth-time truth the account write returned", () => {
|
||||
const source = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8");
|
||||
|
||||
// Given: the account write derives birth-time status and active minute.
|
||||
assert.match(source, /const savedProfile = applyPersistedBirthTime\(nextProfile, payload\?\.birthTime\)/);
|
||||
assert.match(source, /return savedProfile;/);
|
||||
|
||||
// When: any save path resolves.
|
||||
// Then: it must apply the returned profile, or the next consultation runs under a
|
||||
// mode the server rejects with mode_changed.
|
||||
const savePaths = source.match(/[^\n]*await persistProfile\([^\n]*/g) ?? [];
|
||||
assert.equal(savePaths.length > 0, true);
|
||||
for (const savePath of savePaths) {
|
||||
assert.match(savePath, /const savedProfile = await persistProfile\(/);
|
||||
}
|
||||
assert.doesNotMatch(source, /setProfile\(profileDraft\)/);
|
||||
});
|
||||
|
||||
test("account route upserts profiles with the server admin client", () => {
|
||||
const source = readFileSync(new URL("../src/app/api/account/route.ts", import.meta.url), "utf8");
|
||||
assert.match(source, /createAdminSupabaseClient\(\)/);
|
||||
|
||||
Reference in New Issue
Block a user