/** * e2e.test.ts — End-to-end tests for the complete lock lifecycle. * * @module file-claiming/e2e.test */ import { assert, mockOwner, TEST_FILE_A, TEST_FILE_B, SESSION_A, SESSION_B, } from "./test-utils.ts"; // --------------------------------------------------------------------------- // Lazy module cache // --------------------------------------------------------------------------- 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(): Promise { const reg = await getReg(); reg.resetRegistry(); const cfg = await getCfg(); cfg.resetConfig(); } // --------------------------------------------------------------------------- // Scenario 1: Single session — claim → edit → release // --------------------------------------------------------------------------- async function testSingleSessionLifecycle() { await resetAll(); const acq = await getAcq(); const reg = await getReg(); const owner = mockOwner("agent", "editor"); const claim = acq.acquireLock({ path: TEST_FILE_A, lockType: "write", owner, reason: "Editing file A" }); assert(claim.success === true, "Step 1: Claim file"); const claimId = claim.claim!.id; const active = reg.getClaimRegistry().getActiveClaims(TEST_FILE_A); assert(active.length === 1, "Step 2: File appears in active claims"); assert(acq.isFileLocked(TEST_FILE_A) === true, "Step 3: File is locked"); const info = acq.getLockInfo(TEST_FILE_A); assert(info.locked === true, "Step 4: Lock info shows locked"); assert(info.primaryClaim?.id === claimId, "Step 4a: Correct claim"); reg.getClaimRegistry().release(claimId); assert(acq.isFileLocked(TEST_FILE_A) === false, "Step 5: File unlocked after release"); console.log("✅ E2E Scenario 1: Single session lifecycle complete"); } // --------------------------------------------------------------------------- // Scenario 2: Two sessions — coordinated claim → edit → release // --------------------------------------------------------------------------- async function testTwoSessionCoordination() { await resetAll(); const acq = await getAcq(); const reg = await getReg(); const alice = mockOwner("agent", "alice", SESSION_A); const bob = mockOwner("agent", "bob", SESSION_B); const aliceClaim = acq.acquireLock({ path: TEST_FILE_A, lockType: "write", owner: alice }); assert(aliceClaim.success === true, "Alice claims file"); const bobClaim = acq.acquireLock({ path: TEST_FILE_A, lockType: "write", owner: bob }); assert(bobClaim.success === false, "Bob's claim is blocked"); const info = acq.getLockInfo(TEST_FILE_A); assert(info.primaryClaim?.owner.id === "alice", "Alice is lock holder"); reg.getClaimRegistry().release(aliceClaim.claim!.id); const bobClaim2 = acq.acquireLock({ path: TEST_FILE_A, lockType: "write", owner: bob }); assert(bobClaim2.success === true, "Bob claims after Alice releases"); const infoAfter = acq.getLockInfo(TEST_FILE_A); assert(infoAfter.primaryClaim?.owner.id === "bob", "Bob is now lock holder"); console.log("✅ E2E Scenario 2: Two-session coordination complete"); } // --------------------------------------------------------------------------- // Scenario 3: Conflict → resolve → retry → success // --------------------------------------------------------------------------- async function testConflictResolutionFlow() { await resetAll(); const acq = await getAcq(); const reg = await getReg(); const owner1 = mockOwner("agent", "user-1"); const owner2 = mockOwner("agent", "user-2"); acq.acquireLock({ path: TEST_FILE_A, lockType: "write", owner: owner1 }); const conflictResult = acq.acquireLock({ path: TEST_FILE_A, lockType: "write", owner: owner2 }); assert(conflictResult.conflict !== undefined, "Conflict detected"); const resolution = acq.resolveConflict(conflictResult.conflict!, "release"); assert(resolution.resolved === true, "Conflict resolved"); const retry = acq.acquireLock({ path: TEST_FILE_A, lockType: "write", owner: owner2 }); assert(retry.success === true, "Retry succeeds after resolution"); console.log("✅ E2E Scenario 3: Conflict → resolve → retry complete"); } // --------------------------------------------------------------------------- // Scenario 4: Config change during session // --------------------------------------------------------------------------- async function testConfigChangeDuringSession() { await resetAll(); const acq = await getAcq(); const cfg = await getCfg(); const owner = mockOwner("agent", "config-test"); const claim = acq.acquireLock({ path: TEST_FILE_A, lockType: "write", owner }); assert(claim.success === true, "Claim with default config succeeds"); cfg.setConfig({ autoReleaseTTL: 60_000 }); assert(cfg.getConfig().autoReleaseTTL === 60_000, "Config changed"); const claim2 = acq.acquireLock({ path: TEST_FILE_B, lockType: "write", owner }); assert(claim2.success === true, "New claim uses updated config"); cfg.setConfig({ showDiagnostics: false }); assert(cfg.getConfig().showDiagnostics === false, "Diagnostics disabled"); cfg.setConfig({ showDiagnostics: true }); assert(cfg.getConfig().showDiagnostics === true, "Diagnostics re-enabled"); console.log("✅ E2E Scenario 4: Config change during session works"); } // --------------------------------------------------------------------------- // Test runner // --------------------------------------------------------------------------- async function runTests() { console.log("Running End-to-End Tests\n"); const tests = [ testSingleSessionLifecycle, testTwoSessionCoordination, testConflictResolutionFlow, testConfigChangeDuringSession, ]; for (const test of tests) { try { await test(); } catch (err) { console.error(`\n❌ E2E test ${test.name} failed: ${err}`); process.exit(1); } } console.log("\n✅ All end-to-end tests passed!"); } runTests();