Files
FreeCADWasmDev/web/tools/test-module-router.mjs
wangdequan 9a7c4f0335 Save FreeCAD Web workspace sources and user validation evidence
IFC native binding remains incomplete; preserve accurate test results.
2026-09-15 22:01:58 +08:00

107 lines
5.1 KiB
JavaScript

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 registrySource = fs.readFileSync(new URL("../src/module-registry.generated.ts", import.meta.url), "utf8");
const registryAst = ts.createSourceFile("registry.ts", registrySource, ts.ScriptTarget.Latest, true);
const routes = new Map();
const mapping = registryAst.statements.find(node => ts.isFunctionDeclaration(node) && node.name?.text === "getWebModuleWorkerForCommand");
const inspect = node => {
if (ts.isCaseClause(node) && ts.isStringLiteral(node.expression)) {
const result = node.statements.find(ts.isReturnStatement)?.expression;
if (result && ts.isStringLiteral(result)) routes.set(node.expression.text, result.text);
}
ts.forEachChild(node, inspect);
};
inspect(mapping);
function fixture() {
const workers = [];
const route = { module: "TechDraw", primaryWorker: "techdraw-native.worker.ts", commands: [
"techdraw.createRecompute", "techdraw.desktopCommand", "techdraw.nativeSaveReopen",
] };
const registry = {
getWebModuleRoute: module => module === "TechDraw" ? route : undefined,
getWebModuleWorkerForCommand: (module, command) => routes.get(`${module}:${command}`) ?? route.primaryWorker,
createWebModuleWorker: (_module, workerFile) => {
const worker = { workerFile, sent: [], terminated: 0,
postMessage(message) { this.sent.push(message); },
terminate() { this.terminated += 1; },
emit(data) { this.onmessage({ data }); },
};
workers.push(worker);
return worker;
},
};
const cache = new Map([
["./module-registry.generated.js", registry],
["./module-loader.js", {}],
["./native-source-policy.js", { nativeSourceDecision: () => undefined }],
]);
function load(name) {
if (cache.has(name)) return cache.get(name);
const source = fs.readFileSync(new URL(`../src/${name.replace(/^\.\//, "").replace(/\.js$/, ".ts")}`, import.meta.url), "utf8");
const compiled = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 } }).outputText;
const module = { exports: {} };
vm.runInNewContext("(function(exports,require,module){" + compiled + "})", { AbortController })(module.exports, load, module);
cache.set(name, module.exports);
return module.exports;
}
return { router: new (load("./module-router.js").WebModuleRouter)(), workers };
}
test("drawing, desktop commands and save use one existing native document worker", async () => {
const { router, workers } = fixture();
try {
for (const command of ["techdraw.createRecompute", "techdraw.desktopCommand", "techdraw.nativeSaveReopen"]) {
assert.equal(routes.get(`TechDraw:${command}`), "techdraw-python.worker.ts");
const result = router.request("TechDraw", command, { documentId: "CurrentDrawing" });
if (workers.length === 1 && workers[0].sent.length === 0) workers[0].emit({ kind: "ready" });
await Promise.resolve();
const request = workers[0].sent.at(-1);
workers[0].emit({ kind: "result", requestId: request.requestId, result: { documentId: "CurrentDrawing" } });
assert.equal((await result).result.documentId, "CurrentDrawing");
}
assert.equal(workers.length, 1);
assert.equal(workers[0].workerFile, "techdraw-python.worker.ts");
} finally { router.dispose(); }
});
test("uncorrelated worker errors reject pending commands instead of waiting forever", async () => {
const { router, workers } = fixture();
const result = router.request("TechDraw", "techdraw.createRecompute", {});
workers[0].emit({ kind: "ready" });
await Promise.resolve();
const rejected = assert.rejects(result, /invalid_request: wrong envelope/);
workers[0].emit({ kind: "error", requestId: "invalid", error: { code: "invalid_request", message: "wrong envelope" } });
await rejected;
assert.equal(router.listLoaded().length, 0);
assert.equal(workers[0].terminated, 1);
});
test("initialization failure rejects readiness and permits a fresh worker", async () => {
const { router, workers } = fixture();
const first = router.preload("TechDraw", "techdraw-python.worker.ts");
const rejected = assert.rejects(first, /kernel_load_failed: missing wasm/);
workers[0].emit({ kind: "error", requestId: "initialization", error: { code: "kernel_load_failed", message: "missing wasm" } });
await rejected;
const retry = router.preload("TechDraw", "techdraw-python.worker.ts");
assert.equal(workers.length, 2);
workers[1].emit({ kind: "ready" });
await retry;
router.dispose();
});
test("correlated business failure keeps the healthy worker available", async () => {
const { router, workers } = fixture();
const result = router.request("TechDraw", "techdraw.createRecompute", {});
workers[0].emit({ kind: "ready" });
await Promise.resolve();
workers[0].emit({ kind: "error", requestId: workers[0].sent[0].requestId, error: { code: "invalid_argument", message: "bad size" } });
assert.equal((await result).error.code, "invalid_argument");
assert.equal(router.listLoaded().length, 1);
router.dispose();
});