import assert from "node:assert/strict"; import { getOpfsRoot, loadTextFile, saveTextFile, } from "../../../runtime/opfs/file-service.js"; import { defaultMachinePaths, gcodeProgramPath, machineIniPath, normalizeOpfsPath, parameterFilePath, previewCachePath, sessionSnapshotPath, toolTablePath, } from "../../../runtime/opfs/path-model.js"; import { createSessionSnapshot, loadSessionSnapshot, saveSessionSnapshot, validateSessionSnapshot, } from "../../../runtime/opfs/snapshot-store.js"; class MockFileHandle { constructor(name) { this.name = name; this.textValue = ""; } async createWritable() { return { write: async (text) => { this.textValue = String(text); }, close: async () => {}, }; } async getFile() { return { text: async () => this.textValue, }; } } class MockDirectoryHandle { constructor(name = "") { this.name = name; this.dirs = new Map(); this.files = new Map(); } async getDirectoryHandle(name, options = {}) { if (!this.dirs.has(name)) { if (!options.create) { throw new Error(`missing directory: ${name}`); } this.dirs.set(name, new MockDirectoryHandle(name)); } return this.dirs.get(name); } async getFileHandle(name, options = {}) { if (!this.files.has(name)) { if (!options.create) { throw new Error(`missing file: ${name}`); } this.files.set(name, new MockFileHandle(name)); } return this.files.get(name); } } const root = new MockDirectoryHandle(); const storage = { getDirectory: async () => root, }; assert.equal(await getOpfsRoot(storage), root); assert.equal(normalizeOpfsPath("linuxcnc/machines/xyzab.ini"), "linuxcnc/machines/xyzab.ini"); assert.equal(machineIniPath("xyzab-tdr"), "linuxcnc/machines/xyzab-tdr/machine.ini"); assert.equal(toolTablePath("xyzab-tdr"), "linuxcnc/machines/xyzab-tdr/tool.tbl"); assert.equal(parameterFilePath("xyzab-tdr"), "linuxcnc/machines/xyzab-tdr/linuxcnc.var"); assert.equal(gcodeProgramPath("fixture.ngc"), "linuxcnc/gcode/fixture.ngc"); assert.equal(previewCachePath("fixture"), "linuxcnc/preview-cache/fixture/preview.json"); assert.equal(sessionSnapshotPath("session-1"), "linuxcnc/sessions/session-1/snapshot.json"); assert.deepEqual(defaultMachinePaths("xyzab-tdr"), { ini: "linuxcnc/machines/xyzab-tdr/machine.ini", toolTable: "linuxcnc/machines/xyzab-tdr/tool.tbl", parameters: "linuxcnc/machines/xyzab-tdr/linuxcnc.var", }); const snapshotPayload = { files: { ini: machineIniPath("xyzab-tdr"), toolTable: toolTablePath("xyzab-tdr"), }, }; const snapshot = createSessionSnapshot("session-1", snapshotPayload, { createdAt: "2026-06-08T00:00:00.000Z", metadata: { source: "node-smoke" }, }); assert.deepEqual(snapshot, { format: "linuxcnc-wasm-session-snapshot", version: 1, sessionId: "session-1", createdAt: "2026-06-08T00:00:00.000Z", metadata: { source: "node-smoke" }, payload: snapshotPayload, }); assert.equal(validateSessionSnapshot(snapshot, "session-1"), snapshot); await saveTextFile( "linuxcnc/machines/xyzab.ini", "[EMC]\nMACHINE = opfs-smoke\n", storage, ); assert.equal( await loadTextFile("linuxcnc/machines/xyzab.ini", storage), "[EMC]\nMACHINE = opfs-smoke\n", ); await saveSessionSnapshot("session-1", snapshotPayload, { storage, createdAt: "2026-06-08T00:00:00.000Z", metadata: { source: "node-smoke" }, }); assert.deepEqual(await loadSessionSnapshot("session-1", { storage }), snapshot); await assert.rejects( () => loadTextFile("linuxcnc/machines/missing.ini", storage), /missing file/, ); await assert.rejects( () => saveTextFile("../escape.ini", "", storage), /Invalid OPFS path/, ); assert.throws( () => machineIniPath("../escape"), /Invalid machine id/, ); await saveTextFile(sessionSnapshotPath("bad-json"), "{", storage); await assert.rejects( () => loadSessionSnapshot("bad-json", { storage }), /Invalid session snapshot JSON/, ); await assert.rejects( () => loadSessionSnapshot("missing-session", { storage }), /missing directory|missing file/, ); await assert.rejects( () => getOpfsRoot({}), /OPFS is not available/, ); console.log("opfs_file_service_node_smoke=ok");