UNPKG

locker-mcp

Version:

MCP server for file locking and access control for AI code tools

287 lines 12.7 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 locker_1 = require("../locker"); describe('Locker', () => { let tempDir; let locker; let testFile; beforeEach(() => { tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'locker-test-')); locker = new locker_1.Locker(tempDir); 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('getFileState', () => { it('should return unlocked state for untracked file', () => { const result = locker.getFileState({ filePath: testFile }); expect(result.state).toBe('unlocked'); expect(result.context).toBe(''); expect(result.id).toBe(''); expect(result.history).toEqual([]); }); it('should return tracked file state', () => { const context = 'test context'; locker.lockFile({ filePath: testFile, context }); const result = locker.getFileState({ filePath: testFile }); expect(result.state).toBe('locked-context'); expect(result.context).toBe(context); expect(result.id).toBeDefined(); expect(result.history).toHaveLength(1); }); }); describe('lockFile', () => { it('should lock file successfully', () => { const context = 'test context'; const result = locker.lockFile({ filePath: testFile, context }); expect(result.state).toBe('locked-context'); expect(result.context).toBe(context); expect(result.id).toBeDefined(); expect(result.history).toHaveLength(1); // Verify file comment was added const content = fs.readFileSync(testFile, 'utf-8'); expect(content).toMatch(/^\/\/ LOCKED: .+ STATE=locked-context/); }); it('should throw error for empty context', () => { expect(() => { locker.lockFile({ filePath: testFile, context: '' }); }).toThrow('Context cannot be empty'); expect(() => { locker.lockFile({ filePath: testFile, context: ' ' }); }).toThrow('Context cannot be empty'); }); it('should throw error for already tracked file', () => { const context = 'test context'; locker.lockFile({ filePath: testFile, context }); expect(() => { locker.lockFile({ filePath: testFile, context: 'another context' }); }).toThrow('File is already tracked:'); }); it('should cleanup on failure', () => { const nonExistentFile = path.join(tempDir, 'non-existent.ts'); expect(() => { locker.lockFile({ filePath: nonExistentFile, context: 'test' }); }).toThrow(); // Verify no metadata entry was created const state = locker.getFileState({ filePath: nonExistentFile }); expect(state.state).toBe('unlocked'); }); }); describe('unlockFile', () => { it('should unlock tracked file', () => { const context = 'test context'; locker.lockFile({ filePath: testFile, context }); const result = locker.unlockFile({ filePath: testFile }); expect(result.state).toBe('unlocked'); expect(result.context).toBe(context); // Context preserved expect(result.history).toHaveLength(2); // Original lock + unlock // Verify lock comment was removed const content = fs.readFileSync(testFile, 'utf-8'); expect(content).not.toMatch(/^\/\/ LOCKED:/); }); it('should throw error for untracked file', () => { expect(() => { locker.unlockFile({ filePath: testFile }); }).toThrow('File is not tracked:'); }); it('should handle file removal errors gracefully', () => { const context = 'test context'; locker.lockFile({ filePath: testFile, context }); // Delete the file to simulate removal error fs.unlinkSync(testFile); expect(() => { locker.unlockFile({ filePath: testFile }); }).toThrow('Failed to unlock file:'); }); }); describe('updateFile', () => { it('should update file context and state', () => { const initialContext = 'initial context'; locker.lockFile({ filePath: testFile, context: initialContext }); const newContext = 'updated context'; const newState = 'locked'; const result = locker.updateFile({ filePath: testFile, context: newContext, state: newState }); expect(result.state).toBe(newState); expect(result.context).toBe(newContext); expect(result.history).toHaveLength(2); // Verify file comment was updated const content = fs.readFileSync(testFile, 'utf-8'); expect(content).toMatch(/^\/\/ LOCKED: .+ STATE=locked/); }); it('should update context without changing state', () => { const initialContext = 'initial context'; locker.lockFile({ filePath: testFile, context: initialContext }); const newContext = 'updated context'; const result = locker.updateFile({ filePath: testFile, context: newContext, state: 'locked-context' // Same state }); expect(result.context).toBe(newContext); expect(result.history).toHaveLength(1); // No state change }); it('should throw error for empty context', () => { locker.lockFile({ filePath: testFile, context: 'initial' }); expect(() => { locker.updateFile({ filePath: testFile, context: '', state: 'locked' }); }).toThrow('Context cannot be empty'); }); it('should throw error for untracked file', () => { expect(() => { locker.updateFile({ filePath: testFile, context: 'test', state: 'locked' }); }).toThrow('File is not tracked:'); }); it('should handle file update errors gracefully', () => { const context = 'test context'; locker.lockFile({ filePath: testFile, context }); // Delete the file to simulate update error fs.unlinkSync(testFile); expect(() => { locker.updateFile({ filePath: testFile, context: 'updated context', state: 'locked' }); }).toThrow('Failed to update file:'); }); }); describe('finalizeFile', () => { it('should finalize locked-context file', () => { const context = 'test context'; locker.lockFile({ filePath: testFile, context }); const result = locker.finalizeFile(testFile); expect(result.state).toBe('locked'); expect(result.history).toHaveLength(2); // Verify file comment was updated const content = fs.readFileSync(testFile, 'utf-8'); expect(content).toMatch(/^\/\/ LOCKED: .+ STATE=locked/); }); it('should return same result for already locked file', () => { const context = 'test context'; locker.lockFile({ filePath: testFile, context }); locker.finalizeFile(testFile); const result = locker.finalizeFile(testFile); expect(result.state).toBe('locked'); expect(result.history).toHaveLength(2); // No additional history entry }); it('should throw error for untracked file', () => { expect(() => { locker.finalizeFile(testFile); }).toThrow('File is not tracked:'); }); it('should handle file finalization errors gracefully', () => { const context = 'test context'; locker.lockFile({ filePath: testFile, context }); // Delete the file to simulate finalization error fs.unlinkSync(testFile); expect(() => { locker.finalizeFile(testFile); }).toThrow('Failed to finalize file:'); }); }); describe('getAllTrackedFiles', () => { it('should return empty array when no files tracked', () => { const result = locker.getAllTrackedFiles(); expect(result).toEqual([]); }); it('should return all tracked files', () => { const files = ['test1.ts', 'test2.ts', 'test3.ts']; files.forEach((fileName) => { const filePath = path.join(tempDir, fileName); fs.writeFileSync(filePath, `console.log("${fileName}");`); locker.lockFile({ filePath, context: `context for ${fileName}` }); }); const result = locker.getAllTrackedFiles(); expect(result).toHaveLength(3); expect(result.map(r => path.basename(r.filePath))).toEqual(expect.arrayContaining(files)); }); }); describe('integration scenarios', () => { it('should handle complete workflow', () => { const context = 'authentication module'; // Lock file let result = locker.lockFile({ filePath: testFile, context }); expect(result.state).toBe('locked-context'); // Update context result = locker.updateFile({ filePath: testFile, context: 'improved authentication module', state: 'locked-context' }); expect(result.context).toBe('improved authentication module'); // Finalize result = locker.finalizeFile(testFile); expect(result.state).toBe('locked'); // Check final state result = locker.getFileState({ filePath: testFile }); expect(result.state).toBe('locked'); expect(result.context).toBe('improved authentication module'); expect(result.history).toHaveLength(2); }); it('should handle unlock and re-lock workflow', () => { const context = 'test module'; // Lock file locker.lockFile({ filePath: testFile, context }); // Unlock file let result = locker.unlockFile({ filePath: testFile }); expect(result.state).toBe('unlocked'); // Lock again with new context result = locker.lockFile({ filePath: testFile, context: 'updated module' }); expect(result.state).toBe('locked-context'); expect(result.context).toBe('updated module'); }); }); }); //# sourceMappingURL=locker.test.js.map