import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { execFileSync } from "node:child_process"; import { fileURLToPath, pathToFileURL } from "node:url"; import { loadWasmProbe } from "./wasm-probe-runtime.mjs"; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const out = fs.mkdtempSync(path.join(os.tmpdir(), "freecad-draft-business-")); const ids = JSON.parse(process.env.FREECAD_BUSINESS_IDS ?? '["Draft:draft.formatBatch"]'); const svgIds = new Set(["business-format:Draft:import:.svg", "business-format:Draft:export:.svg"]); const dxfId = "business-format:Draft:import:.dxf"; const started = performance.now(); const timings = { operations: [] }; let exitCode = 0; try { let stageStarted = performance.now(); execFileSync(path.join(root, "web/node_modules/.bin/tsc"), [ "--target", "ES2022", "--module", "ESNext", "--moduleResolution", "Bundler", "--strict", "--skipLibCheck", "--outDir", out, path.join(root, "web/src/facade-envelope.ts"), path.join(root, "web/src/draft-python-family-facade.ts"), ], { cwd: root, stdio: "pipe" }); timings.transpileSeconds = (performance.now() - stageStarted) / 1000; const facade = await import(pathToFileURL(path.join(out, "draft-python-family-facade.js"))); const { FacadeSerialQueue } = await import(pathToFileURL(path.join(out, "facade-envelope.js"))); stageStarted = performance.now(); const { kernel } = await loadWasmProbe(process.env.FREECAD_DRAFT_BUSINESS_BUILD ?? path.join(root, "web/public/kernel"), "FreeCADWebDraftRuntimeProbe"); timings.nativeLoadSeconds = (performance.now() - stageStarted) / 1000; const queue = new FacadeSerialQueue(); let n = 0; const call = async records => { const began = performance.now(); const result = await facade.dispatchDraftFormat(queue, facade.createDraftFormatRequest(`draft-${++n}`, records), kernel); timings.operations.push({ format: records[0].format, direction: records[0].direction, seconds: (performance.now() - began) / 1000, result: result.kind }); return result; }; const cases = []; for (const request of [null, undefined, {}, { requestId: 17 }]) { const rejected = await facade.dispatchDraftFormat(queue, request, kernel); assert.equal(rejected.kind, "error", JSON.stringify(rejected)); assert.equal(rejected.requestId, "invalid"); cases.push({ input: { malformedRequest: request ?? null }, actual: rejected, assertions: [{ name: "malformed format envelope returns a structured error", passed: true }] }); } const lengths = []; for (const [documentId, width, height] of [["DraftCaseA", 5, 3], ["DraftCaseB", 8, 4]]) { const payload = ``; const imported = await call([{ format: "svg", direction: "import", documentId, payload }]); assert.equal(imported.kind, "result", JSON.stringify(imported)); const first = imported.result.records[0]; assert.equal(first.documentId, documentId); assert.equal(first.objectCount > 0, true); assert.equal(first.objects.every(object => object.isNull === false && object.length > 0), true); lengths.push(first.objects.reduce((sum, object) => sum + object.length, 0)); const appended = await call([{ format: "svg", direction: "import", documentId, payload }]); assert.equal(appended.kind, "result", JSON.stringify(appended)); const second = appended.result.records[0]; assert.equal(second.documentId, documentId); assert.equal(second.objectCount, first.objectCount); assert.equal(second.objects.every(object => !first.objects.some(prior => prior.objectHandle === object.objectHandle)), true); const exported = await call([{ format: "svg", direction: "export", documentId }]); assert.equal(exported.kind, "result", JSON.stringify(exported)); assert.match(exported.result.records[0].payload, / object.objectHandle)); const retainsImports = [...first.objects, ...second.objects].every(object => exportedNames.has(object.objectHandle)); assert.equal(retainsImports, true); const roundTrip = await call([{ format: "svg", direction: "import", documentId: `${documentId}RoundTrip`, payload: exported.result.records[0].payload }]); assert.equal(roundTrip.kind, "result", JSON.stringify(roundTrip)); const restored = roundTrip.result.records[0]; const exportedLength = exported.result.records[0].objects.reduce((sum, object) => sum + object.length, 0); const restoredLength = restored.objects.reduce((sum, object) => sum + object.length, 0); assert.ok(Math.abs(restoredLength - exportedLength) < 1e-6, `SVG round trip changed native perimeter: ${exportedLength} -> ${restoredLength}`); cases.push({ actual: { firstImport: first, repeatedImport: second, export: exported.result.records[0], roundTrip: restored }, input: { records: [ { format: "svg", direction: "import", documentId, payload }, { format: "svg", direction: "import", documentId, payload }, { format: "svg", direction: "export", documentId }, ] }, assertions: [ { name: "native SVG import creates geometry in the requested new document", passed: first.documentId === documentId && first.objects.every(object => object.isNull === false && object.length > 0) }, { name: "repeated import appends distinct objects to the existing document", passed: second.objects.every(object => !first.objects.some(prior => prior.objectHandle === object.objectHandle)) }, { name: "export retains geometry from both imports", passed: retainsImports }, { name: "exported SVG restores the native perimeter in another document", passed: Math.abs(restoredLength - exportedLength) < 1e-6 }, ] }); } assert.ok(Math.abs(lengths[1] / lengths[0] - 1.5) < 1e-8, `native perimeters must scale with 5x3 and 8x4 rectangles: ${lengths}`); const observedInput = { format: "svg", direction: "import", documentId: "draft-user", payload: '' }; const observed = await call([observedInput]); assert.equal(observed.kind, "result", JSON.stringify(observed)); assert.equal(observed.result.records[0].objects.every(object => object.isNull === false && object.length > 0), true); cases.push({ input: observedInput, actual: observed.result.records[0], assertions: [ { name: "original runtime issue input creates real native SVG geometry", passed: true }, { name: "different SVG dimensions produce proportional native perimeters", passed: true }, ] }); const missing = await call([{ format: "svg", direction: "export", documentId: "NoSuchDraftDocument" }]); assert.equal(missing.kind, "error"); cases.push({ input: { format: "svg", direction: "export", documentId: "NoSuchDraftDocument" }, assertions: [{ name: "missing document is rejected", passed: missing.kind === "error" }] }); const dxfCases = []; let dxfError = null; if (ids.includes(dxfId) || ids.includes("Draft:draft.formatBatch")) { try { const length = record => record.objects.reduce((sum, object) => sum + (object.length ?? 0), 0); for (const [documentId, x, y] of [["DXFBusinessCheck", 3, 4], ["DXFBusinessOther", 6, 8]]) { const payload = ["0", "SECTION", "2", "ENTITIES", "0", "LINE", "8", "0", "10", "0", "20", "0", "30", "0", "11", String(x), "21", String(y), "31", "0", "0", "ENDSEC", "0", "EOF", ""].join("\n"); const input = { format: "dxf", direction: "import", documentId, payload }; const imported = await call([input]); assert.equal(imported.kind, "result", JSON.stringify(imported)); const first = imported.result.records[0]; assert.equal(first.documentId, documentId); assert.ok(Math.abs(length(first) - Math.hypot(x, y)) < 1e-6, JSON.stringify(first)); const exported = await call([{ format: "dxf", direction: "export", documentId }]); assert.equal(exported.kind, "result", JSON.stringify(exported)); const saved = exported.result.records[0]; assert.match(saved.payload, /ENTITIES/); const roundTrip = await call([{ ...input, documentId: `${documentId}Restored`, payload: saved.payload }]); assert.equal(roundTrip.kind, "result", JSON.stringify(roundTrip)); assert.ok(Math.abs(length(roundTrip.result.records[0]) - Math.hypot(x, y)) < 1e-6); const invalid = await call([{ ...input, payload: "not a DXF document" }]); assert.equal(invalid.kind, "error", JSON.stringify(invalid)); const recovered = await call([{ format: "dxf", direction: "export", documentId }]); assert.equal(recovered.kind, "result", JSON.stringify(recovered)); assert.ok(Math.abs(length(recovered.result.records[0]) - Math.hypot(x, y)) < 1e-6); dxfCases.push({ input: { import: input, export: { format: "dxf", direction: "export", documentId }, roundTripDocument: `${documentId}Restored`, rejectedPayload: "not a DXF document" }, actual: { imported: first, exported: saved, restored: roundTrip.result.records[0], recovered: recovered.result.records[0] }, assertions: [ { name: "supplied DXF coordinates produce the expected native line length", passed: true }, { name: "native DXF export and reimport preserve geometry in another document", passed: true }, { name: "invalid import is rejected and preserves existing document geometry", passed: true }, ] }); } } catch (error) { dxfError = String(error.stack ?? error); } } timings.totalSeconds = (performance.now() - started) / 1000; const report = { schema: "freecad-web-business-cases/v1", timings, results: ids.map(capabilityId => { if (svgIds.has(capabilityId)) return { capabilityId, status: "passed", scope: "native-svg-format-workflow", cases }; if (capabilityId === dxfId || capabilityId === "Draft:draft.formatBatch") return { capabilityId, status: dxfError ? "failed" : "passed", scope: "native-svg-dxf-document-workflow", error: dxfError, resolvedIssueIds: capabilityId === "Draft:draft.formatBatch" && !dxfError ? ["runtime:8fe11605fccfd0b61b22", "runtime:be933a588c91c63f48da"] : [], cases: capabilityId === dxfId ? dxfCases : [...cases, ...dxfCases], }; return { capabilityId, status: "not_implemented", cases: [] }; }) }; await new Promise((resolve, reject) => process.stdout.write(JSON.stringify(report) + "\n", error => error ? reject(error) : resolve())); } catch (error) { fs.writeSync(2, String(error.stack ?? error) + "\n"); if (error.stdout?.length) fs.writeSync(2, error.stdout); if (error.stderr?.length) fs.writeSync(2, error.stderr); exitCode = 1; } finally { fs.rmSync(out, { recursive: true, force: true }); } process.exit(exitCode);