/** * multi-session.test.ts — Integration tests for multi-session lock coordination. * * Uses require() for pi-dependent modules to avoid ESM .d.ts resolution issues. * * @module file-claiming/multi-session.test */ import { assert, mockOwner, TEST_FILE_A, TEST_FILE_B, SESSION_A, SESSION_B, SESSION_C } from "./test-utils.ts"; // --------------------------------------------------------------------------- // Module cache (require-based for CJS compat) // --------------------------------------------------------------------------- function getAcq() { return require("../src/lock-acquisition"); } function getReg() { return require("../index"); } function getCfg() { return require("../src/config"); } function resetAll(): void { getReg().resetRegistry(); getCfg().resetConfig(); } // --------------------------------------------------------------------------- // Simulated session helper // --------------------------------------------------------------------------- class SimulatedSession { public sessionId: string; public owner: ReturnType; public claims: Set = new Set(); constructor(sessionId: string, agentId: string) { this.sessionId = sessionId; this.owner = mockOwner("agent", agentId, sessionId); } acquire(path: string, lockType: "read" | "write" | "exclusive" = "write"): any { const { acquireLock } = getAcq(); const result = acquireLock({ path, lockType, owner: this.owner }); if (result.success && result.claim) { this.claims.add(result.claim.id); } return result; } shutdown(): void { getReg().getClaimRegistry().releaseAllByOwner(this.owner); this.claims.clear(); } } // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- function testDifferentFiles() { resetAll(); const sessionA = new SimulatedSession(SESSION_A, "alpha"); const sessionB = new SimulatedSession(SESSION_B, "beta"); const resultA = sessionA.acquire(TEST_FILE_A, "write"); const resultB = sessionB.acquire(TEST_FILE_B, "write"); assert(resultA.success === true, "Session A acquires file A"); assert(resultB.success === true, "Session B acquires file B"); console.log("✅ Multi-session: different files work independently"); } function testSameFileConflict() { resetAll(); const sessionA = new SimulatedSession(SESSION_A, "alpha"); const sessionB = new SimulatedSession(SESSION_B, "beta"); const resultA = sessionA.acquire(TEST_FILE_A, "write"); assert(resultA.success === true, "Session A acquires file A"); const resultB = sessionB.acquire(TEST_FILE_A, "write"); assert(resultB.success === false, "Session B conflicts on same file"); assert(resultB.conflict !== undefined, "Conflict details present"); console.log("✅ Multi-session: same file conflict detection works"); } function testCrossSessionRelease() { resetAll(); const sessionA = new SimulatedSession(SESSION_A, "alpha"); const sessionB = new SimulatedSession(SESSION_B, "beta"); sessionA.acquire(TEST_FILE_A, "write"); const resultB = sessionB.acquire(TEST_FILE_A, "write"); assert(resultB.success === false, "Session B blocked before release"); const registry = getReg().getClaimRegistry(); const claims = registry.getActiveClaims(TEST_FILE_A); for (const c of claims) { if (c.owner.sessionId === SESSION_A) registry.release(c.id); } const resultB2 = sessionB.acquire(TEST_FILE_A, "write"); assert(resultB2.success === true, "Session B acquires after release by A"); console.log("✅ Multi-session: cross-session release unblocks"); } function testConcurrentReadLocks() { resetAll(); const sessionA = new SimulatedSession(SESSION_A, "alpha"); const sessionB = new SimulatedSession(SESSION_B, "beta"); const sessionC = new SimulatedSession(SESSION_C, "gamma"); const resultA = sessionA.acquire(TEST_FILE_A, "read"); const resultB = sessionB.acquire(TEST_FILE_A, "read"); const resultC = sessionC.acquire(TEST_FILE_A, "read"); assert(resultA.success === true, "Session A gets read lock"); assert(resultB.success === true, "Session B gets read lock"); assert(resultC.success === true, "Session C gets read lock"); const active = Object.values(getReg().getClaimRegistry().claims).filter((c: any) => c.status === "active"); const fileAReads = active.filter((c: any) => c.path === TEST_FILE_A); assert(fileAReads.length === 3, "Three concurrent read claims exist"); console.log("✅ Multi-session: concurrent read locks work"); } function testExclusiveBlocking() { resetAll(); const sessionA = new SimulatedSession(SESSION_A, "alpha"); const sessionB = new SimulatedSession(SESSION_B, "beta"); const resultA = sessionA.acquire(TEST_FILE_A, "exclusive"); assert(resultA.success === true, "Session A gets exclusive lock"); const writeTry = sessionB.acquire(TEST_FILE_A, "write"); assert(writeTry.success === false, "Exclusive blocks write"); const readTry = sessionB.acquire(TEST_FILE_A, "read"); assert(readTry.success === false, "Exclusive blocks read"); console.log("✅ Multi-session: exclusive lock blocks everything"); } function testSessionShutdownCleanup() { resetAll(); const sessionA = new SimulatedSession(SESSION_A, "alpha"); const sessionB = new SimulatedSession(SESSION_B, "beta"); sessionA.acquire(TEST_FILE_A, "write"); sessionB.acquire(TEST_FILE_B, "write"); sessionA.shutdown(); const registry = getReg().getClaimRegistry(); const aClaims = Object.values(registry.claims).filter( (c: any) => c.owner.sessionId === SESSION_A && c.status === "active", ); assert(aClaims.length === 0, "Session A claims released after shutdown"); const bClaims = Object.values(registry.claims).filter( (c: any) => c.owner.sessionId === SESSION_B && c.status === "active", ); assert(bClaims.length === 1, "Session B claims remain after A shutdown"); console.log("✅ Multi-session: session shutdown cleanup works"); } // --------------------------------------------------------------------------- // Test runner // --------------------------------------------------------------------------- function runTests() { console.log("Running Multi-Session Integration Tests\n"); const tests = [ testDifferentFiles, testSameFileConflict, testCrossSessionRelease, testConcurrentReadLocks, testExclusiveBlocking, testSessionShutdownCleanup, ]; for (const test of tests) { try { test(); } catch (err) { console.error(`\n❌ Test ${test.name} failed: ${err}`); process.exit(1); } } console.log("\n✅ All multi-session integration tests passed!"); } runTests();