diff --git a/frontend/src/lib/secondary-page-data.ts b/frontend/src/lib/secondary-page-data.ts index badd02e1..cbc060ff 100644 --- a/frontend/src/lib/secondary-page-data.ts +++ b/frontend/src/lib/secondary-page-data.ts @@ -255,7 +255,10 @@ const ephemerisCache = createMemoryCache(async () => { headers: { Accept: "application/json" }, cache: "no-store", }); - if (response.status === 401) return { kind: "unauthorized" }; + if (response.status === 401) { + ephemerisSubject = subjectId; + return { kind: "unauthorized" }; + } const json: unknown = await response.json().catch(() => null); const payload = parseEphemerisOkResponse(json); if (!payload) throw new Error("ephemeris_page_bad_payload"); @@ -304,8 +307,14 @@ const reportsCache = createMemoryCache(async () => { }); const json: unknown = await response.json().catch(() => null); if (readCurrentSubjectId() !== subjectId) throw new Error("reports_subject_changed"); - if (response.status === 401) return { kind: "unauthorized" }; - if (!response.ok) return { kind: "error", reports: [] }; + if (response.status === 401) { + reportsSubject = subjectId; + return { kind: "unauthorized" }; + } + if (!response.ok) { + reportsSubject = subjectId; + return { kind: "error", reports: [] }; + } reportsSubject = subjectId; return { kind: "ready", reports: readReports(json) }; }); diff --git a/frontend/tests/chart-page-view.test.tsx b/frontend/tests/chart-page-view.test.tsx index 96790077..1d384c36 100644 --- a/frontend/tests/chart-page-view.test.tsx +++ b/frontend/tests/chart-page-view.test.tsx @@ -442,6 +442,6 @@ test("the (app) layout mounts one sidebar for home and the four secondary routes assert.equal(layout.match(/ { } assert.match(routeSource, /export async function GET/); assert.match(serviceSource, /createServerSupabaseClient/); - assert.match(serviceSource, /ACCOUNT_BIRTH_SELECT/); + assert.match(serviceSource, /loadSubjectBirth/); + assert.doesNotMatch(serviceSource, /clientBirth|body\.profile/); assert.match(loadSource, /\/api\/chart/); assert.match(loadSource, /\/api\/dasha\/chara/); assert.match(loadSource, /\/api\/varga_full/); diff --git a/frontend/tests/consultation-entrypoint.test.ts b/frontend/tests/consultation-entrypoint.test.ts index 858ea9c2..06daac3f 100644 --- a/frontend/tests/consultation-entrypoint.test.ts +++ b/frontend/tests/consultation-entrypoint.test.ts @@ -320,7 +320,7 @@ test("rectification mutations report pending state while session-level return co // 新值:onPendingChange: setRectificationMutationPending 写在 hook 的 panel // 原因:mutationPending 仍由外壳对象持有,但 setter 经 panel 交给子树 assert.match(source, /onPendingChange: setRectificationMutationPending/); - assert.match(source, /disabled=\{productEntrypointsDisabled \|\| rectificationLoading \|\| rectificationMutationPending\}/); + assert.match(source, /disabled=\{productEntrypointsDisabled \|\| rectificationLoading \|\| rectificationMutationPending \|\| !rectificationForSelf\}/); assert.doesNotMatch(source, /重试恢复/); assert.doesNotMatch(source, /返回并恢复原问题|返回首页/); }); diff --git a/frontend/tests/database-admin-account-reset.test.ts b/frontend/tests/database-admin-account-reset.test.ts index 23b27a25..b2d8d120 100644 --- a/frontend/tests/database-admin-account-reset.test.ts +++ b/frontend/tests/database-admin-account-reset.test.ts @@ -82,8 +82,9 @@ test("admin customer reset clears only rebuildable application state", async () insert into public.chat_sessions (user_id, title, theme, messages) values ('${targetId}', 'Reset Chat', 'general', '[]'::jsonb); + -- 原值: role self。新值: other。原因: 本人镜像已停写,重置仍要清掉可重建的他人档案。 insert into public.chart_profiles (user_id, role, profile) - values ('${targetId}', 'self', '{}'::jsonb); + values ('${targetId}', 'other', '{}'::jsonb); insert into public.synastry_reports (user_id, partner_name, report) values ('${targetId}', 'Partner', '{}'::jsonb); insert into public.credit_transactions (user_id, transaction_type, amount, balance_after, request_id) diff --git a/frontend/tests/home-bootstrap-reveal.test.ts b/frontend/tests/home-bootstrap-reveal.test.ts index 571f2476..f5c2a0e5 100644 --- a/frontend/tests/home-bootstrap-reveal.test.ts +++ b/frontend/tests/home-bootstrap-reveal.test.ts @@ -162,7 +162,8 @@ test("bootstrap enters the prepare phase instead of revealing after the account // 原值: 3 个 prepare 守卫(onboarding / 今日星语 / 入口摘要)。 // 新值: 2 个(今日星语 / 入口摘要)。 // 原因: 建议问题请求已删除。 - const guards = page.match(/if \(bootstrapPhase === "account" \|\| !accountId/g) ?? []; + const binder = readFileSync(new URL("../src/components/daily-starlanguage-binder.tsx", import.meta.url), "utf8"); + const guards = `${page}\n${binder}`.match(/if \(bootstrapPhase === "account" \|\| !accountId/g) ?? []; assert.equal(guards.length, 2, "daily starlanguage and entry summary must both key off bootstrapPhase"); assert.match(page, /setRectificationEntrySummarySettled\(true\);/); assert.match(page, /bootstrapRevealDelayMs\(Date\.now\(\), prepareStartedAt\.current, bootstrapPrepareReady\)/); diff --git a/frontend/tests/personal-report-api.test.ts b/frontend/tests/personal-report-api.test.ts index 9cf89c07..36472b26 100644 --- a/frontend/tests/personal-report-api.test.ts +++ b/frontend/tests/personal-report-api.test.ts @@ -1298,7 +1298,7 @@ function seedRecord(): PersonalReportRecord { test("POST route uses dual clients: authenticated reads + admin persistence", () => { assert.match(createRoute, /createServerSupabaseClient\(\)/); - assert.match(createRoute, /\.from\("profiles"\)/); + assert.match(createRoute, /loadSubjectBirth/); assert.match(createRoute, /createAdminSupabaseClient\(\)/); assert.match(createRoute, /createSupabasePersonalReportService\(admin\)/); assert.match(createRoute, /createPersonalReportDataClient\(admin\)/); @@ -1358,7 +1358,7 @@ test("POST enqueues durable work without Next.js after and GET lists metadata wi assert.match(createRoute, /jobs:\s*createSupabasePersonalReportJobService\(admin\)/); assert.doesNotMatch(createRoute, /\bafter\s*\(/); assert.doesNotMatch(createRoute, /deferGeneration/); - assert.match(createRoute, /export async function GET\(\)/); + assert.match(createRoute, /export async function GET\(request: Request\)/); assert.match(createRoute, /REPORT_LIST_COLUMNS/); const listColumns = createRoute.slice( createRoute.indexOf("const REPORT_LIST_COLUMNS"), diff --git a/frontend/tests/report-row-delete.test.tsx b/frontend/tests/report-row-delete.test.tsx index 4f8ed510..4fff53f2 100644 --- a/frontend/tests/report-row-delete.test.tsx +++ b/frontend/tests/report-row-delete.test.tsx @@ -80,7 +80,7 @@ test("mounted report center removes deleted row and an older GET cannot resurrec writeReportsPage({ kind: "ready", reports: [row] }); globalThis.fetch = async (url, options) => { if (options?.method === "DELETE") return new Response(null, { status: 204 }); - if (url === "/api/reports") { gets += 1; return stale.promise; } + if (String(url).startsWith("/api/reports")) { gets += 1; return stale.promise; } return Response.json({}, { status: 401 }); }; try { diff --git a/frontend/tests/secondary-page-entry.test.ts b/frontend/tests/secondary-page-entry.test.ts index 4b587699..d24dbf8b 100644 --- a/frontend/tests/secondary-page-entry.test.ts +++ b/frontend/tests/secondary-page-entry.test.ts @@ -97,7 +97,7 @@ test("a warm chart cache does not go through a null waiting state", async () => assert.notEqual(warm, null); const secondMountView = peekChartPage()?.kind === "view" ? peekChartPage() : null; assert.equal(secondMountView?.kind, "view"); - assert.match(chartHook, /useState\(\(\) => viewFromSnapshot\(peekChartPage\(\)\)\)/); + assert.match(chartHook, /useState\(\(\) => viewMatchingSubject\(subjectId\)\)/); }); test("chart / ephemeris / reports caches share one inflight fetch and keep the last result", async () => { diff --git a/frontend/tests/server-owned-birth-profile.test.ts b/frontend/tests/server-owned-birth-profile.test.ts index 713b264f..8d98064f 100644 --- a/frontend/tests/server-owned-birth-profile.test.ts +++ b/frontend/tests/server-owned-birth-profile.test.ts @@ -91,7 +91,7 @@ test("daily and synastry routes share a literal account-birth select string", () const subjectBirth = readFileSync(new URL("../src/lib/subject-birth.ts", import.meta.url), "utf8"); assert.match(subjectBirth, /select\(ACCOUNT_BIRTH_SELECT\)/); assert.match(daily, /loadSubjectBirth/); - assert.match(synastry, /select\(ACCOUNT_BIRTH_SELECT\)/); + assert.match(synastry, /loadSubjectBirth/); assert.doesNotMatch(daily, /accountBirthColumns/); assert.doesNotMatch(synastry, /accountBirthColumns/); assert.doesNotMatch(daily, /\.join\(",\"\)/); diff --git a/frontend/tests/session-list-provider.test.ts b/frontend/tests/session-list-provider.test.ts index fafc65f1..6fed7acb 100644 --- a/frontend/tests/session-list-provider.test.ts +++ b/frontend/tests/session-list-provider.test.ts @@ -18,7 +18,7 @@ test("the app layout owns one SessionListProvider and one SidebarProvider", () = }); test("the provider fetches the session list once and home does not fetch it again", () => { - assert.match(provider, /fetch\(`\/api\/sessions\?limit=\$\{SESSION_PAGE_SIZE\}`/); + assert.match(provider, /fetch\(`\/api\/sessions\?limit=\$\{SESSION_PAGE_SIZE\}&subject=\$\{encodeURIComponent\(readCurrentSubjectId\(\)\)\}`/); assert.match(provider, /fetch\("\/api\/account"/); // 原值: page.tsx 里 `await sessionListReady;` // 新值: page.tsx 把 sessionListReady 交给 runHomeBootstrap,函数里 `await deps.sessionListReady`。 diff --git a/frontend/tests/settings-mvp-contract.test.ts b/frontend/tests/settings-mvp-contract.test.ts index ebd2e564..dd4df7a0 100644 --- a/frontend/tests/settings-mvp-contract.test.ts +++ b/frontend/tests/settings-mvp-contract.test.ts @@ -8,7 +8,8 @@ const overlay = readFileSync(new URL("../src/components/account-dialog-overlay.t test("account menu exposes the four MVP settings sections", () => { assert.match(sidebar, /个人资料<\/span>/); - assert.match(sidebar, /星盘资料<\/span>/); + assert.match(sidebar, /href: "\/people", label: "星盘档案"/); + assert.doesNotMatch(sidebar, /星盘资料<\/span>/); assert.match(sidebar, /通用设置<\/span>/); assert.match(sidebar, /账户与点数<\/span>/); assert.match(sidebar, /onOpenGeneral/);