Files
Jyotisha/frontend/tests/personal-report-longform-md.test.ts
T
Jesse_ChenandCursor b466a6fc8c fix(report): stop listing reports via PostgREST JSON paths (BUG-574)
Staging PostgREST rejects the executiveSummary JSON-path alias, so GET /api/reports 500s. Persist a plain card_summary column from the Markdown excerpt instead.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 11:08:00 +08:00

152 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
"",
"## 摘要",
"",
"事业方向保持观察。",
"",
"<script>alert(1)</script>",
"<img src=\"https://evil.example/x.png\">",
"[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, /1030 秒/);
});
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, /<script/i);
assert.doesNotMatch(markup, /<img/i);
assert.doesNotMatch(markup, /javascript:/i);
assert.doesNotMatch(markup, /evil\.example/);
const viewSource = readFileSync(
new URL("../src/components/personal-report/personal-report-markdown-view.tsx", import.meta.url),
"utf8",
);
assert.match(viewSource, /skipHtml/);
assert.match(viewSource, /remarkGfm/);
assert.doesNotMatch(viewSource, /rehype-raw/);
assert.doesNotMatch(viewSource, /dangerouslySetInnerHTML/);
});