UNPKG

mcp-codesentry

Version:

CodeSentry MCP - AI-powered code review assistant with 5 specialized review tools for security, best practices, and comprehensive code analysis

215 lines 9.6 kB
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { StorageManager } from '../../src/storage/manager.js'; import * as fs from 'fs-extra'; import * as path from 'path'; // Mock fs-extra vi.mock('fs-extra', () => ({ ensureDir: vi.fn().mockResolvedValue(undefined), writeFile: vi.fn().mockResolvedValue(undefined), readFile: vi.fn().mockResolvedValue(''), pathExists: vi.fn().mockResolvedValue(false), readdir: vi.fn().mockResolvedValue([]), stat: vi.fn().mockResolvedValue({ size: 0, mtimeMs: 0 }), remove: vi.fn().mockResolvedValue(undefined) })); describe('StorageManager', () => { let storage; const testBasePath = '/tmp/test-storage'; const testConfig = { basePath: testBasePath, encrypt: false, maxSizeMB: 100 }; beforeEach(() => { vi.clearAllMocks(); storage = new StorageManager(testConfig); }); afterEach(() => { vi.clearAllMocks(); }); describe('initialization', () => { it('should create required directories on initialize', async () => { await storage.initialize(); expect(fs.ensureDir).toHaveBeenCalledWith(testBasePath); expect(fs.ensureDir).toHaveBeenCalledWith(path.join(testBasePath, 'snapshots')); expect(fs.ensureDir).toHaveBeenCalledWith(path.join(testBasePath, 'tasks')); }); }); describe('snapshot management', () => { it('should store pre-implementation snapshot', async () => { const taskId = 'test-123'; const content = 'test content'; vi.mocked(fs.writeFile).mockResolvedValueOnce(undefined); const snapshotId = await storage.storeSnapshot(taskId, content, 'pre'); expect(snapshotId).toContain(taskId); expect(snapshotId).toContain('pre'); expect(fs.writeFile).toHaveBeenCalledWith(expect.stringContaining(snapshotId), content); }); it('should store post-implementation snapshot', async () => { const taskId = 'test-123'; const content = 'test content'; vi.mocked(fs.writeFile).mockResolvedValueOnce(undefined); const snapshotId = await storage.storeSnapshot(taskId, content, 'post'); expect(snapshotId).toContain(taskId); expect(snapshotId).toContain('post'); expect(fs.writeFile).toHaveBeenCalledWith(expect.stringContaining(snapshotId), content); }); it('should enforce size limits', async () => { const content = 'x'.repeat(101 * 1024 * 1024); // 101MB await expect(storage.storeSnapshot('test', content, 'pre')) .rejects.toThrow('exceeds maximum size'); }); it('should retrieve snapshot', async () => { const snapshotId = 'test-123-pre-1234567890'; const content = 'test content'; vi.mocked(fs.pathExists).mockResolvedValueOnce(true); vi.mocked(fs.readFile).mockResolvedValueOnce(Buffer.from(content)); const retrieved = await storage.retrieveSnapshot(snapshotId); expect(retrieved).toBe(content); }); it('should throw error if snapshot not found', async () => { vi.mocked(fs.pathExists).mockResolvedValueOnce(false); await expect(storage.retrieveSnapshot('nonexistent')) .rejects.toThrow('Snapshot not found'); }); }); describe('task context management', () => { const testContext = { taskId: 'test-123', taskDescription: 'Test task', plan: 'Test plan', implementation: 'Test implementation', preSnapshot: 'snapshot-1', postSnapshot: 'snapshot-2', reviews: [], createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; it('should store task context', async () => { vi.mocked(fs.writeFile).mockResolvedValueOnce(undefined); await storage.storeTaskContext(testContext.taskId, testContext); expect(fs.writeFile).toHaveBeenCalledWith(expect.stringContaining(testContext.taskId), expect.any(String)); }); it('should retrieve task context', async () => { vi.mocked(fs.pathExists).mockResolvedValueOnce(true); vi.mocked(fs.readFile).mockResolvedValueOnce(Buffer.from(JSON.stringify(testContext))); const retrieved = await storage.retrieveTaskContext(testContext.taskId); expect(retrieved).toEqual(testContext); }); it('should return null if task context not found', async () => { vi.mocked(fs.pathExists).mockResolvedValueOnce(false); const retrieved = await storage.retrieveTaskContext('nonexistent'); expect(retrieved).toBeNull(); }); }); describe('encryption', () => { let encryptedStorage; beforeEach(() => { encryptedStorage = new StorageManager({ ...testConfig, encrypt: true }); }); it('should encrypt content when storing snapshot', async () => { vi.mocked(fs.writeFile).mockResolvedValueOnce(undefined); const content = 'sensitive data'; const snapshotId = await encryptedStorage.storeSnapshot('test', content, 'pre'); const writtenData = vi.mocked(fs.writeFile).mock.calls[0][1]; expect(writtenData).not.toBe(content); expect(writtenData).toContain(':'); // IV separator }); it('should decrypt content when retrieving snapshot', async () => { const content = 'sensitive data'; vi.mocked(fs.pathExists).mockResolvedValueOnce(true); // Store encrypted content vi.mocked(fs.writeFile).mockResolvedValueOnce(undefined); const snapshotId = await encryptedStorage.storeSnapshot('test', content, 'pre'); const encryptedData = vi.mocked(fs.writeFile).mock.calls[0][1]; // Retrieve and decrypt vi.mocked(fs.readFile).mockResolvedValueOnce(Buffer.from(encryptedData)); const retrieved = await encryptedStorage.retrieveSnapshot(snapshotId); expect(retrieved).toBe(content); }); }); describe('cleanup', () => { it('should remove old snapshots', async () => { const now = Date.now(); const oldDate = now - (8 * 24 * 60 * 60 * 1000); // 8 days old vi.mocked(fs.readdir).mockResolvedValueOnce(['old-snapshot.txt', 'new-snapshot.txt']); vi.mocked(fs.stat).mockImplementation((path) => { return Promise.resolve({ mtimeMs: path.toString().includes('old') ? oldDate : now, size: 0, isFile: () => true, isDirectory: () => false, isSymbolicLink: () => false, dev: 0, ino: 0, mode: 0, nlink: 1, uid: 0, gid: 0, rdev: 0, blksize: 4096, blocks: 0, atimeMs: now, ctimeMs: now, birthtimeMs: now, atime: new Date(now), mtime: new Date(now), ctime: new Date(now), birthtime: new Date(now) }); }); await storage.cleanup(7); // 7 days threshold expect(fs.remove).toHaveBeenCalledWith(expect.stringContaining('old-snapshot.txt')); expect(fs.remove).not.toHaveBeenCalledWith(expect.stringContaining('new-snapshot.txt')); }); }); describe('storage stats', () => { it('should return storage statistics', async () => { vi.mocked(fs.readdir).mockImplementation((path) => { if (path.toString().includes('snapshots')) { return Promise.resolve(['snap1.txt', 'snap2.txt']); } return Promise.resolve(['task1.json']); }); vi.mocked(fs.stat).mockResolvedValue({ size: 1024 * 1024, // 1MB mtimeMs: Date.now(), isFile: () => true, isDirectory: () => false, isSymbolicLink: () => false, dev: 0, ino: 0, mode: 0, nlink: 1, uid: 0, gid: 0, rdev: 0, blksize: 4096, blocks: 0, atimeMs: Date.now(), ctimeMs: Date.now(), birthtimeMs: Date.now(), atime: new Date(), mtime: new Date(), ctime: new Date(), birthtime: new Date() }); const stats = await storage.getStorageStats(); expect(stats.totalSnapshots).toBe(2); expect(stats.totalTasks).toBe(1); expect(stats.totalSizeMB).toBeGreaterThan(0); }); }); describe('path sanitization', () => { it('should sanitize unsafe paths', () => { const unsafePath = '../../../etc/passwd'; const sanitized = storage.sanitizePath(unsafePath); expect(sanitized).not.toContain('..'); expect(sanitized).not.toContain('/'); }); }); }); //# sourceMappingURL=manager.test.js.map