UNPKG

@andend-collective/locker

Version:

Advanced file locking and context management for AI coding assistants using Model Context Protocol

397 lines 20.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const os = __importStar(require("os")); const file_manager_1 = require("../file-manager"); describe('FileManager', () => { let tempDir; let testFile; beforeEach(() => { tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'locker-test-')); testFile = path.join(tempDir, 'test.ts'); fs.writeFileSync(testFile, 'console.log("hello world");'); }); afterEach(() => { if (fs.existsSync(tempDir)) { fs.rmSync(tempDir, { recursive: true, force: true }); } }); describe('addLockComment', () => { it('should add lock comment to file', () => { const id = 'test-id-123'; const state = 'locked-context'; file_manager_1.FileManager.addLockComment(testFile, id, state, tempDir); const content = fs.readFileSync(testFile, 'utf-8'); expect(content.startsWith(`// LOCKED: ${id} STATE=${state}`)).toBe(true); expect(content).toContain('console.log("hello world");'); }); it('should throw error for non-existent file', () => { const nonExistentFile = path.join(tempDir, 'non-existent.ts'); expect(() => { file_manager_1.FileManager.addLockComment(nonExistentFile, 'id', 'locked', tempDir); }).toThrow('File does not exist:'); }); it('should throw error if file already has lock comment', () => { const id = 'test-id-123'; const state = 'locked-context'; file_manager_1.FileManager.addLockComment(testFile, id, state, tempDir); expect(() => { file_manager_1.FileManager.addLockComment(testFile, 'another-id', 'locked', tempDir); }).toThrow('File already has a lock comment:'); }); it('should validate file paths for security', () => { expect(() => { file_manager_1.FileManager.addLockComment('../../../etc/passwd', 'id', 'locked', tempDir); }).toThrow('Invalid file path:'); }); }); describe('removeLockComment', () => { it('should remove lock comment from file', () => { const id = 'test-id-123'; const state = 'locked-context'; const originalContent = 'console.log("hello world");'; file_manager_1.FileManager.addLockComment(testFile, id, state, tempDir); file_manager_1.FileManager.removeLockComment(testFile, tempDir); const content = fs.readFileSync(testFile, 'utf-8'); expect(content).toBe(originalContent); }); it('should do nothing if no lock comment exists', () => { const originalContent = fs.readFileSync(testFile, 'utf-8'); file_manager_1.FileManager.removeLockComment(testFile, tempDir); const content = fs.readFileSync(testFile, 'utf-8'); expect(content).toBe(originalContent); }); it('should throw error for non-existent file', () => { const nonExistentFile = path.join(tempDir, 'non-existent.ts'); expect(() => { file_manager_1.FileManager.removeLockComment(nonExistentFile, tempDir); }).toThrow('File does not exist:'); }); }); describe('updateLockComment', () => { it('should update existing lock comment', () => { const id = 'test-id-123'; const initialState = 'locked-context'; const newState = 'locked'; file_manager_1.FileManager.addLockComment(testFile, id, initialState, tempDir); file_manager_1.FileManager.updateLockComment(testFile, id, newState, tempDir); const content = fs.readFileSync(testFile, 'utf-8'); expect(content).toContain(`// LOCKED: ${id} STATE=${newState}`); expect(content).not.toContain(`STATE=${initialState}`); }); it('should throw error if no lock comment exists', () => { expect(() => { file_manager_1.FileManager.updateLockComment(testFile, 'id', 'locked', tempDir); }).toThrow('File does not have a lock comment:'); }); }); describe('hasLockComment', () => { it('should return true for content with lock comment', () => { const content = '// LOCKED: test-id-123 STATE=locked-context\nconsole.log("test");'; expect(file_manager_1.FileManager.hasLockComment(content)).toBe(true); }); it('should return false for content without lock comment', () => { const content = 'console.log("test");'; expect(file_manager_1.FileManager.hasLockComment(content)).toBe(false); }); it('should return false for malformed lock comment', () => { const content = '// LOCKED: invalid\nconsole.log("test");'; expect(file_manager_1.FileManager.hasLockComment(content)).toBe(false); }); }); describe('extractLockInfo', () => { it('should extract lock info from valid comment', () => { const id = 'test-id-123'; const state = 'locked-context'; const content = `// LOCKED: ${id} STATE=${state}\nconsole.log("test");`; const result = file_manager_1.FileManager.extractLockInfo(content); expect(result).not.toBeNull(); expect(result.id).toBe(id); expect(result.state).toBe(state); }); it('should return null for content without lock comment', () => { const content = 'console.log("test");'; const result = file_manager_1.FileManager.extractLockInfo(content); expect(result).toBeNull(); }); it('should return null for malformed lock comment', () => { const content = '// LOCKED: invalid format\nconsole.log("test");'; const result = file_manager_1.FileManager.extractLockInfo(content); expect(result).toBeNull(); }); it('should return null for lock comment with missing id', () => { const content = '// LOCKED: STATE=locked-context\nconsole.log("test");'; const result = file_manager_1.FileManager.extractLockInfo(content); expect(result).toBeNull(); }); it('should return null for lock comment with missing state', () => { const content = '// LOCKED: test-id-123\nconsole.log("test");'; const result = file_manager_1.FileManager.extractLockInfo(content); expect(result).toBeNull(); }); it('should return null when regex matches but capture groups fail', () => { // Test edge case where line matches format but captures fail const content = '// LOCKED: STATE=\nconsole.log("test");'; const result = file_manager_1.FileManager.extractLockInfo(content); expect(result).toBeNull(); }); it('should return null when id capture fails', () => { // Test where STATE matches but ID doesn't const content = '// LOCKED: STATE=locked\nconsole.log("test");'; const result = file_manager_1.FileManager.extractLockInfo(content); expect(result).toBeNull(); }); it('should return null when state capture fails', () => { // Test where ID matches but STATE doesn't const content = '// LOCKED: valid-id STATE=\nconsole.log("test");'; const result = file_manager_1.FileManager.extractLockInfo(content); expect(result).toBeNull(); }); it('should handle multiple potential lock lines', () => { // Test content with multiple lines that might match const content = `// LOCKED: valid-id STATE=locked // LOCKED: malformed console.log("test");`; const result = file_manager_1.FileManager.extractLockInfo(content); expect(result).not.toBeNull(); expect(result.id).toBe('valid-id'); expect(result.state).toBe('locked'); }); }); describe('path validation', () => { it('should reject paths with directory traversal', () => { expect(() => { file_manager_1.FileManager.addLockComment('../test.ts', 'id', 'locked', tempDir); }).toThrow('Invalid file path:'); expect(() => { file_manager_1.FileManager.addLockComment('../../etc/passwd', 'id', 'locked', tempDir); }).toThrow('Invalid file path:'); }); it('should accept absolute paths', () => { // Absolute paths should work fine now const id = 'test-id-123'; const state = 'locked-context'; expect(() => { file_manager_1.FileManager.addLockComment(testFile, id, state, tempDir); }).not.toThrow(); }); it('should resolve relative paths correctly', () => { // Create a file with a relative path within the temp directory const relativeFileName = 'relative-test.ts'; const relativeFile = path.join(tempDir, relativeFileName); fs.writeFileSync(relativeFile, 'console.log("relative test");'); const id = 'test-id-123'; const state = 'locked-context'; // This should work - relative path resolved against tempDir expect(() => { file_manager_1.FileManager.addLockComment(relativeFileName, id, state, tempDir); }).not.toThrow(); // Verify the file was actually modified const content = fs.readFileSync(relativeFile, 'utf-8'); expect(content.startsWith(`// LOCKED: ${id} STATE=${state}`)).toBe(true); }); }); describe('relative path resolution', () => { it('should resolve relative paths against project root', () => { // Create a subdirectory with a file const subDir = path.join(tempDir, 'subdir'); fs.mkdirSync(subDir); const subFile = path.join(subDir, 'sub-test.ts'); fs.writeFileSync(subFile, 'console.log("sub file");'); const id = 'test-id-123'; const state = 'locked-context'; // Use relative path from project root const relativePath = 'subdir/sub-test.ts'; file_manager_1.FileManager.addLockComment(relativePath, id, state, tempDir); const content = fs.readFileSync(subFile, 'utf-8'); expect(content.startsWith(`// LOCKED: ${id} STATE=${state}`)).toBe(true); }); it('should handle nested relative paths', () => { // Create nested directories const nestedDir = path.join(tempDir, 'dir1', 'dir2'); fs.mkdirSync(nestedDir, { recursive: true }); const nestedFile = path.join(nestedDir, 'nested.ts'); fs.writeFileSync(nestedFile, 'console.log("nested file");'); const id = 'nested-id-456'; const state = 'locked'; // Use nested relative path const relativePath = 'dir1/dir2/nested.ts'; file_manager_1.FileManager.addLockComment(relativePath, id, state, tempDir); const content = fs.readFileSync(nestedFile, 'utf-8'); expect(content.startsWith(`// LOCKED: ${id} STATE=${state}`)).toBe(true); }); it('should work with different project roots', () => { // Create a different temp directory as project root const otherTempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'locker-other-')); const otherFile = path.join(otherTempDir, 'other.ts'); fs.writeFileSync(otherFile, 'console.log("other file");'); try { const id = 'other-id-789'; const state = 'locked-context'; // Use relative path with different project root file_manager_1.FileManager.addLockComment('other.ts', id, state, otherTempDir); const content = fs.readFileSync(otherFile, 'utf-8'); expect(content.startsWith(`// LOCKED: ${id} STATE=${state}`)).toBe(true); } finally { // Cleanup if (fs.existsSync(otherTempDir)) { fs.rmSync(otherTempDir, { recursive: true, force: true }); } } }); it('should fail for non-existent relative paths', () => { expect(() => { file_manager_1.FileManager.addLockComment('non-existent/file.ts', 'id', 'locked', tempDir); }).toThrow('File does not exist:'); }); it('should handle absolute paths correctly', () => { const id = 'abs-id-123'; const state = 'locked-context'; // Test with absolute path (should not use projectRoot) // This tests the path.isAbsolute() === true branch file_manager_1.FileManager.addLockComment(testFile, id, state, '/some/other/root'); const content = fs.readFileSync(testFile, 'utf-8'); expect(content.startsWith(`// LOCKED: ${id} STATE=${state}`)).toBe(true); }); it('should resolve relative paths using projectRoot', () => { // Create a file specifically for relative path testing const relativeFileName = 'test-relative-resolve.ts'; const fullPath = path.join(tempDir, relativeFileName); fs.writeFileSync(fullPath, 'console.log("relative resolve test");'); const id = 'relative-resolve-id'; const state = 'locked'; // Test with relative path (should use projectRoot) // This tests the path.isAbsolute() === false branch file_manager_1.FileManager.addLockComment(relativeFileName, id, state, tempDir); const content = fs.readFileSync(fullPath, 'utf-8'); expect(content.startsWith(`// LOCKED: ${id} STATE=${state}`)).toBe(true); }); it('should handle temp directory paths in validation', () => { // Create a file in /tmp to test temp directory validation const tmpFile = path.join('/tmp', 'test-locker-file.ts'); fs.writeFileSync(tmpFile, 'console.log("temp test");'); try { const id = 'tmp-id-123'; const state = 'locked'; expect(() => { file_manager_1.FileManager.addLockComment(tmpFile, id, state, tempDir); }).not.toThrow(); const content = fs.readFileSync(tmpFile, 'utf-8'); expect(content.startsWith(`// LOCKED: ${id} STATE=${state}`)).toBe(true); } finally { // Cleanup if (fs.existsSync(tmpFile)) { fs.unlinkSync(tmpFile); } } }); it('should handle /tmp/ paths on Unix systems', () => { // Create a mock file that contains '/tmp/' in its path const mockTmpPath = path.join(tempDir, 'tmp', 'test-file.ts'); fs.mkdirSync(path.dirname(mockTmpPath), { recursive: true }); fs.writeFileSync(mockTmpPath, 'console.log("tmp test");'); const id = 'tmp-id-123'; const state = 'locked'; // This should pass validation due to /tmp/ path allowance expect(() => { file_manager_1.FileManager.addLockComment(mockTmpPath, id, state, tempDir); }).not.toThrow(); }); it('should handle Windows \\temp\\ paths', () => { // Create a mock file that would match Windows temp pattern const mockWinTempPath = path.join(tempDir, 'temp', 'test-file.ts'); fs.mkdirSync(path.dirname(mockWinTempPath), { recursive: true }); fs.writeFileSync(mockWinTempPath, 'console.log("win temp test");'); const id = 'win-temp-id-123'; const state = 'locked'; // This should pass validation due to temp path allowance expect(() => { file_manager_1.FileManager.addLockComment(mockWinTempPath, id, state, tempDir); }).not.toThrow(); }); it('should handle /var/folders/ paths (macOS)', () => { // Create a mock file that contains '/var/folders/' in its path const mockVarFoldersPath = path.join(tempDir, 'var', 'folders', 'abc', 'test-file.ts'); fs.mkdirSync(path.dirname(mockVarFoldersPath), { recursive: true }); fs.writeFileSync(mockVarFoldersPath, 'console.log("var folders test");'); const id = 'var-folders-id-123'; const state = 'locked'; // This should pass validation due to /var/folders/ path allowance expect(() => { file_manager_1.FileManager.addLockComment(mockVarFoldersPath, id, state, tempDir); }).not.toThrow(); }); it('should validate all temp directory patterns', () => { // Test that all three temp patterns are properly recognized const tempPatterns = [ path.join(tempDir, 'tmp', 'file1.ts'), // /tmp/ pattern path.join(tempDir, 'temp', 'file2.ts'), // \temp\ pattern (normalized) path.join(tempDir, 'var', 'folders', 'file3.ts') // /var/folders/ pattern ]; tempPatterns.forEach((filePath, index) => { fs.mkdirSync(path.dirname(filePath), { recursive: true }); fs.writeFileSync(filePath, `console.log("test ${index}");`); const id = `pattern-id-${index}`; const state = 'locked-context'; expect(() => { file_manager_1.FileManager.addLockComment(filePath, id, state, tempDir); }).not.toThrow(); // Verify the file was actually modified const content = fs.readFileSync(filePath, 'utf-8'); expect(content.startsWith(`// LOCKED: ${id} STATE=${state}`)).toBe(true); }); }); it('should handle regular non-temp paths that pass validation', () => { // Test a path that doesn't match any of the temp patterns // This tests the case where all temp pattern checks return false const regularPath = path.join(tempDir, 'src', 'components', 'Button.tsx'); fs.mkdirSync(path.dirname(regularPath), { recursive: true }); fs.writeFileSync(regularPath, 'export const Button = () => <button />;'); const id = 'regular-file-id'; const state = 'locked-context'; // This should work fine as the validation passes for regular paths expect(() => { file_manager_1.FileManager.addLockComment(regularPath, id, state, tempDir); }).not.toThrow(); const content = fs.readFileSync(regularPath, 'utf-8'); expect(content.startsWith(`// LOCKED: ${id} STATE=${state}`)).toBe(true); }); }); }); //# sourceMappingURL=file-manager.test.js.map