UNPKG

@boundless-oss/atlas

Version:

Atlas - MCP Server for comprehensive startup project management

349 lines (294 loc) 10.7 kB
import { describe, it, expect, beforeEach, vi } from 'vitest'; import { MemoryManager } from '../memory-manager.js'; import { ConfigManager } from '../../../config/config-manager.js'; import { MemoryEntry, MemoryQuery, MemorySearchResult } from '../types.js'; // Mock ConfigManager vi.mock('../../../config/config-manager.js'); // Mock fs vi.mock('fs', () => ({ promises: { readFile: vi.fn().mockRejectedValue(new Error('File not found')), writeFile: vi.fn().mockResolvedValue(undefined), mkdir: vi.fn().mockResolvedValue(undefined), readdir: vi.fn().mockResolvedValue([]) } })); const MockConfigManager = ConfigManager as vi.MockedClass<typeof ConfigManager>; describe('MemoryManager', () => { let memoryManager: MemoryManager; let mockConfigManager: vi.Mocked<ConfigManager>; it('should create MemoryManager instance', () => { const config = {} as any; const manager = new MemoryManager(config); expect(manager).toBeInstanceOf(MemoryManager); }); beforeEach(async () => { vi.clearAllMocks(); mockConfigManager = new MockConfigManager() as vi.Mocked<ConfigManager>; mockConfigManager.getDataPath = vi.fn().mockReturnValue('/test/data/memory'); mockConfigManager.getStorageManager = vi.fn().mockReturnValue({ getModuleDataPath: vi.fn().mockResolvedValue('/test/data/memory/memories.json'), getStorageLocation: vi.fn().mockResolvedValue({ data: '/test/data', config: '/test/config', cache: '/test/cache' }), loadData: vi.fn().mockResolvedValue(null), saveData: vi.fn().mockResolvedValue(undefined) }); memoryManager = new MemoryManager(mockConfigManager); await memoryManager.init(); }); describe('store', () => { it('should store a code memory entry', async () => { const memoryData = { content: 'function calculateTotal(items) { return items.reduce((sum, item) => sum + item.price, 0); }', type: 'code' as const, metadata: { file: 'src/utils/calculations.js', function: 'calculateTotal', language: 'javascript', }, tags: ['calculation', 'utility', 'reduce'], importance: 'high' as const, }; const entryId = await memoryManager.store(memoryData); expect(entryId).toMatch(/^[a-f0-9-]+$/); }); it('should store a documentation memory entry', async () => { const memoryData = { content: 'API endpoint for user authentication accepts POST requests with email and password', type: 'documentation' as const, metadata: { section: 'Authentication', document: 'API.md', topics: ['authentication', 'api', 'security'], }, tags: ['api', 'auth', 'security'], importance: 'critical' as const, }; const entryId = await memoryManager.store(memoryData); expect(entryId).toBeDefined(); }); it('should store a decision memory entry', async () => { const memoryData = { content: 'Decided to use React over Vue.js for better team familiarity and ecosystem support', type: 'decision' as const, metadata: { reasoning: 'Team has more React experience, larger ecosystem, better TypeScript support', alternatives: ['Vue.js', 'Angular', 'Svelte'], impact: 'high', }, tags: ['architecture', 'frontend', 'framework'], importance: 'critical' as const, }; const entryId = await memoryManager.store(memoryData); expect(entryId).toBeDefined(); }); }); describe('search', () => { it('should search memories by query string', async () => { try { // Store a memory first const memoryId = await memoryManager.store({ content: 'React component for user authentication', type: 'code', tags: ['react', 'authentication', 'component'], importance: 'high', metadata: {} }); // Verify the memory was stored expect(memoryId).toBeDefined(); // Check if the memory is actually in the memories map const storedMemory = (memoryManager as any).memories.get(memoryId); expect(storedMemory).toBeDefined(); expect(storedMemory?.content).toBe('React component for user authentication'); const query: MemoryQuery = { query: 'authentication', limit: 10, minScore: 0 // Lower the threshold }; const results = await memoryManager.search(query); // Debug: log the search results and score calculation if (results.length === 0) { console.log('No results found. Checking memory storage...'); const stats = await memoryManager.getStats(); console.log('Total memories:', stats.totalEntries); console.log('Memory map size:', (memoryManager as any).memories.size); console.log('Stored memory:', storedMemory); } // Since we're storing in memory, the search should work expect(results.length).toBeGreaterThanOrEqual(1); expect(results[0].content).toContain('authentication'); } catch (error) { console.error('Test error:', error); throw error; } }); it('should return empty results when no matches', async () => { const query: MemoryQuery = { query: 'nonexistent', limit: 10 }; const results = await memoryManager.search(query); expect(results).toEqual([]); }); it('should respect result limit', async () => { // Store multiple memories for (let i = 0; i < 5; i++) { await memoryManager.store({ content: `Test memory ${i}`, type: 'code', tags: ['test'], importance: 'medium', metadata: {} }); } const query: MemoryQuery = { query: 'test', limit: 2 }; const results = await memoryManager.search(query); expect(results.length).toBeLessThanOrEqual(2); }); }); describe('getStats', () => { beforeEach(async () => { // Add memories for stats await memoryManager.store({ content: 'Test memory 1', type: 'code', tags: ['test', 'code'], importance: 'high', metadata: {} }); await memoryManager.store({ content: 'Test memory 2', type: 'documentation', tags: ['test', 'docs'], importance: 'medium', metadata: {} }); }); it('should return memory statistics', async () => { const stats = await memoryManager.getStats(); expect(stats.totalEntries).toBeGreaterThan(0); expect(stats.byType).toBeDefined(); expect(stats.byImportance).toBeDefined(); expect(stats.topTags).toBeDefined(); expect(stats.recentActivity).toBeDefined(); }); }); describe('clear', () => { beforeEach(async () => { await memoryManager.store({ content: 'Old memory', type: 'code', tags: ['old'], importance: 'low', metadata: {} }); }); it('should clear memories by type', async () => { const deletedCount = await memoryManager.clear({ type: 'code' }); expect(deletedCount).toBeGreaterThan(0); }); it('should clear memories older than specified date', async () => { const cutoffDate = new Date(Date.now() + 1000).toISOString(); // Future date const deletedCount = await memoryManager.clear({ olderThan: cutoffDate }); expect(deletedCount).toBeGreaterThan(0); }); }); describe('export', () => { beforeEach(async () => { await memoryManager.store({ content: 'Export test memory', type: 'code', tags: ['export', 'test'], importance: 'medium', metadata: {} }); }); it('should export memories as JSON', async () => { const result = await memoryManager.export({ format: 'json', includeMetadata: true }); expect(result).toBeDefined(); expect(result.entryCount).toBeGreaterThan(0); expect(result.size).toBeGreaterThan(0); }); it('should export memories as markdown', async () => { const result = await memoryManager.export({ format: 'markdown', includeMetadata: false }); expect(result).toBeDefined(); expect(result.entryCount).toBeGreaterThan(0); expect(result.size).toBeGreaterThan(0); }); }); describe('generateInsights', () => { beforeEach(async () => { // Add context-building memories await memoryManager.store({ content: 'Project uses React with TypeScript for frontend', type: 'context', tags: ['project', 'frontend', 'react', 'typescript'], importance: 'high', metadata: {} }); await memoryManager.store({ content: 'Backend built with Node.js and Express', type: 'context', tags: ['project', 'backend', 'nodejs', 'express'], importance: 'high', metadata: {} }); }); it('should generate insights from memories', async () => { const insights = await memoryManager.generateInsights(); expect(insights).toBeDefined(); expect(Array.isArray(insights)).toBe(true); expect(insights.length).toBeGreaterThan(0); if (insights.length > 0) { expect(insights[0]).toHaveProperty('type'); expect(insights[0]).toHaveProperty('title'); expect(insights[0]).toHaveProperty('description'); } }); }); describe('suggestRelated', () => { beforeEach(async () => { await memoryManager.store({ content: 'React hooks for state management', type: 'code', tags: ['react', 'hooks', 'state'], importance: 'high', metadata: {} }); await memoryManager.store({ content: 'useState hook for component state', type: 'code', tags: ['react', 'hooks', 'useState'], importance: 'high', metadata: {} }); await memoryManager.store({ content: 'Vue.js reactive properties', type: 'code', tags: ['vue', 'reactive', 'properties'], importance: 'medium', metadata: {} }); }); it('should suggest related memories', async () => { const related = await memoryManager.suggestRelated({ currentContext: 'React hooks for state management', limit: 5 }); expect(related).toBeDefined(); expect(Array.isArray(related)).toBe(true); }); }); });