@andend-collective/locker
Version:
Advanced file locking and context management for AI coding assistants using Model Context Protocol
397 lines • 20.6 kB
JavaScript
;
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