/** * lock-acquisition.test.ts — Unit tests for lock acquisition module. * * @module file-claiming/lock-acquisition.test */ import { assert, mockOwner, TEST_FILE_A, TEST_FILE_B, SESSION_A, SESSION_B, } from "./test-utils.ts"; // --------------------------------------------------------------------------- // Lazy module getters (dynamic import for ESM compat) // --------------------------------------------------------------------------- let _acq: any = null; let _reg: any = null; let _cfg: any = null; async function getAcq() { if (!_acq) _acq = await import("./lock-acquisition.ts"); return _acq; } async function getReg() { if (!_reg) _reg = await import("./index.ts"); return _reg; } async function getCfg() { if (!_cfg) _cfg = await import("./config.ts"); return _cfg; } async function resetAll() { const reg = await getReg(); const cfg = await getCfg(); reg.resetRegistry(); cfg.resetConfig(); } // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- async function testAcquireLockBasic() { await resetAll(); const acq = await getAcq(); const reg = await getReg(); const owner = mockOwner("agent", "main"); const result = acq.acquireLock({ path: TEST_FILE_A, lockType: "write", owner }); assert(result.success === true, "acquireLock succeeds on unclaimed file"); assert(result.claim !== undefined, "acquireLock returns a claim"); assert(result.claim!.path === TEST_FILE_A, "Claim path matches"); assert(result.claim!.lockType === "write", "Claim lock type matches"); assert(result.claim!.status === "active", "Claim status is active"); console.log("✅ acquireLock: basic acquisition works"); } async function testAcquireLockConflict() { await resetAll(); const acq = await getAcq(); const owner1 = mockOwner("agent", "owner-1"); const owner2 = mockOwner("agent", "owner-2"); acq.acquireLock({ path: TEST_FILE_A, lockType: "write", owner: owner1 }); const conflict = acq.acquireLock({ path: TEST_FILE_A, lockType: "write", owner: owner2 }); assert(conflict.success === false, "Conflict: write on write-locked file"); assert(conflict.conflict !== undefined, "Conflict details provided"); console.log("✅ acquireLock: conflict detection works"); } async function testCompatibleReadLocks() { await resetAll(); const acq = await getAcq(); const reg = await getReg(); const owner1 = mockOwner("agent", "reader-1"); const owner2 = mockOwner("agent", "reader-2"); acq.acquireLock({ path: TEST_FILE_B, lockType: "read", owner: owner1 }); const result = acq.acquireLock({ path: TEST_FILE_B, lockType: "read", owner: owner2 }); assert(result.success === true, "Multiple read locks are compatible"); const active = reg.getClaimRegistry().getActiveClaims(TEST_FILE_B); assert(active.length === 2, "Two active read claims"); console.log("✅ acquireLock: compatible read locks work"); } async function testAcquireLockTTL() { await resetAll(); const acq = await getAcq(); const cfg = await getCfg(); cfg.setConfig({ autoReleaseTTL: 10_000 }); const owner = mockOwner("agent", "main"); const result = acq.acquireLock({ path: TEST_FILE_A, lockType: "write", owner, autoReleaseTTL: 5_000 }); assert(result.success === true, "Lock with TTL succeeds"); assert(result.claim!.expiresAt !== undefined, "Claim has expiresAt"); const result2 = acq.acquireLock({ path: TEST_FILE_B, lockType: "read", owner, autoReleaseTTL: 0 }); assert(result2.success === true, "Lock with 0 TTL succeeds"); assert(result2.claim!.expiresAt === undefined, "Zero TTL claim has no expiresAt"); console.log("✅ acquireLock: TTL handling works"); } async function testAutoClaim() { await resetAll(); const acq = await getAcq(); const owner = mockOwner("agent", "main"); const result = acq.autoClaim({ path: TEST_FILE_A, lockType: "write", owner }); assert(result.success === true, "Auto-claim succeeds"); assert(result.autoClaimed === true, "autoClaimed flag is true"); const other = mockOwner("agent", "other"); const conflict = acq.autoClaim({ path: TEST_FILE_A, lockType: "write", owner: other }); assert(conflict.success === false, "Auto-claim conflict with other owner"); console.log("✅ autoClaim: auto-claim behavior works"); } async function testIsFileLocked() { await resetAll(); const acq = await getAcq(); const owner = mockOwner("agent", "main"); assert(acq.isFileLocked(TEST_FILE_A) === false, "Unclaimed file is not locked"); acq.acquireLock({ path: TEST_FILE_A, lockType: "write", owner }); assert(acq.isFileLocked(TEST_FILE_A) === true, "Write-locked file shows locked"); console.log("✅ isFileLocked: lock detection works"); } async function testGetLockInfo() { await resetAll(); const acq = await getAcq(); const owner = mockOwner("agent", "main"); const beforeInfo = acq.getLockInfo(TEST_FILE_A); assert(beforeInfo.locked === false, "Info: no lock before acquisition"); acq.acquireLock({ path: TEST_FILE_A, lockType: "write", owner, reason: "testing" }); const info = acq.getLockInfo(TEST_FILE_A); assert(info.locked === true, "Info: locked after acquisition"); assert(info.path === TEST_FILE_A, "Info: path matches"); assert(info.claims.length > 0, "Info: has claims"); assert(info.locks.length > 0, "Info: has locks"); assert(info.primaryLock !== undefined, "Info: has primary lock"); assert(info.lockType === "write", "Info: lock type is write"); console.log("✅ getLockInfo: detailed lock information works"); } async function testBuildBlockingError() { await resetAll(); const acq = await getAcq(); const owner = mockOwner("agent", "main"); const noLock = acq.buildBlockingError(TEST_FILE_A); assert(noLock.includes("not locked"), "Non-locked file shows not locked"); acq.acquireLock({ path: TEST_FILE_A, lockType: "write", owner }); const blocked = acq.buildBlockingError(TEST_FILE_A); assert(blocked.includes("locked"), "Blocking error mentions locked"); assert(blocked.includes("Release lock"), "Blocking error suggests action"); console.log("✅ buildBlockingError: lock-contention error messages work"); } async function testResolveConflict() { await resetAll(); const acq = await getAcq(); const reg = await getReg(); const owner1 = mockOwner("agent", "blocker"); const owner2 = mockOwner("agent", "blocked"); acq.acquireLock({ path: TEST_FILE_A, lockType: "write", owner: owner1 }); const conflict = reg.getClaimRegistry().checkConflict(TEST_FILE_A, "write", owner2); assert(conflict !== undefined, "Conflict exists for resolution tests"); const release = acq.resolveConflict(conflict!, "release"); assert(release.resolved === true, "Release strategy resolves conflict"); console.log("✅ resolveConflict: resolution strategies work"); } async function testMutationTools() { const acq = await getAcq(); assert(acq.isMutationTool("edit") === true, "edit is mutation tool"); assert(acq.isMutationTool("read") === false, "read is not mutation tool"); assert(acq.shouldAutoClaim("edit") === true, "edit triggers auto-claim"); console.log("✅ isMutationTool / shouldAutoClaim: tool detection works"); } async function testHandleToolLock() { await resetAll(); const acq = await getAcq(); const owner = mockOwner("agent", "main"); const mutResult = acq.handleToolLock("edit", TEST_FILE_A, "write", owner); assert(mutResult.success === true, "handleToolLock for edit succeeds"); console.log("✅ handleToolLock: tool integration handler works"); } async function testCheckToolBlocking() { await resetAll(); const acq = await getAcq(); const cfg = await getCfg(); const owner = mockOwner("agent", "main"); const notBlocked = acq.checkToolBlocking("edit", TEST_FILE_A); assert(notBlocked === null, "No blocking when no locks"); acq.acquireLock({ path: TEST_FILE_A, lockType: "write", owner }); const blocked = acq.checkToolBlocking("edit", TEST_FILE_A); assert(blocked !== null, "Blocked tool returns blocking result"); assert(blocked!.block === true, "Blocking result has block=true"); cfg.setConfig({ blockedTools: [] }); const emptyBlocked = acq.checkToolBlocking("edit", TEST_FILE_A); assert(emptyBlocked === null, "Empty blockedTools means no blocking"); console.log("✅ checkToolBlocking: tool blocking checks work"); } async function testGetLockStatusString() { await resetAll(); const acq = await getAcq(); const owner = mockOwner("agent", "main"); const freeStr = acq.getLockStatusString(TEST_FILE_B); assert(freeStr.includes("FREE"), "Free file shows FREE"); acq.acquireLock({ path: TEST_FILE_A, lockType: "write", owner }); const lockedStr = acq.getLockStatusString(TEST_FILE_A); assert(lockedStr.includes("LOCKED"), "Locked file shows LOCKED"); console.log("✅ getLockStatusString: status string works"); } async function testIsToolBlockedFromPath() { await resetAll(); const acq = await getAcq(); const owner = mockOwner("agent", "main"); assert(acq.isToolBlockedFromPath("edit", TEST_FILE_A) === false, "Not blocked on unlocked file"); acq.acquireLock({ path: TEST_FILE_A, lockType: "write", owner }); assert(acq.isToolBlockedFromPath("edit", TEST_FILE_A) === true, "Edit blocked on locked file"); console.log("✅ isToolBlockedFromPath: per-path blocking works"); } async function testConcurrentAccess() { await resetAll(); const acq = await getAcq(); const owner1 = mockOwner("agent", "main", SESSION_A); const owner2 = mockOwner("agent", "other", SESSION_B); acq.acquireLock({ path: TEST_FILE_A, lockType: "read", owner: owner1 }); const concurrent = acq.getConcurrentAccess(TEST_FILE_A, SESSION_B); assert(concurrent.length === 1, "One concurrent access detected"); const report = acq.buildConcurrentAccessReport(TEST_FILE_A, SESSION_B); assert(report.includes("Concurrent access"), "Report mentions concurrent access"); console.log("✅ Concurrent access: detection and reporting works"); } // --------------------------------------------------------------------------- // Test runner // --------------------------------------------------------------------------- async function runTests() { console.log("Running Lock Acquisition Unit Tests\n"); const tests = [ testAcquireLockBasic, testAcquireLockConflict, testCompatibleReadLocks, testAcquireLockTTL, testAutoClaim, testIsFileLocked, testGetLockInfo, testBuildBlockingError, testResolveConflict, testMutationTools, testHandleToolLock, testCheckToolBlocking, testGetLockStatusString, testIsToolBlockedFromPath, testConcurrentAccess, ]; try { for (const test of tests) { try { await test(); } catch (err) { console.error(`\n❌ Test ${test.name} failed: ${err}`); throw err; } } console.log("\n✅ All lock acquisition unit tests passed!"); } catch (err) { console.error(`\n❌ Test suite failed: ${err}`); process.exit(1); } } runTests();