import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import test from "node:test"; import { releaseRawReportAppendix, MISSING_DASHA_APPLICABILITY } from "../src/lib/personal-report-raw-appendix.ts"; import { projectOrdinaryReportMarkdown } from "../src/lib/report-public-projection.ts"; import { cleanReaderAppendixMarkdown } from "../src/lib/reader-appendix-language.ts"; // Vocabulary/security cases, not fabricated engine contract fixtures. test("raw appendix retains status rows and raw columns without changing ordinary projection", () => { const source = "# 数据\n\n| sign_cn | degree_in_sign | nakshatra_lord |\n| --- | --- | --- |\n| Aries | 12.5 | parameter_sensitive |\n"; const before = projectOrdinaryReportMarkdown(source); const result = releaseRawReportAppendix(source); assert.equal(result.documentKind, "raw_appendix"); assert.equal(result.markdown, source.replace("parameter_sensitive", "参数敏感") + MISSING_DASHA_APPLICABILITY); assert.equal(result.markdown.split("\n").filter((line) => line.startsWith("|")).length, 3); assert.equal(projectOrdinaryReportMarkdown(source), before); assert.doesNotMatch(before, /parameter_sensitive|参数敏感|12\.5/); }); test("raw appendix cleans old cache vocabulary but leaves English prose intact", () => { for (const [value, label] of Object.entries({ parameter_sensitive: "参数敏感", pyjhora_behavior_only: "仅单一外部参照,未做多引擎核对", not_multiengine_parity: "仅单一外部参照,未做多引擎核对", unresolved_external_tuple_boundary: "外部边界未对齐", partial_verified: "部分核验", raw_appendix_only: "仅原始附录可见", missing_in_local: "本地暂无", })) assert.equal(cleanReaderAppendixMarkdown(value), label); for (const value of ["PL9 第 83-88 页", "PL9 第 40 / 48 页", "PL9 page 99-100", "PL9.pdf"]) { assert.equal(cleanReaderAppendixMarkdown(value), "外部参照资料"); } assert.equal(cleanReaderAppendixMarkdown("PyJHora JHora cmd_full_reading"), "外部参照引擎 外部参照引擎 本地计算"); const prose = "The available result was computed and executed, not blocked."; assert.equal(cleanReaderAppendixMarkdown(prose), prose); assert.equal(cleanReaderAppendixMarkdown("| blocked | `executed` | available | computed |"), "| 暂不可用 | `已执行` | 可用 | 已计算 |"); }); test("raw appendix keeps established HTML URL secret and chart-fence safety boundaries", () => { const fence = '```jyotish-chart\n{"version":1,"id":"D1","title":"本命","layout":"north","ascendant":{"sign":"Aries","degree":1},"planets":[]}\n```\n'; const raw = '\n\n[bad](javascript:alert(1))\nhttp://127.0.0.1:5200/internal\nsk-testsecretvalue\n' + fence; const result = releaseRawReportAppendix(raw).markdown; assert.doesNotMatch(result, /script|iframe|javascript:|127\.0\.0\.1|sk-testsecretvalue/); assert.ok(result.includes(fence)); assert.equal(releaseRawReportAppendix(result).markdown, result); }); function routeCase(mode: string) { const script = ` import { mock } from "node:test"; const mode = ${JSON.stringify(mode)}; const id = "123e4567-e89b-12d3-a456-426614174000"; const filters = []; let reads = 0; mock.module("@/lib/personal-report-entitlement", { namedExports: { checkSameOrigin: () => ({ ok: mode !== "cross-origin" }), resolveAllowedReportOrigins: () => [], }}); mock.module("@/lib/personal-report-service", { namedExports: { createSupabasePersonalReportService: () => ({ getOwnedById: async (userId, reportId) => { if (userId !== "owner" || reportId !== id) throw new Error("identity mismatch"); return mode === "foreign" ? null : { status: mode === "generating" ? "generating" : "ready", requestId: "request" }; }}), }}); mock.module("@/lib/supabase/config", { namedExports: { isSupabaseConfigurationError: () => false }}); mock.module("@/lib/supabase/server", { namedExports: { createServerSupabaseClient: async () => ({ auth: { getUser: async () => ({ data: { user: mode === "anonymous" ? null : { id: "owner" } }, error: null }) }, from: (table) => table === "personal_report_sections" ? { select() { return this; }, eq() { return this; }, async maybeSingle() { return { data: null, error: null }; } } : ({ select() { return this; }, eq(key, value) { filters.push([key,value]); return this; }, async maybeSingle() { reads++; return { error: null, data: mode === "missing" ? null : { report_id: mode === "wrong-report" ? "other" : id, user_id: mode === "wrong-owner" ? "other" : "owner", request_id: mode === "wrong-request" ? "other" : "request", status: mode === "pending" ? "pending" : "ready", markdown: "| degree_in_sign | parameter_sensitive |", }}; }, }), }), }}); globalThis.fetch = () => { throw new Error("must not recalculate"); }; 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/" + id + "/raw-appendix", { method: "POST" }), { params: Promise.resolve({ reportId: mode === "invalid-id" ? "invalid" : id }) }); console.log(JSON.stringify({ status: response.status, body: await response.json(), cache: response.headers.get("cache-control"), filters, reads })); `; 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", }); assert.equal(result.status, 0, result.stderr || result.stdout); return JSON.parse(result.stdout.trim()); } for (const [mode, status] of Object.entries({ anonymous: 401, "cross-origin": 403, foreign: 404, "invalid-id": 404, generating: 409, missing: 410, pending: 410, "wrong-report": 410, "wrong-owner": 410, "wrong-request": 410, ready: 200 })) { test(`raw appendix route guards ${mode}`, () => { const result = routeCase(mode); assert.equal(result.status, status); assert.equal(result.cache, "private, no-store"); if (status < 410 && status !== 200) assert.equal(result.reads, 0); if (status === 200) { assert.deepEqual(result.filters, [["report_id", "123e4567-e89b-12d3-a456-426614174000"], ["user_id", "owner"], ["request_id", "request"]]); assert.deepEqual(result.body, { documentKind: "raw_appendix", format: "markdown", markdown: "| degree_in_sign | 参数敏感 |" + MISSING_DASHA_APPLICABILITY }); } else assert.equal(result.body.markdown, undefined); }); } test("raw appendix UI is independent of ordinary export and accepts no birth payload", () => { const ui = readFileSync(new URL("../src/components/personal-report/raw-appendix-download.tsx", import.meta.url), "utf8"); assert.match(ui, /下载原始附录/); assert.match(ui, /\/raw-appendix/); assert.doesNotMatch(ui, /正在加载|Spinner|body:|birth|projectOrdinaryReportMarkdown/); const ordinary = readFileSync(new URL("../src/lib/personal-report-longform-download.ts", import.meta.url), "utf8"); assert.doesNotMatch(ordinary, /raw-appendix/); });