import assert from "node:assert/strict"; import fs from "node:fs"; import vm from "node:vm"; import test from "node:test"; import ts from "typescript"; const modules = new Map(); function load(name) { if (modules.has(name)) return modules.get(name); const source = fs.readFileSync(new URL(`../src/${name.replace(/^\.\//, "").replace(/\.js$/, ".ts")}`, import.meta.url), "utf8"); const code = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 } }).outputText; const module = { exports: {} }; vm.runInNewContext("(function(exports,require,module){" + code + "})", { AbortController, ArrayBuffer, Uint8Array })(module.exports, load, module); modules.set(name, module.exports); return module.exports; } const facade = load("./partdesign-document-facade.js"); const input = { schema: facade.PARTDESIGN_DOCUMENT_SCHEMA, module: "PartDesign", document: "Current", action: "setSketch", body: "Body", object: "Sketch", profileType: "Rectangle", parameters: { Width: 10, Height: 8 }, position: [2, 3, 0] }; const sketch = { name: "Sketch", type: "Sketcher::SketchObject", body: "Body", profileType: "Rectangle", parameters: input.parameters, position: input.position, dof: 0, fullyConstrained: true }; const response = object => ({ ok: true, result: { document: "Current", objectCount: 1, objects: [object] } }); const call = (payload, kernel) => facade.handleDesignDocument(payload, new AbortController().signal, kernel); test("native sketch requests preserve document ownership, dimensions and placement", async () => { let request; await call(input, { ccall(_name, _type, _types, args) { request = JSON.parse(args[0]); return JSON.stringify(response(sketch)); } }); assert.deepEqual(request, { cmd: "partdesign.document", args: input }); }); test("invalid profile parameters fail before calling the native module", async () => { for (const override of [{ document: "other/doc" }, { body: "" }, { parameters: { Width: 10 } }, { parameters: { Width: 0, Height: 4 } }, { parameters: { Width: Infinity, Height: 4 } }, { profileType: "constructor" }, { position: [1, 2] }, { position: [NaN, 0, 0] }]) await assert.rejects(call({ ...input, ...override }, { ccall() { assert.fail("invalid request reached native code"); } })); }); test("underconstrained, wrong-body and incomplete native replies cannot pass", async () => { for (const override of [{ body: "Other" }, { dof: 2 }, { fullyConstrained: false }, { parameters: { Width: 10 } }, { parameters: { Width: null, Height: 8 } }, { position: [0, 0, 0] }]) await assert.rejects(call(input, { ccall: () => JSON.stringify(response({ ...sketch, ...override })) }), /does not match/); }); test("Pad and Pocket require matching native Profile links, length and direction", async () => { const payload = { ...input, action: "setFeature", object: "Pocket", featureType: "Pocket", profile: "Sketch", length: 3, reversed: false }; const feature = { name: "Pocket", type: "PartDesign::Pocket", body: "Body", profile: "Sketch", length: 3, reversed: false, volume: 442.3 }; await call(payload, { ccall: () => JSON.stringify(response(feature)) }); for (const override of [{ profile: "Other" }, { length: 5 }, { reversed: true }, { volume: null }]) await assert.rejects(call(payload, { ccall: () => JSON.stringify(response({ ...feature, ...override })) }), /links or dimensions/); }); test("native failures and invalid FCStd paths are not converted into success", async () => { await assert.rejects(call(input, { ccall: () => JSON.stringify({ ok: false, code: "partdesign_document_failed", stage: "recompute", error: "profile invalid" }) }), /recompute: profile invalid/); await assert.rejects(call({ ...input, action: "save", path: "/other/document.FCStd" }, { ccall() { assert.fail("invalid path reached native code"); } }), /document-local/); }); test("subtractive features require matching native types and persistent section/spine links", async () => { for (const type of ["SubtractiveLoft", "SubtractivePipe"]) { const links = type.endsWith("Loft") ? { sections: ["Section"], ruled: true, closed: false } : { spine: "Base", spineEdges: ["Edge1"] }; const payload = { ...input, action: "setFeature", featureType: type, object: "Result", profile: "Sketch", ...links }; const feature = { name: "Result", type: `PartDesign::${type}`, body: "Body", profile: "Sketch", reversed: false, volume: 970, ...links }; await call(payload, { ccall: () => JSON.stringify(response(feature)) }); for (const override of [{ type: feature.type.replace("Subtractive", "Additive") }, { body: "Other" }, { volume: null }, ...(type.endsWith("Loft") ? [{ sections: ["Missing"] }, { ruled: false }] : [{ spine: "Other" }, { spineEdges: [] }])]) await assert.rejects(call(payload, { ccall: () => JSON.stringify(response({ ...feature, ...override })) })); for (const override of type.endsWith("Loft") ? [{ sections: [] }, { sections: ["Section", "Section"] }, { closed: "yes" }] : [{ spine: "" }, { spineEdges: ["bad"] }]) await assert.rejects(call({ ...payload, ...override }, { ccall() { assert.fail("invalid subtraction reached native code"); } })); } }); test("subtractive desktop routes cannot turn a subtraction into an additive request", async () => { const { executePartDesignDesktop } = load("./partdesign-desktop-adapter.js"); for (const type of ["SubtractiveLoft", "SubtractivePipe"]) { const requests = []; const router = { request: async (...args) => { requests.push(args); return { kind: "result", result: { document: "Current", objects: [{ name: "Result" }] } }; } }; const args = { body: "Body", object: "Result", profile: "Sketch", featureType: "Loft", sections: ["Section"], spine: "Base", spineEdges: ["Edge1"] }; assert.equal((await executePartDesignDesktop(router, `PartDesign_${type}`, args, "Current")).kind, "result"); assert.equal(requests[0][2].featureType, type); assert.equal(requests[0][1], "partdesign.document"); assert.equal((await executePartDesignDesktop(router, `PartDesign_${type}`, { ...args, activeTask: true }, "Current")).kind, "error"); assert.equal(requests.length, 1); } }); test("rotary feature validates angle, native axis ownership result and direction", async () => { const payload = { ...input, action: "setFeature", object: "Turn", featureType: "Revolution", profile: "Sketch", angle: 180, axis: "Y_Axis", reversed: true }; const feature = { name: "Turn", type: "PartDesign::Revolution", body: "Body", profile: "Sketch", angle: 180, axis: "Y_Axis", reversed: true, volume: 200 }; await call(payload, { ccall: () => JSON.stringify(response(feature)) }); for (const override of [{ angle: 0 }, { angle: 361 }, { axis: "" }, { axis: "other/axis" }]) await assert.rejects(call({ ...payload, ...override }, { ccall() { assert.fail("invalid rotary request reached native"); } })); for (const override of [{ angle: 360 }, { axis: "X_Axis" }, { reversed: false }]) await assert.rejects(call(payload, { ccall: () => JSON.stringify(response({ ...feature, ...override })) })); }); test("dress-up validates explicit edge selection and matching native feature result", async () => { const payload = { ...input, action: "setDressUp", object: "Round", dressUpType: "Fillet", base: "Pad", size: 0.8, edges: ["Edge1", "Edge3"] }; const feature = { name: "Round", type: "PartDesign::Fillet", dressUpType: "Fillet", body: "Body", base: "Pad", size: 0.8, edges: payload.edges, volume: 190 }; await call(payload, { ccall: () => JSON.stringify(response(feature)) }); for (const override of [{ size: 0 }, { base: "Round" }, { edges: [] }, { edges: ["Edge0"] }, { edges: ["Edge1", "Edge1"] }, { dressUpType: "Other" }]) await assert.rejects(call({ ...payload, ...override }, { ccall() { assert.fail("invalid dress-up request reached native"); } })); for (const override of [{ base: "Other" }, { edges: ["Edge1"] }, { size: 1 }]) await assert.rejects(call(payload, { ccall: () => JSON.stringify(response({ ...feature, ...override })) })); }); test("geometry export returns isolated native bytes only for the requested object and path", async () => { const payload = { ...input, action: "export", object: "Pad", path: "/tmp/Selected.step" }; const file = new Uint8Array([1, 2, 3, 4]); const native = { ...response({ name: "Pad", type: "PartDesign::Pad", volume: 192 }).result, path: payload.path, size: file.length, exportedObject: "Pad" }; const kernel = result => ({ ccall: () => JSON.stringify({ ok: true, result }), FS: { readFile: path => { assert.equal(path, payload.path); return file; } } }); const actual = await call(payload, kernel(native)); assert.deepEqual(new Uint8Array(actual.bytes), file); file[0] = 9; assert.equal(new Uint8Array(actual.bytes)[0], 1); for (const override of [{ exportedObject: "Body" }, { path: "/tmp/Other.step" }, { size: 0 }, { size: 3 }]) await assert.rejects(call(payload, kernel({ ...native, ...override }))); }); test("geometry input bytes are staged once and released on success and native failure", async () => { const payload = { ...input, action: "importShape", object: "Imported", path: "/tmp/Selected.STP", bytes: new Uint8Array([2, 4, 8]).buffer }; for (const succeeded of [true, false]) { const operations = []; const kernel = { FS: { writeFile: (path, bytes) => { assert.equal(path, payload.path); assert.deepEqual([...bytes], [2, 4, 8]); operations.push("write"); }, unlink: path => { assert.equal(path, payload.path); operations.push("unlink"); }, }, ccall(_name, _type, _types, args) { const native = JSON.parse(args[0]); assert.equal(native.args.bytes, undefined); assert.equal(native.args.object, "Imported"); operations.push("native"); return JSON.stringify(succeeded ? response({ name: "Imported", type: "Part::Feature", volume: 192 }) : { ok: false, error: "invalid file" }); }, }; if (succeeded) await call(payload, kernel); else await assert.rejects(call(payload, kernel), /invalid file/); assert.deepEqual(operations, ["write", "native", "unlink"]); } }); test("invalid geometry requests and cancellation never write files or call native code", async () => { const payload = { ...input, action: "importShape", object: "Imported", path: "/tmp/Selected.brep", bytes: new Uint8Array([1]).buffer }; const kernel = { ccall() { assert.fail("unexpected native call"); }, FS: { writeFile() { assert.fail("unexpected file write"); } } }; for (const override of [{ path: "/outside.step" }, { path: "/tmp/../Selected.step" }, { path: "/tmp/Selected.exe" }, { object: "../Imported" }, { bytes: new ArrayBuffer(0) }, { bytes: [1] }]) await assert.rejects(call({ ...payload, ...override }, kernel)); const controller = new AbortController(); const result = facade.handleDesignDocument(payload, controller.signal, Promise.resolve(kernel)); controller.abort(); await assert.rejects(result, /cancelled/i); });