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.
145 lines
12 KiB
TypeScript
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);
|
|
});
|
|
}
|