Files
FreeCADWasmDev/tools/run_p6_assembly_facade_smoke.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

111 lines
5.5 KiB
JavaScript

#!/usr/bin/env node
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
const modulePath = process.argv[2];
if (!modulePath) throw new Error("compiled assembly-facade module path is required");
const facade = await import(modulePath);
const envelope = await import(path.resolve(path.dirname(modulePath), "facade-envelope.js"));
const {
ASSEMBLY_ACTION_SCHEMA,
ASSEMBLY_COMMAND,
ASSEMBLY_FIXTURE,
ASSEMBLY_OBJECT_TYPE,
createAssemblyRequest,
handleAssemblyCreateRecompute,
isAssemblyRequest,
} = facade;
const { FacadeSerialQueue, createCancel, isFacadeResponse } = envelope;
const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), "..");
const docsRoot = path.join(root, "docs");
const outputPath = process.env.P6_ASSEMBLY_FACADE_OUTPUT ?? path.join(docsRoot, "p6", "assembly-facade.json");
const schemaPath = path.join(docsRoot, "p6", "assembly-facade.schema.json");
const logPath = path.join(docsRoot, "freecad-p6-asm-e-facade.log");
const queue = new FacadeSerialQueue();
const trace = [];
let active = 0;
let maxActive = 0;
async function dispatch(request) {
trace.push({ direction: "request", requestId: request.requestId, command: request.command, payload: request.payload });
const response = await queue.dispatch(request, async (payload, signal) => {
active += 1;
maxActive = Math.max(maxActive, active);
trace.push({ direction: "handler-start", requestId: request.requestId });
try {
return handleAssemblyCreateRecompute(payload, signal);
} finally {
active -= 1;
trace.push({ direction: "handler-end", requestId: request.requestId });
}
});
trace.push({ direction: "response", requestId: response.requestId, kind: response.kind, code: response.kind === "error" ? response.error.code : null });
return response;
}
const success = await dispatch(createAssemblyRequest("asm-success"));
const expectedError = await dispatch(createAssemblyRequest("asm-error", { ...ASSEMBLY_FIXTURE, objectType: "Assembly::MissingObject" }));
const duplicate = await dispatch(createAssemblyRequest("asm-success"));
const cancelRequest = createAssemblyRequest("asm-cancel");
const cancelPromise = dispatch(cancelRequest);
trace.push({ direction: "cancel", requestId: "cancel-1", cancelOf: cancelRequest.requestId });
const cancelAccepted = queue.cancel(createCancel("cancel-1", cancelRequest.requestId, "assembly-doc"));
const cancelled = await cancelPromise;
const afterCancel = await dispatch(createAssemblyRequest("asm-after-cancel"));
assert.equal(ASSEMBLY_COMMAND, "assembly.createRecompute");
assert.equal(ASSEMBLY_OBJECT_TYPE, "Assembly::AssemblyObject");
assert.equal(isAssemblyRequest(createAssemblyRequest("schema-test")), true);
assert.equal(isAssemblyRequest({ ...createAssemblyRequest("schema-test"), command: "other" }), false);
assert.equal(success.kind, "result");
assert.equal(success.result.schema, ASSEMBLY_ACTION_SCHEMA);
assert.equal(success.result.object.type, ASSEMBLY_OBJECT_TYPE);
assert.equal(success.result.document.objectCount, 9);
assert.equal(expectedError.kind, "error");
assert.equal(expectedError.error.code, "invalid_type");
assert.equal(duplicate.kind, "error");
assert.equal(duplicate.error.code, "duplicate_request");
assert.equal(cancelAccepted, true);
assert.equal(cancelled.kind, "error");
assert.equal(cancelled.error.code, "cancelled");
assert.equal(afterCancel.kind, "result");
assert.equal(afterCancel.result.stateSha256, success.result.stateSha256);
assert.equal(maxActive, 1);
assert.equal(active, 0);
for (const response of [success, expectedError, duplicate, cancelled, afterCancel]) assert.equal(isFacadeResponse(response), true);
const schema = JSON.parse(fs.readFileSync(schemaPath, "utf8"));
assert.equal(schema.$id, "https://freecad.org/schemas/freecad-web-assembly-create-recompute-v1.json");
assert.equal(schema.$defs.request.properties.command.const, ASSEMBLY_COMMAND);
assert.equal(schema.$defs.request.properties.payload.properties.objectType.const, ASSEMBLY_OBJECT_TYPE);
assert.equal(schema.$defs.result.properties.result.properties.schema.const, ASSEMBLY_ACTION_SCHEMA);
const report = {
schema: "freecad-web-p6-assembly-facade/v1",
result: "pass",
action: ASSEMBLY_COMMAND,
envelopeSchema: "freecad-web-facade/v1",
typedSchema: ASSEMBLY_ACTION_SCHEMA,
fixture: { ...ASSEMBLY_FIXTURE, inputSha256: crypto.createHash("sha256").update(JSON.stringify(ASSEMBLY_FIXTURE)).digest("hex") },
responses: { success, expectedError, duplicate, cancelled, afterCancel },
trace,
sequencing: { maxActive, activeAfter: active, requestCount: 5, responseCount: 5, cancelAccepted },
state: { beforeCancel: success.result.stateSha256, afterCancel: afterCancel.result.stateSha256, unchangedAfterCancel: success.result.stateSha256 === afterCancel.result.stateSha256 },
schemaChecks: { request: true, result: true, expectedError: true, duplicate: true, cancel: true, noNativePointers: !JSON.stringify(reportSafe(trace)).match(/pointer|native/i) },
evidence: { schema: path.relative(root, schemaPath), workerLog: path.relative(root, logPath) },
};
function reportSafe(value) {
return value;
}
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
fs.writeFileSync(outputPath, `${JSON.stringify(report, null, 2)}\n`);
fs.writeFileSync(logPath, `${trace.map((event, index) => `${String(index + 1).padStart(2, "0")} ${JSON.stringify(event)}`).join("\n")}\nresult=pass\nmaxActive=${maxActive}\n`);
console.log(JSON.stringify({ result: report.result, requests: report.sequencing.requestCount, responses: report.sequencing.responseCount, maxActive }));