import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; import test from "node:test"; import ts from "typescript"; const repoRoot = path.resolve(import.meta.dirname, "../../.."); const sourcePath = path.join(repoRoot, "web/protocol/diagnostic-report.ts"); const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 }, fileName: sourcePath, reportDiagnostics: true, }); assert.deepEqual(transpiled.diagnostics, []); const moduleUrl = "data:text/javascript;base64," + Buffer.from(transpiled.outputText).toString("base64"); const { APP_DIAGNOSTIC_MESSAGES, appendAppDiagnostic, createAppDiagnosticEntry, createAppDiagnosticReport } = await import(moduleUrl); test("M7-17 keeps user summaries stable while retaining detailed causes", () => { const cause = new Error("native allocator detail"); const error = new Error("WORKER_CRASH_INJECTED: engine.worker.ts:91", { cause }); error.code = "WORKER_TERMINATED"; const entry = createAppDiagnosticEntry({ sequence: 1, occurredAt: "2026-08-15T20:00:00.000Z", area: "ENGINE", code: "ENGINE_WORKER_TERMINATED", error, context: { projectId: "basic_scene", revision: 9 }, }); assert.equal(entry.summary, APP_DIAGNOSTIC_MESSAGES.ENGINE_WORKER_TERMINATED); assert.equal(entry.summary.includes("WORKER_CRASH_INJECTED"), false); assert.match(entry.detail, /WORKER_CRASH_INJECTED/); assert.equal(entry.sourceCode, "WORKER_TERMINATED"); assert.equal(entry.cause, "native allocator detail"); }); test("M7-17 bounds the ledger and exports a sequence-sorted schema v1 report", () => { const entries = [3, 1, 2].map((sequence) => createAppDiagnosticEntry({ sequence, occurredAt: `2026-08-15T20:00:0${sequence}.000Z`, area: "STORAGE", code: "STORAGE_BUDGET_FAILED", error: { code: "STORAGE_TRANSACTION", message: `detail-${sequence}` }, })); assert.deepEqual(appendAppDiagnostic(entries.slice(0, 2), entries[2], 2).map((entry) => entry.sequence), [1, 2]); const report = createAppDiagnosticReport({ generatedAt: "2026-08-15T20:01:00.000Z", runtime: { url: "http://127.0.0.1:5173/", userAgent: "test", language: "zh-CN", crossOriginIsolated: true }, project: { projectId: "basic_scene", revision: 9 }, entries, }); assert.equal(report.schemaVersion, 1); assert.equal(report.product, "Web Blender Modeler V1"); assert.deepEqual(report.entries.map((entry) => entry.sequence), [1, 2, 3]); assert.deepEqual(report.entries.map((entry) => entry.detail), ["detail-1", "detail-2", "detail-3"]); }); test("M7-17 user-visible status setters never interpolate raw exception detail", () => { const appSource = fs.readFileSync(path.join(repoRoot, "web/app/src/app/App.tsx"), "utf8"); const statusLines = appSource.split("\n").filter((line) => /set(?:Engine|Storage|Wasm|Manifest)Status\(|setViewportError\(/.test(line)); assert.ok(statusLines.length > 20, "expected to audit all user-visible status boundaries"); for (const line of statusLines) { assert.doesNotMatch(line, /error\.message|errorMessage\(|fault\.error\.message/, line.trim()); } assert.doesNotMatch(appSource, /\{fault\.error\.message\}/); });