import assert from "node:assert/strict"; import { createHash } from "node:crypto"; import { projectOrdinaryReportMarkdown } from "../src/lib/report-public-projection"; import { spawnSync } from "node:child_process"; import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import test from "node:test"; import { cleanReaderAppendixMarkdown } from "../src/lib/reader-appendix-language"; import { releaseRawReportAppendix } from "../src/lib/personal-report-raw-appendix"; import { createPersonalReportSectionService } from "../src/lib/personal-report-section-service-core"; import { LONGFORM_SNAPSHOT_SECTION_ID } from "../src/lib/personal-report-longform-snapshot"; const golden = JSON.parse(readFileSync(new URL("./fixtures/report-density-fictional-reader.json", import.meta.url), "utf8")); test("density full engine reader cleanup removes embedded product contracts without losing rows or numeric fields", () => { assert.equal(golden.fixtureProvenance.fictional, true); const raw = golden.markdown as string; // 原值 01cc1e03c5adca2833e014156e1162fff12e5cb336e4484c5871859cd878acd7 // 新值 f3aeb8f4b09ba0a78af4ab5e7aee4db32d7499b8e6b1488f2925b35fd91750b9 // 原因:原 sha 锁的是投影截断后的泄漏输出(半句与孤立标记仍在)。整行删除 PL9 内部词后正文更短。 // 原值 f3aeb8f4b09ba0a78af4ab5e7aee4db32d7499b8e6b1488f2925b35fd91750b9 // 新值 3d16948c52d97adbdcfee45441336f8cd1ae242e3c0c99f766cb8aa0bc964067 // 原因:BUG-1026 修复单 D3 保留内部表之前的标题;逐行对照仅新增标题和空行。 assert.equal(createHash("sha256").update(projectOrdinaryReportMarkdown(raw)).digest("hex"), "3d16948c52d97adbdcfee45441336f8cd1ae242e3c0c99f766cb8aa0bc964067"); assert.match(raw, /pl9_personal_long_report\.v2/); const clean = cleanReaderAppendixMarkdown(raw); assert.doesNotMatch(clean, /pl9/i); assert.equal(clean.split("\n").length, raw.split("\n").length); assert.equal(clean.split("\n").filter(line => line.startsWith("|")).length, raw.split("\n").filter(line => line.startsWith("|")).length); for (const field of ["sign_cn", "degree_in_sign", "nakshatra_lord"]) { assert.equal(clean.split(field).length, raw.split(field).length); } assert.doesNotMatch(releaseRawReportAppendix(raw, golden.reader_dasha_applicability).markdown, /pl9/i); }); test("density snapshot never enters writer recovery or chapter progress lists", async () => { const row = { user_id: "123e4567-e89b-12d3-a456-426614174001", request_id: "123e4567-e89b-12d3-a456-426614174002", section_id: LONGFORM_SNAPSHOT_SECTION_ID, status: "ready", payload: { kind: "longform_calculation_snapshot", version: 1 } }; const client = { from() { return { select() { return this; }, eq() { return this; }, order: async () => ({ data: [row], error: null }) }; } }; const service = createPersonalReportSectionService(client as never); assert.deepEqual(await service.list(row.user_id, row.request_id), []); const listSource = readFileSync(new URL("../src/app/api/reports/route.ts", import.meta.url), "utf8"); assert.match(listSource, /row\.section_id === LONGFORM_SNAPSHOT_SECTION_ID\) continue/); }); for (const mode of ["snapshot-only", "appendix-only", "final-ready", "legacy", "corrupt", "mismatch", "pending-with-markdown", "late-pending", "late-ready", "parallel-ready"]) { test(`density durable retry golden: ${mode}`, () => { const script = ` import assert from "node:assert/strict"; import { mock } from "node:test"; import { readFileSync } from "node:fs"; mock.module("server-only", { defaultExport: {} }); const { generatePersonalReportLongform } = await import("./src/lib/personal-report-longform-generate.ts"); const { readLongformSnapshot } = await import("./src/lib/personal-report-longform-snapshot.ts"); const { projectOrdinaryReportMarkdown } = await import("./src/lib/report-public-projection.ts"); const { releaseRawReportAppendix } = await import("./src/lib/personal-report-raw-appendix.ts"); const packet = JSON.parse(readFileSync("tests/fixtures/report-density-fictional-engine.json", "utf8")); const golden = JSON.parse(readFileSync("tests/fixtures/report-density-fictional-reader.json", "utf8")); const mode = ${JSON.stringify(mode)}; const rows = new Map(); let calls = 0; let interrupt = mode === "snapshot-only"; let releaseWrite, reachedWrite; const writeGate = new Promise(resolve => { releaseWrite = resolve; }); const reached = new Promise(resolve => { reachedWrite = resolve; }); let delayed = false; const admin = { async rpc(name, args) { if (!delayed && ((mode === "late-pending" && name === "ensure_personal_report_section") || ((mode === "late-ready" || mode === "parallel-ready") && name === "complete_personal_report_section"))) { delayed = true; reachedWrite(); await writeGate; } const table = "personal_report_sections"; const current = rows.get(table); if (name === "ensure_personal_report_section" && !current) rows.set(table, { user_id: args.p_user_id, request_id: args.p_request_id, section_id: args.p_section_id, status: "pending", payload: null }); if (name === "complete_personal_report_section" && current?.status === "pending") rows.set(table, { ...current, status: "ready", payload: structuredClone(args.p_payload) }); return { data: [], error: null }; }, from(table) { return { select() { return this; }, eq() { return this; }, async maybeSingle() { return { data: structuredClone(rows.get(table) ?? null), error: null }; }, async upsert(row) { if (table === "personal_report_longform_appendices" && interrupt) { interrupt = false; return { error: { message: "simulated crash" } }; } rows.set(table, structuredClone(row)); return { error: null }; } }; }}; const report = { id: "123e4567-e89b-12d3-a456-426614174000", userId: "123e4567-e89b-12d3-a456-426614174001", requestId: "123e4567-e89b-12d3-a456-426614174002", reportType: "personal_full", presentationMode: "default", depth: "standard", requestedThemes: ["career"], skillName: "jyotish-vedic-astrology", skillVersion: "6.9.14", skillSourceCommit: null, skillSnapshotSha256: "ab".repeat(32) }; const deps = { report, admin, profile: { birth_date: "2000-01-01", reported_birth_time: "12:00", birth_time_status: "reported", latitude: 0, longitude: 0, timezone_offset: 0, ayanamsa: "lahiri" }, candidateRange: null, displayName: "虚构测试", birthTimeStatus: "reported", now: () => new Date("2026-09-22T12:00:00Z"), fetchImpl: async () => { calls++; return Response.json({ format: "markdown", markdown: golden.markdown, fact_table_packet: packet, reader_dasha_applicability: golden.reader_dasha_applicability }); } }; if (mode === "legacy") rows.set("personal_report_longform_appendices", { report_id: report.id, user_id: report.userId, request_id: report.requestId, status: "ready", markdown: golden.markdown }); if (mode === "late-pending" || (mode === "late-ready" || mode === "parallel-ready")) { const controller = new AbortController(); const stale = generatePersonalReportLongform({ ...deps, signal: controller.signal, fetchImpl: async () => { calls++; return Response.json({ format: "markdown", markdown: golden.markdown + "\\nSTALE", fact_table_packet: packet, reader_dasha_applicability: golden.reader_dasha_applicability }); } }); const staleResult = stale.then(value => ({ value }), error => ({ error })); await reached; if (mode !== "parallel-ready") controller.abort(); const winner = await generatePersonalReportLongform(deps); assert.equal(winner.document.factTables.length, 8); assert.equal(winner.document.charts.length, 16); const committed = structuredClone(rows.get("personal_report_sections")); releaseWrite(); const loser = await staleResult; if (mode === "parallel-ready") { assert.ok(loser.value); assert.deepEqual(loser.value.document, winner.document); } assert.deepEqual(rows.get("personal_report_sections"), committed); assert.equal(rows.get("personal_report_longform_appendices").markdown, golden.markdown); } else if (mode === "snapshot-only") await assert.rejects(generatePersonalReportLongform(deps), /appendix_persist_failed/); else { const first = await generatePersonalReportLongform(deps); assert.equal(first.document.charts.length, mode === "legacy" ? 1 : 16); if (mode !== "legacy") assert.equal(first.document.factTables.length, 8); // appendix-only deliberately discards the result before final report persistence. if (mode === "final-ready") report.reportDocument = structuredClone(first.document); } const snapshotRow = rows.get("personal_report_sections"); if (mode === "corrupt") snapshotRow.payload.charts = []; if (mode === "mismatch") snapshotRow.payload.requestId = report.userId; if (mode === "pending-with-markdown") { snapshotRow.status = "pending"; snapshotRow.payload = null; } if (["corrupt", "mismatch", "pending-with-markdown"].includes(mode)) { await assert.rejects(generatePersonalReportLongform(deps), /longform_snapshot_invalid/); assert.equal(calls, 1); } else { const retry = await generatePersonalReportLongform(deps); assert.equal(calls, mode === "legacy" ? 0 : (mode === "late-ready" || mode === "parallel-ready") ? 2 : 1); assert.equal(retry.document.factTables?.length ?? 0, mode === "legacy" ? 0 : 8); assert.equal(retry.document.charts.length, mode === "legacy" ? 1 : 16); const storedMarkdown = rows.get("personal_report_longform_appendices").markdown; assert.equal(storedMarkdown, golden.markdown); assert.equal(projectOrdinaryReportMarkdown(storedMarkdown), projectOrdinaryReportMarkdown(golden.markdown)); const persisted = await readLongformSnapshot(admin, { reportId: report.id, userId: report.userId, requestId: report.requestId }); const raw = releaseRawReportAppendix(storedMarkdown, persisted?.snapshot?.readerDashaApplicability).markdown; assert.doesNotMatch(raw, /pl9/i); assert.match(raw, mode === "legacy" ? /缺少本次计算的结构化适用性证据/ : /Tribhagi-40/); if (mode === "final-ready") { mock.module("@/lib/personal-report-entitlement", { namedExports: { checkSameOrigin: () => ({ ok: true }), resolveAllowedReportOrigins: () => [] } }); mock.module("@/lib/personal-report-service", { namedExports: { createSupabasePersonalReportService: () => ({ getOwnedById: async () => ({ ...report, status: "ready" }) }) } }); mock.module("@/lib/supabase/config", { namedExports: { isSupabaseConfigurationError: () => false } }); mock.module("@/lib/supabase/server", { namedExports: { createServerSupabaseClient: async () => ({ ...admin, auth: { getUser: async () => ({ data: { user: { id: report.userId } }, error: null }) } }) } }); const { POST } = await import("./src/app/api/reports/[reportId]/raw-appendix/route.ts"); const response = await POST(new Request("https://staging.jyotisha.chat/api/reports/" + report.id + "/raw-appendix", { method: "POST" }), { params: Promise.resolve({ reportId: report.id }) }); assert.equal(response.status, 200); const body = await response.json(); assert.equal(body.markdown, raw); assert.doesNotMatch(body.markdown, /pl9|pyjhora|jhora|cmd_[a-z0-9_]+|build_professional_report_reference_packet/i); assert.equal(calls, 1); } } `; const result = spawnSync(process.execPath, ["--experimental-test-module-mocks", "--import", "tsx", "--input-type=module", "--eval", script], { cwd: fileURLToPath(new URL("../", import.meta.url)), encoding: "utf8", timeout: 120_000, }); assert.equal(result.status, 0, result.stderr + result.stdout); }); }