import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import React from "react"; import { renderToStaticMarkup } from "react-dom/server"; import { PersonalReportMarkdownView } from "../src/components/personal-report/personal-report-markdown-view.tsx"; import { buildLongformCoverDocument } from "../src/lib/personal-report-longform-cover.ts"; import { PERSONAL_REPORT_EXPORT_LABEL, PERSONAL_REPORT_GENERATING_COPY, PERSONAL_REPORT_LEGACY_PLACEHOLDER, } from "../src/lib/personal-report-longform-copy.ts"; import { PERSONAL_REPORT_CARD_SUMMARY_MAX_CHARS, buildLongformOutline, extractLongformSummary, personalReportMarkdownFilename, } from "../src/lib/personal-report-longform-outline.ts"; import { PERSONAL_REPORT_WRITER_ENABLED } from "../src/lib/personal-report-writer-flag.ts"; Object.assign(globalThis, { React }); const SAMPLE_MARKDOWN = [ "# 个人长报告", "", "## 成品阅读导航", "", "导航正文 blocked", "", "## 摘要", "", "事业方向保持观察,不承诺日期。parameter_sensitive", "", "## 力量章", "", "### 子节", "", "后续章节", "", "## 质量验收矩阵", "", "| 项 | 状态 |", "| --- | --- |", "| MEVG | blocked |", ].join("\n"); const UNSAFE_MARKDOWN = [ SAMPLE_MARKDOWN, "", "## 摘要", "", "事业方向保持观察。", "", "", "", "[bad](javascript:alert(1))", "", "| 月 | KP |", "| --- | --- |", "| 2026-01 | blocked |", ].join("\n"); test("report centre cards read the stored Markdown excerpt, not a writer summary field name", () => { const listRoute = readFileSync(new URL("../src/app/api/reports/route.ts", import.meta.url), "utf8"); assert.match(listRoute, /"card_summary"/); assert.doesNotMatch(listRoute, /report_document->|->>summary/); const coverSource = readFileSync( new URL("../src/lib/personal-report-longform-cover.ts", import.meta.url), "utf8", ); assert.match(coverSource, /extractLongformSummary/); assert.match(coverSource, /PERSONAL_REPORT_CARD_SUMMARY_MAX_CHARS/); }); test("writer pipeline stays in the tree but is feature-off", () => { assert.equal(PERSONAL_REPORT_WRITER_ENABLED, false); assert.equal(PERSONAL_REPORT_EXPORT_LABEL, "导出报告(.md)"); assert.equal(PERSONAL_REPORT_LEGACY_PLACEHOLDER, "旧版本报告,请重新生成"); assert.match(PERSONAL_REPORT_GENERATING_COPY, /10–30 秒/); }); test("longform generate persists through PK upsert and logs engine timing without bodies", () => { const generateSource = readFileSync( new URL("../src/lib/personal-report-longform-generate.ts", import.meta.url), "utf8", ); assert.match(generateSource, /onConflict:\s*"report_id"/); assert.match(generateSource, /\[personal-report\] engine http_status=/); assert.match(generateSource, /duration_ms=/); assert.doesNotMatch(generateSource, /console\.(?:info|error|log)\([^\n]*markdown/); }); test("outline lifts navigation and summary to the first screen", () => { const outline = buildLongformOutline(SAMPLE_MARKDOWN); assert.ok(outline.headings.some((heading) => heading.title === "成品阅读导航")); assert.ok(outline.headings.some((heading) => heading.title === "摘要")); assert.match(outline.leadMarkdown, /成品阅读导航/); assert.match(outline.leadMarkdown, /事业方向保持观察/); assert.equal(outline.sections.some((section) => section.eager && section.title === "成品阅读导航"), true); assert.equal(outline.sections.some((section) => section.eager && section.title === "摘要"), true); }); test("card excerpt comes from the Markdown 摘要 section", () => { const excerpt = extractLongformSummary(SAMPLE_MARKDOWN, 80); assert.equal(PERSONAL_REPORT_CARD_SUMMARY_MAX_CHARS, 500); assert.match(excerpt, /事业方向保持观察/); assert.doesNotMatch(excerpt, /executiveSummary/); assert.equal(personalReportMarkdownFilename("2026-09-06T08:00:00.000Z"), "个人报告-2026-09-06"); }); test("cover document parses and stores the Markdown excerpt", () => { const document = buildLongformCoverDocument({ report: { id: "123e4567-e89b-12d3-a456-426614174000", reportType: "personal_full", presentationMode: "default", depth: "standard", requestedThemes: ["career", "wealth"], skillName: "jyotish-vedic-astrology", skillVersion: "6.9.14", skillSourceCommit: null, skillSnapshotSha256: "ab".repeat(32), }, subject: { displayName: "测试", birthTimeStatus: "accepted", birthPlaceLabel: "测试地点", }, markdown: SAMPLE_MARKDOWN, generatedAt: "2026-09-06T00:00:00.000Z", }); assert.equal(document.schemaVersion, "report_document.v2"); assert.match(document.executiveSummary.summary, /事业方向保持观察/); assert.equal(document.blockedConflictDisclosure.length, 2); assert.equal(document.thematicNarrative.length, 0); }); test("markdown view escapes HTML, skips images, and wraps wide tables", () => { const started = Date.now(); const markup = renderToStaticMarkup(React.createElement(PersonalReportMarkdownView, { markdown: UNSAFE_MARKDOWN })); const elapsed = Date.now() - started; assert.ok(elapsed < 1500, `first paint ${elapsed}ms`); assert.match(markup, /personal-report-toc/); assert.match(markup, /成品阅读导航/); assert.match(markup, /parameter_sensitive/); assert.match(markup, /blocked/); assert.match(markup, /markdown-table personal-report-table-wrap/); assert.doesNotMatch(markup, /