/**
* Agent Synch MCP Server - Lock Manager Tests
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { LockManager } from './lock-manager';
import * as fs from 'fs/promises';
import * as path from 'path';
import * as os from 'os';
describe('LockManager', () => {
let lockManager: LockManager;
let testDir: string;
beforeEach(async () => {
testDir = path.join(os.tmpdir(), `lock-test-${Date.now()}`);
lockManager = new LockManager(testDir, 5000); // 5s timeout
await lockManager.initialize();
});
afterEach(async () => {
await fs.rm(testDir, { recursive: true, force: true });
});
describe('Basic Locking', () => {
it('should acquire lock on unlocked resource', async () => {
const acquired = await lockManager.acquireLock('file1', 'agent-a', 'write');
expect(acquired).toBe(true);
});
it('should return lock status', async () => {
await lockManager.acquireLock('file1', 'agent-a', 'write');
const status = lockManager.getLockStatus('file1');
expect(status).not.toBeNull();
expect(status?.agentId).toBe('agent-a');
});
it('should release lock', async () => {
await lockManager.acquireLock('file1', 'agent-a', 'write');
const released = await lockManager.releaseLock('file1', 'agent-a');
expect(released).toBe(true);
expect(lockManager.getLockStatus('file1')).toBeNull();
});
it('should not release lock for wrong agent', async () => {
await lockManager.acquireLock('file1', 'agent-a', 'write');
const released = await lockManager.releaseLock('file1', 'agent-b');
expect(released).toBe(false);
});
});
describe('Queue Behavior', () => {
it('should queue second agent request', async () => {
await lockManager.acquireLock('file1', 'agent-a', 'write');
// Agent B tries to acquire - should queue
const agentBPromise = lockManager.acquireLock('file1', 'agent-b', 'write', 1000);
expect(lockManager.getQueueLength('file1')).toBe(1);
// Release agent A's lock
await lockManager.releaseLock('file1', 'agent-a');
// Agent B should now have the lock
const agentBAcquired = await agentBPromise;
expect(agentBAcquired).toBe(true);
expect(lockManager.getLockStatus('file1')?.agentId).toBe('agent-b');
});
it('should timeout queued request', async () => {
await lockManager.acquireLock('file1', 'agent-a', 'write');
// Agent B tries with short timeout
const acquired = await lockManager.acquireLock('file1', 'agent-b', 'write', 100);
expect(acquired).toBe(false);
});
});
describe('Lock Extension', () => {
it('should extend lock for same agent', async () => {
await lockManager.acquireLock('file1', 'agent-a', 'write', 500);
// Wait a bit so the initial 500ms lock is almost expired
await new Promise((r) => setTimeout(r, 100));
// Re-acquire (extend) with 5000ms
const extended = await lockManager.acquireLock('file1', 'agent-a', 'write', 5000);
expect(extended).toBe(true);
const extendedLock = lockManager.getLockStatus('file1');
// The new expiry should be at least 4 seconds from now
const minExpectedExpiry = Date.now() + 4000;
expect(new Date(extendedLock!.expiresAt).getTime()).toBeGreaterThan(minExpectedExpiry);
});
});
describe('Multiple Resources', () => {
it('should handle locks on different resources independently', async () => {
await lockManager.acquireLock('file1', 'agent-a', 'write');
await lockManager.acquireLock('file2', 'agent-b', 'write');
expect(lockManager.getLockStatus('file1')?.agentId).toBe('agent-a');
expect(lockManager.getLockStatus('file2')?.agentId).toBe('agent-b');
});
it('should list all active locks', async () => {
await lockManager.acquireLock('file1', 'agent-a', 'write');
await lockManager.acquireLock('file2', 'agent-b', 'read');
const allLocks = lockManager.getAllLocks();
expect(allLocks).toHaveLength(2);
});
});
});