Files
Jyotisha/frontend/tests/personal-report-density-retry.test.ts
T
jesse-ux 2a1f5f0fc9 fix(report): use the upstream reader edition for new longform bodies
New reports request reader_main from upstream origin/main 23b9609e.
KP and transit no longer copy an empty vars() dict, solar returns keep
birth_asc_sign_idx, and ordinary projection deletes internal lines whole.
2026-09-25 02:19:32 +08:00

145 lines
12 KiB
TypeScript

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 内部词后正文更短。
assert.equal(createHash("sha256").update(projectOrdinaryReportMarkdown(raw)).digest("hex"), "f3aeb8f4b09ba0a78af4ab5e7aee4db32d7499b8e6b1488f2925b35fd91750b9");
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);
});
}