import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; import test from "node:test"; import ts from "typescript"; const repoRoot = path.resolve(import.meta.dirname, "../../.."); const sourcePath = path.join(repoRoot, "web/protocol/project-action-mutex.ts"); const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 }, fileName: sourcePath, reportDiagnostics: true, }); assert.deepEqual(transpiled.diagnostics, []); const moduleUrl = `data:text/javascript;base64,${Buffer.from(transpiled.outputText).toString("base64")}`; const { PROJECT_ACTION_CONFLICT_MATRIX, acquireProjectAction, createProjectActionMutexState, releaseProjectAction, } = await import(moduleUrl); const action = (kind, sequence) => ({ kind, actionId: `${kind}:${sequence}` }); test("M7-02 keeps the project action conflict matrix symmetric and exclusive", () => { for (const requested of ["OPEN", "SAVE", "CLOSE"]) { for (const owner of ["OPEN", "SAVE", "CLOSE"]) { assert.equal(PROJECT_ACTION_CONFLICT_MATRIX[requested][owner], true); assert.equal(PROJECT_ACTION_CONFLICT_MATRIX[requested][owner], PROJECT_ACTION_CONFLICT_MATRIX[owner][requested]); } } }); test("M7-02 rejects repeated open with its original owner unchanged", () => { const first = action("OPEN", 1); const second = action("OPEN", 2); const acquired = acquireProjectAction(createProjectActionMutexState(), first); assert.equal(acquired.granted, true); assert.deepEqual(acquireProjectAction(acquired.state, second), { granted: false, state: acquired.state, conflict: { code: "USER_ACTION_CONFLICT", reason: "REPEATED_OPEN", requested: second, owner: first }, }); }); test("M7-02 rejects concurrent save and close-during-save with stable reasons", () => { const first = action("SAVE", 1); const acquired = acquireProjectAction(createProjectActionMutexState(), first); assert.equal(acquired.granted, true); assert.equal(acquireProjectAction(acquired.state, action("SAVE", 2)).conflict.reason, "CONCURRENT_SAVE"); assert.equal(acquireProjectAction(acquired.state, action("CLOSE", 3)).conflict.reason, "CLOSE_DURING_SAVE"); }); test("M7-02 only lets the exact owner release the project lock", () => { const owner = action("SAVE", 1); const acquired = acquireProjectAction(createProjectActionMutexState(), owner); assert.equal(acquired.granted, true); assert.deepEqual(releaseProjectAction(acquired.state, action("SAVE", 2)), { released: false, state: acquired.state, errorCode: "PROJECT_ACTION_LOCK_IDENTITY_MISMATCH", }); assert.deepEqual(releaseProjectAction(acquired.state, owner), { released: true, state: { owner: null }, errorCode: null, }); });