import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import { homeSurface as source } from "./home-surface.ts"; const route = readFileSync(new URL("../src/app/api/chart-profiles/route.ts", import.meta.url), "utf8"); test("other chart saves do not require the owner's rectification state", () => { assert.match(source, /function missingOtherProfileStep\(profile: Profile\)/); assert.match(source, /if \(missingOtherProfileStep\(nextProfile\)\)/); assert.doesNotMatch(source, /saveOtherChart[\s\S]{0,500}missingProfileStep\(nextProfile\)/); }); test("other chart save fails closed when cloud sync fails", () => { // Former values that locked the dual-truth fallback: // `let cloudSaved = false`, catch copy "已保存到本地星盘库;云端同步失败", // `localStorage.setItem(chartLibraryStorageKey(accountId), JSON.stringify(next))`, // delete copy "已从本地星盘库删除;云端同步失败". Cloud write failure is now failure. assert.doesNotMatch(source, /let cloudSaved = false/); assert.match(source, /await saveCloudChartProfile\(record\)/); assert.match(source, /async function saveOtherChart[\s\S]{0,1600}catch \{[\s\S]{0,180}保存失败,请重试/); assert.doesNotMatch(source, /localStorage\.setItem\(chartLibraryStorageKey/); assert.doesNotMatch(source, /已保存到本地星盘库/); assert.match(source, /已保存到云端星盘库/); assert.match(source, /async function deleteOtherChart[\s\S]{0,500}await deleteCloudChartProfile\(recordId\)/); assert.match(source, /async function deleteOtherChart[\s\S]{0,700}catch \{[\s\S]{0,160}删除失败,请重试[\s\S]{0,80}return;/); assert.doesNotMatch(source, /已从本地星盘库删除/); }); test("adding another chart waits for the user to choose a relationship type", () => { assert.doesNotMatch(source, /async function saveOtherChart[\s\S]{0,1400}await draftSynastryQuestionFromChart\(/); assert.match(source, /请选择关系类型后点击“用于合盘”。/); assert.match(source, /用于合盘/); assert.match(source, /\/api\/synastry/); }); test("a successful cloud read replaces stale local other charts", () => { assert.match( source, /fetchCloudChartLibrary\(\)[\s\S]{0,800}chartLibraryFromCloudOthers\(cloudLibrary, profileForLibrary, upsertSelfChart\)/, ); assert.doesNotMatch(source, /fetchCloudChartLibrary\(\)[\s\S]{0,800}new Map\(\[[\s\S]{0,500}current\.filter\(\(record\) => record\.role === "other"\)/); }); test("other chart creation lets the database create its UUID", () => { assert.match(route, /\.insert\(\{ user_id: user\.id, role, profile: body\.profile, updated_at: updatedAt \}\)/); assert.doesNotMatch(route, /\.upsert\(record, \{ onConflict: "id" \}\)/); assert.doesNotMatch(source, /id: record\.role === "self" \? undefined : record\.id/); }); test("cloud save failures preserve the server error message", () => { assert.match(source, /const payload = await response\.json\(\)\.catch\(\(\) => null\) as \{ error\?: string \} \| null;/); assert.match(source, /throw new Error\(payload\?\.error \|\| "cloud_chart_profile_save_failed"\);/); }); test("synastry selection exposes a pending state while the evidence packet is computed", () => { assert.match(source, /const \[synastryPendingId, setSynastryPendingId\] = useState\(null\);/); assert.match(source, /setSynastryPendingId\(record\.id\);/); assert.match(source, /finally \{\s*setSynastryPendingId\(null\);\s*\}/); assert.match(source, /disabled=\{synastryPendingId !== null\}/); assert.match(source, /synastryPendingId === record\.id \? "正在计算合盘\.\.\." : "用于合盘"/); }); test("relationship intent selects domain-specific evidence instead of treating every pairing as romance", () => { const route = readFileSync(new URL("../src/app/api/synastry/route.ts", import.meta.url), "utf8"); assert.match(source, /商业合作/); assert.match(source, /亲友\/家庭/); assert.match(source, /其他关系/); assert.match(source, /relationshipType: SynastryRelationshipType/); assert.match(source, /body: JSON\.stringify\(\{ partnerChartProfileId: record\.id, relationshipType \}\)/); assert.doesNotMatch(source, /selfProfile: profile, partnerProfile: record\.profile/); assert.match(route, /partnerChartProfileId/); assert.match(route, /from\("profiles"\)/); assert.match(route, /from\("chart_profiles"\)/); assert.match(route, /eq\("role", "other"\)/); assert.match(route, /consumeUserRequestRateLimit\("synastry"/); assert.match(route, /relationshipType === "business"/); assert.match(route, /divisions: \["D2", "D10", "D11"\]/); assert.match(route, /"D10_Dasamsa"/); assert.match(route, /"D11_Rudramsa"/); assert.match(route, /blockedLayers:/); assert.match(route, /"A10"/); assert.match(route, /"functional_benefic_malefic"/); assert.match(route, /"vimshottari_narayana"/); assert.match(source, /基础商业合作证据筛查/); assert.match(source, /relationshipType === "business"/); }); test("synastry history is replaced by the cloud list and save failures stay in page memory", () => { // Former value: `new Map([...current, ...cloudHistory])` unioned local+cloud // and `writeSynastryHistory(accountId, next)` on cloud save failure. assert.doesNotMatch(source, /new Map\(\[\.\.\.current, \.\.\.cloudHistory\]/); assert.match(source, /未能存入历史/); assert.doesNotMatch(source, /writeSynastryHistory\(/); assert.match(source, /discardLegacyCloudMirrorKeys\(/); }); test("current chart selection is local and does not overwrite the owner's profile", () => { assert.match(source, /activeChartStorageKey/); assert.match(source, /localStorage\.setItem\(activeChartStorageKey\(accountId\), record\.id\)/); assert.match(source, /已设为当前使用资料,账户本人的出生资料未被覆盖/); assert.doesNotMatch(source, /function makeDefaultChart[\s\S]{0,500}persistProfile\(/); assert.match(readFileSync(new URL("../src/app/api/chart-profiles/[id]/route.ts", import.meta.url), "utf8"), /星盘不存在或无权更新/); });