169 lines
6.1 KiB
TypeScript
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();
|