Files
pi-file-claiming/tests/multi-session.test.ts
2026-06-19 12:46:02 -04:00

192 lines
6.6 KiB
TypeScript

/**
* 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<typeof mockOwner>;
public claims: Set<string> = 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();