Skip to main content
Glama
MemoryManager.test.ts14.4 kB
// Critical path tests for MemoryManager (v2.0) // Includes Knowledge Graph tests import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { MemoryManager } from '../../src/lib/MemoryManager.js'; import fs from 'fs'; import path from 'path'; import os from 'os'; describe('MemoryManager - Critical Path', () => { let testDbPath: string; let manager: MemoryManager; beforeEach(() => { // Use temp directory for test database testDbPath = path.join(os.tmpdir(), `test-hi-ai-${Date.now()}-${Math.random().toString(36).slice(2)}.db`); // Force new instance with test path (MemoryManager as any).instance = null; manager = MemoryManager.getInstance(testDbPath); }); afterEach(() => { // Close database connection first try { manager.close(); } catch (e) { // Ignore close errors } (MemoryManager as any).instance = null; // Wait a bit for Windows file handles to release setTimeout(() => { try { if (fs.existsSync(testDbPath)) { fs.unlinkSync(testDbPath); } // Also clean up WAL files if (fs.existsSync(testDbPath + '-wal')) { fs.unlinkSync(testDbPath + '-wal'); } if (fs.existsSync(testDbPath + '-shm')) { fs.unlinkSync(testDbPath + '-shm'); } } catch (e) { // Ignore cleanup errors on Windows } }, 100); }); describe('CRUD Operations', () => { it('should save and recall memory', () => { manager.save('test-key', 'test-value', 'general', 0); const memory = manager.recall('test-key'); expect(memory).toBeDefined(); expect(memory?.key).toBe('test-key'); expect(memory?.value).toBe('test-value'); expect(memory?.category).toBe('general'); }); it('should update existing memory', () => { manager.save('key1', 'value1', 'general', 0); manager.save('key1', 'value2', 'general', 0); const memory = manager.recall('key1'); expect(memory?.value).toBe('value2'); }); it('should delete memory', () => { manager.save('key1', 'value1', 'general', 0); manager.delete('key1'); const memory = manager.recall('key1'); expect(memory).toBeNull(); }); it('should list all memories', () => { manager.save('key1', 'value1', 'project', 0); manager.save('key2', 'value2', 'personal', 0); manager.save('key3', 'value3', 'project', 0); const all = manager.list(); expect(all.length).toBe(3); }); it('should filter by category', () => { manager.save('key1', 'value1', 'project', 0); manager.save('key2', 'value2', 'personal', 0); manager.save('key3', 'value3', 'project', 0); const projectMemories = manager.list('project'); expect(projectMemories.length).toBe(2); expect(projectMemories.every(m => m.category === 'project')).toBe(true); }); }); describe('Search Functionality', () => { it('should search by keyword in value', () => { manager.save('key1', 'hello world', 'general', 0); manager.save('key2', 'foo bar', 'general', 0); manager.save('key3', 'world peace', 'general', 0); const results = manager.search('world'); expect(results.length).toBe(2); expect(results.every(m => m.value.includes('world'))).toBe(true); }); it('should return empty array for no matches', () => { manager.save('key1', 'hello', 'general', 0); const results = manager.search('nonexistent'); expect(results.length).toBe(0); }); }); describe('Priority Handling', () => { it('should retrieve high priority memories', () => { manager.save('key1', 'value1', 'general', 0); manager.save('key2', 'value2', 'general', 2); manager.save('key3', 'value3', 'general', 1); const highPriority = manager.getByPriority(2); expect(highPriority.length).toBe(1); expect(highPriority[0].priority).toBe(2); }); it('should update priority', () => { manager.save('key1', 'value1', 'general', 0); manager.setPriority('key1', 2); const memory = manager.recall('key1'); expect(memory?.priority).toBe(2); }); }); describe('Error Handling', () => { it('should return null for non-existent key', () => { const memory = manager.recall('nonexistent'); expect(memory).toBeNull(); }); it('should handle special characters in values', () => { const specialValue = 'Test "quotes" and \'apostrophes\' and \\ backslash'; manager.save('special', specialValue, 'general', 0); const memory = manager.recall('special'); expect(memory?.value).toBe(specialValue); }); it('should handle large values', () => { const largeValue = 'x'.repeat(10000); manager.save('large', largeValue, 'general', 0); const memory = manager.recall('large'); expect(memory?.value).toBe(largeValue); }); }); describe('Concurrent Operations', () => { it('should handle multiple rapid writes', () => { for (let i = 0; i < 100; i++) { manager.save(`key${i}`, `value${i}`, 'general', 0); } const all = manager.list(); expect(all.length).toBe(100); }); it('should handle interleaved read/write', () => { manager.save('key1', 'value1', 'general', 0); const read1 = manager.recall('key1'); manager.save('key2', 'value2', 'general', 0); const read2 = manager.recall('key2'); expect(read1?.value).toBe('value1'); expect(read2?.value).toBe('value2'); }); }); describe('Database Initialization', () => { it('should create database if not exists', () => { expect(fs.existsSync(testDbPath)).toBe(true); }); it('should initialize with empty database', () => { const all = manager.list(); expect(all.length).toBe(0); }); }); // v2.0 Knowledge Graph Tests describe('Knowledge Graph - Link Memories', () => { it('should link two memories', () => { manager.save('source', 'source value', 'general', 0); manager.save('target', 'target value', 'general', 0); const result = manager.linkMemories('source', 'target', 'related_to', 0.8); expect(result).toBe(true); }); it('should get relations for a memory', () => { manager.save('a', 'value a', 'general', 0); manager.save('b', 'value b', 'general', 0); manager.save('c', 'value c', 'general', 0); manager.linkMemories('a', 'b', 'related_to', 1.0); manager.linkMemories('a', 'c', 'depends_on', 0.5); const relations = manager.getRelations('a', 'outgoing'); expect(relations.length).toBe(2); expect(relations.some(r => r.targetKey === 'b')).toBe(true); expect(relations.some(r => r.targetKey === 'c')).toBe(true); }); it('should get incoming relations', () => { manager.save('a', 'value a', 'general', 0); manager.save('b', 'value b', 'general', 0); manager.linkMemories('a', 'b', 'related_to', 1.0); const relations = manager.getRelations('b', 'incoming'); expect(relations.length).toBe(1); expect(relations[0].sourceKey).toBe('a'); }); it('should unlink memories', () => { manager.save('a', 'value a', 'general', 0); manager.save('b', 'value b', 'general', 0); manager.linkMemories('a', 'b', 'related_to', 1.0); const unlinkResult = manager.unlinkMemories('a', 'b', 'related_to'); expect(unlinkResult).toBe(true); const relations = manager.getRelations('a', 'outgoing'); expect(relations.length).toBe(0); }); it('should delete relations when memory is deleted', () => { manager.save('a', 'value a', 'general', 0); manager.save('b', 'value b', 'general', 0); manager.linkMemories('a', 'b', 'related_to', 1.0); manager.delete('a'); const relations = manager.getRelations('b', 'both'); expect(relations.length).toBe(0); }); }); describe('Knowledge Graph - Graph Traversal', () => { it('should get related memories with depth 1', () => { manager.save('a', 'value a', 'general', 0); manager.save('b', 'value b', 'general', 0); manager.save('c', 'value c', 'general', 0); manager.linkMemories('a', 'b', 'related_to', 1.0); manager.linkMemories('b', 'c', 'related_to', 1.0); const related = manager.getRelatedMemories('a', 1); expect(related.length).toBe(1); expect(related[0].key).toBe('b'); }); it('should get related memories with depth 2', () => { manager.save('a', 'value a', 'general', 0); manager.save('b', 'value b', 'general', 0); manager.save('c', 'value c', 'general', 0); manager.linkMemories('a', 'b', 'related_to', 1.0); manager.linkMemories('b', 'c', 'related_to', 1.0); const related = manager.getRelatedMemories('a', 2); expect(related.length).toBe(2); expect(related.some(m => m.key === 'b')).toBe(true); expect(related.some(m => m.key === 'c')).toBe(true); }); it('should filter by relation type', () => { manager.save('a', 'value a', 'general', 0); manager.save('b', 'value b', 'general', 0); manager.save('c', 'value c', 'general', 0); manager.linkMemories('a', 'b', 'related_to', 1.0); manager.linkMemories('a', 'c', 'depends_on', 1.0); const related = manager.getRelatedMemories('a', 1, 'related_to'); expect(related.length).toBe(1); expect(related[0].key).toBe('b'); }); }); describe('Knowledge Graph - Memory Graph', () => { it('should get memory graph structure', () => { manager.save('a', 'value a', 'general', 0); manager.save('b', 'value b', 'general', 0); manager.save('c', 'value c', 'general', 0); manager.linkMemories('a', 'b', 'related_to', 1.0); manager.linkMemories('b', 'c', 'related_to', 1.0); const graph = manager.getMemoryGraph('a', 2); expect(graph.nodes.length).toBe(3); expect(graph.edges.length).toBe(2); }); it('should detect clusters', () => { manager.save('a', 'value a', 'general', 0); manager.save('b', 'value b', 'general', 0); manager.save('c', 'value c', 'general', 0); manager.save('d', 'value d', 'general', 0); manager.linkMemories('a', 'b', 'related_to', 1.0); manager.linkMemories('c', 'd', 'related_to', 1.0); const graph = manager.getMemoryGraph(); // Two separate clusters expect(graph.clusters.length).toBe(2); }); it('should find path between memories', () => { manager.save('a', 'value a', 'general', 0); manager.save('b', 'value b', 'general', 0); manager.save('c', 'value c', 'general', 0); manager.linkMemories('a', 'b', 'related_to', 1.0); manager.linkMemories('b', 'c', 'related_to', 1.0); const path = manager.findPath('a', 'c'); expect(path).not.toBeNull(); expect(path?.length).toBe(3); expect(path?.[0]).toBe('a'); expect(path?.[1]).toBe('b'); expect(path?.[2]).toBe('c'); }); it('should return null when no path exists', () => { manager.save('a', 'value a', 'general', 0); manager.save('b', 'value b', 'general', 0); // No link between a and b const path = manager.findPath('a', 'b'); expect(path).toBeNull(); }); }); describe('Knowledge Graph - Timeline', () => { it('should get timeline', () => { manager.save('a', 'value a', 'general', 0); manager.save('b', 'value b', 'general', 0); manager.save('c', 'value c', 'general', 0); const timeline = manager.getTimeline(); expect(timeline.length).toBe(3); // Most recent first expect(timeline[0].key).toBe('c'); }); it('should limit timeline results', () => { manager.save('a', 'value a', 'general', 0); manager.save('b', 'value b', 'general', 0); manager.save('c', 'value c', 'general', 0); const timeline = manager.getTimeline(undefined, undefined, 2); expect(timeline.length).toBe(2); }); }); describe('Knowledge Graph - Advanced Search', () => { it('should search with keyword strategy', () => { manager.save('auth', 'authentication logic', 'general', 0); manager.save('user', 'user management', 'general', 0); manager.save('auth-token', 'auth token handling', 'general', 0); const results = manager.searchAdvanced('auth', 'keyword', { limit: 10 }); expect(results.length).toBe(2); }); it('should search with temporal strategy', () => { manager.save('old', 'old value', 'general', 0); manager.save('new', 'new value', 'general', 0); const results = manager.searchAdvanced('value', 'temporal', { limit: 10 }); // Most recent first expect(results[0].key).toBe('new'); }); it('should search with priority strategy', () => { manager.save('low', 'test value', 'general', 0); manager.save('high', 'test value', 'general', 5); const results = manager.searchAdvanced('test', 'priority', { limit: 10 }); // Highest priority first expect(results[0].key).toBe('high'); }); it('should search with context_aware strategy', () => { manager.save('auth', 'authentication', 'general', 5); manager.save('other', 'authentication related', 'general', 0); const results = manager.searchAdvanced('auth', 'context_aware', { limit: 10 }); // Key match + high priority should rank first expect(results[0].key).toBe('auth'); }); it('should filter by category in advanced search', () => { manager.save('a', 'test value', 'project', 0); manager.save('b', 'test value', 'personal', 0); const results = manager.searchAdvanced('test', 'keyword', { category: 'project' }); expect(results.length).toBe(1); expect(results[0].category).toBe('project'); }); }); describe('Statistics', () => { it('should return correct stats', () => { manager.save('a', 'value', 'project', 0); manager.save('b', 'value', 'project', 0); manager.save('c', 'value', 'personal', 0); const stats = manager.getStats(); expect(stats.total).toBe(3); expect(stats.byCategory['project']).toBe(2); expect(stats.byCategory['personal']).toBe(1); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/su-record/hi-ai'

If you have feedback or need assistance with the MCP directory API, please join our Discord server