@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
349 lines (294 loc) • 10.7 kB
text/typescript
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);
});
});
});