const FileLock = require('../file-lock'); const fs = require('fs'); const path = require('path'); describe('FileLock', () => { let lock; const testFilePath = path.join(__dirname, 'test-file.txt'); beforeEach(() => { lock = new FileLock(); // Clean up any leftover lock files const lockFilePath = `${testFilePath}.lock`; if (fs.existsSync(lockFilePath)) { fs.unlinkSync(lockFilePath); } // Create test file fs.writeFileSync(testFilePath, 'test content'); }); afterEach(() => { // Clean up files const lockFilePath = `${testFilePath}.lock`; if (fs.existsSync(lockFilePath)) { fs.unlinkSync(lockFilePath); } if (fs.existsSync(testFilePath)) { fs.unlinkSync(testFilePath); } }); describe('acquire and release', () => { test('should acquire lock without waiting when not locked', async () => { const startTime = Date.now(); await lock.acquire(testFilePath); const endTime = Date.now(); // Should resolve almost immediately (no waiting) expect(endTime - startTime).toBeLessThan(100); lock.release(testFilePath); }); test('should protect file access without waiting when not locked', async () => { let protectedFunctionCalled = false; const protectedFunction = async () => { await lock.acquire(testFilePath); protectedFunctionCalled = true; // Simulate some async work await new Promise(resolve => setTimeout(resolve, 10)); lock.release(testFilePath); return 'done'; }; const result = await protectedFunction(); expect(protectedFunctionCalled).toBe(true); expect(result).toBe('done'); }); test('should allow consecutive acquisitions and releases without queueing', async () => { // First acquisition await lock.acquire(testFilePath); lock.release(testFilePath); // Second acquisition await lock.acquire(testFilePath); lock.release(testFilePath); // Third acquisition await lock.acquire(testFilePath); lock.release(testFilePath); }); test('should handle multiple concurrent acquisitions correctly', async () => { let counter = 0; const incrementCounter = async () => { await lock.acquire(testFilePath); const temp = counter; // Simulate some async work that could cause race condition await new Promise(resolve => setTimeout(resolve, 1)); counter = temp + 1; lock.release(testFilePath); }; // Run multiple concurrent operations await Promise.all([ incrementCounter(), incrementCounter(), incrementCounter() ]); expect(counter).toBe(3); }); test('should handle case where release is called without any pending acquirers', () => { // Calling release on unlocked lock should not error expect(() => lock.release(testFilePath)).not.toThrow(); }); test('should properly queue multiple concurrent requests', async () => { let executionOrder = []; const task = async (id) => { await lock.acquire(testFilePath); executionOrder.push(id); // Simulate work await new Promise(resolve => setTimeout(resolve, 10)); lock.release(testFilePath); }; // Start multiple concurrent tasks const tasks = [ task(1), task(2), task(3) ]; await Promise.all(tasks); // All tasks should execute in order expect(executionOrder).toEqual([1, 2, 3]); }); test('should create lock file when acquiring lock', async () => { await lock.acquire(testFilePath); const lockFilePath = `${testFilePath}.lock`; expect(fs.existsSync(lockFilePath)).toBe(true); expect(fs.readFileSync(lockFilePath, 'utf8')).toBe(process.pid.toString()); lock.release(testFilePath); }); test('should remove lock file when releasing lock', async () => { await lock.acquire(testFilePath); const lockFilePath = `${testFilePath}.lock`; expect(fs.existsSync(lockFilePath)).toBe(true); lock.release(testFilePath); expect(fs.existsSync(lockFilePath)).toBe(false); }); }); });