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

169 lines
6.1 KiB
TypeScript

/**
* 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<void> {
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();