UNPKG

@codai/memorai

Version:

Universal Database & Storage Service for CODAI Ecosystem - CBD Backend

582 lines (453 loc) 20 kB
import { describe, it, expect, vi, beforeEach, afterEach, beforeAll, afterAll } from 'vitest'; import { MemoraiService } from '../../src/services/MemoraiService'; import { DatabaseService } from '../../src/services/DatabaseService'; import { StorageService } from '../../src/services/StorageService'; import { MemoryService } from '../../src/services/MemoryService'; import { SyncService } from '../../src/services/SyncService'; import { CacheService } from '../../src/services/CacheService'; import { AnalyticsService } from '../../src/services/AnalyticsService'; import type { MemoraiConfig, Memory, MemoryQuery, MemorySearchResult, StorageFile, DatabaseQuery } from '../../src/types'; // Mock all dependencies vi.mock('../../src/services/DatabaseService'); vi.mock('../../src/services/StorageService'); vi.mock('../../src/services/MemoryService'); vi.mock('../../src/services/SyncService'); vi.mock('../../src/services/CacheService'); vi.mock('../../src/services/AnalyticsService'); describe('MemoraiService - Universal Database & Storage Service', () => { let memoraiService: MemoraiService; let mockDatabaseService: vi.Mocked<DatabaseService>; let mockStorageService: vi.Mocked<StorageService>; let mockMemoryService: vi.Mocked<MemoryService>; let mockSyncService: vi.Mocked<SyncService>; let mockCacheService: vi.Mocked<CacheService>; let mockAnalyticsService: vi.Mocked<AnalyticsService>; const testConfig: Partial<MemoraiConfig> = { database: { url: 'sqlite://test.db', type: 'sqlite' }, storage: { provider: 'local', basePath: './test-storage' }, memory: { enableVectorSearch: true, maxMemories: 10000 } }; beforeEach(async () => { // Reset all mocks vi.clearAllMocks(); // Create fresh service instance for each test memoraiService = await MemoraiService.create(testConfig); // Get mock instances mockDatabaseService = memoraiService.database as vi.Mocked<DatabaseService>; mockStorageService = memoraiService.storage as vi.Mocked<StorageService>; mockMemoryService = memoraiService.memory as vi.Mocked<MemoryService>; mockSyncService = memoraiService.sync as vi.Mocked<SyncService>; mockCacheService = memoraiService.cache as vi.Mocked<CacheService>; mockAnalyticsService = memoraiService.analytics as vi.Mocked<AnalyticsService>; }); afterEach(async () => { await memoraiService?.shutdown(); }); describe('Service Initialization', () => { it('should create singleton instance', async () => { const instance1 = await MemoraiService.create(testConfig); const instance2 = await MemoraiService.create(testConfig); expect(instance1).toBe(instance2); }); it('should initialize all core services', async () => { expect(memoraiService.database).toBeDefined(); expect(memoraiService.storage).toBeDefined(); expect(memoraiService.memory).toBeDefined(); expect(memoraiService.sync).toBeDefined(); expect(memoraiService.cache).toBeDefined(); expect(memoraiService.analytics).toBeDefined(); }); it('should apply configuration correctly', async () => { expect(memoraiService.getConfiguration()).toMatchObject(testConfig); }); it('should emit initialization events', async () => { const initSpy = vi.fn(); memoraiService.on('initialized', initSpy); await memoraiService.initialize(); expect(initSpy).toHaveBeenCalledWith({ status: 'ready', services: expect.any(Array) }); }); }); describe('Memory Management', () => { const testMemory: Memory = { id: 'test-memory-1', content: 'User prefers dark theme in IDE', userId: 'user-123', metadata: { context: 'ui-preferences', importance: 0.8, timestamp: new Date() } }; it('should store memories with vector embeddings', async () => { mockMemoryService.storeMemory.mockResolvedValue(testMemory); const result = await memoraiService.storeMemory(testMemory); expect(mockMemoryService.storeMemory).toHaveBeenCalledWith(testMemory); expect(result).toEqual(testMemory); }); it('should search memories with semantic similarity', async () => { const mockSearchResults: MemorySearchResult[] = [ { memory: testMemory, relevanceScore: 0.95, matchedContext: ['ui-preferences'] } ]; mockMemoryService.searchMemories.mockResolvedValue(mockSearchResults); const query: MemoryQuery = { query: 'theme preferences', userId: 'user-123', limit: 10 }; const results = await memoraiService.searchMemories(query); expect(mockMemoryService.searchMemories).toHaveBeenCalledWith(query); expect(results).toEqual(mockSearchResults); expect(results[0].relevanceScore).toBeGreaterThan(0.9); }); it('should handle concurrent memory operations', async () => { const memories = Array.from({ length: 100 }, (_, i) => ({ ...testMemory, id: `memory-${i}`, content: `Test memory ${i}` })); mockMemoryService.storeMemory.mockImplementation(async (memory) => memory); const promises = memories.map(memory => memoraiService.storeMemory(memory)); const results = await Promise.all(promises); expect(results).toHaveLength(100); expect(mockMemoryService.storeMemory).toHaveBeenCalledTimes(100); }); it('should validate memory data before storage', async () => { const invalidMemory = { ...testMemory, userId: '' }; await expect( memoraiService.storeMemory(invalidMemory) ).rejects.toThrow('Invalid memory data'); }); }); describe('Database Operations', () => { it('should execute SQL queries safely', async () => { const testQuery: DatabaseQuery = { sql: 'SELECT * FROM users WHERE id = ?', params: ['user-123'] }; const mockResult = { rows: [{ id: 'user-123', name: 'Test User' }] }; mockDatabaseService.query.mockResolvedValue(mockResult); const result = await memoraiService.query(testQuery); expect(mockDatabaseService.query).toHaveBeenCalledWith(testQuery); expect(result).toEqual(mockResult); }); it('should handle database connection failures', async () => { mockDatabaseService.query.mockRejectedValue(new Error('Connection failed')); await expect( memoraiService.query({ sql: 'SELECT 1' }) ).rejects.toThrow('Connection failed'); }); it('should support database transactions', async () => { const transactionSpy = vi.fn(); mockDatabaseService.transaction = transactionSpy; await memoraiService.transaction(async (tx) => { await tx.query({ sql: 'INSERT INTO test VALUES (?)' }); }); expect(transactionSpy).toHaveBeenCalled(); }); it('should maintain connection pool efficiency', async () => { // Execute multiple concurrent queries const queries = Array.from({ length: 50 }, (_, i) => ({ sql: 'SELECT ?', params: [i] })); mockDatabaseService.query.mockImplementation(async (query) => ({ rows: [{ value: query.params?.[0] }] })); const promises = queries.map(query => memoraiService.query(query)); const results = await Promise.all(promises); expect(results).toHaveLength(50); expect(mockDatabaseService.query).toHaveBeenCalledTimes(50); }); }); describe('File Storage Operations', () => { const testFile: StorageFile = { id: 'file-123', filename: 'test-document.pdf', path: '/uploads/documents/test-document.pdf', size: 1024000, mimeType: 'application/pdf', metadata: { uploadedBy: 'user-123', uploadDate: new Date() } }; it('should upload files with metadata', async () => { mockStorageService.uploadFile.mockResolvedValue(testFile); const fileBuffer = Buffer.from('test file content'); const result = await memoraiService.uploadFile(fileBuffer, 'test-document.pdf'); expect(mockStorageService.uploadFile).toHaveBeenCalledWith( fileBuffer, 'test-document.pdf' ); expect(result).toEqual(testFile); }); it('should generate secure download URLs', async () => { const mockUrl = 'https://storage.codai.com/secure/file-123?token=xyz'; mockStorageService.generateDownloadUrl.mockResolvedValue(mockUrl); const url = await memoraiService.generateDownloadUrl('file-123'); expect(mockStorageService.generateDownloadUrl).toHaveBeenCalledWith('file-123'); expect(url).toBe(mockUrl); }); it('should handle file upload errors gracefully', async () => { mockStorageService.uploadFile.mockRejectedValue(new Error('Upload failed')); const fileBuffer = Buffer.from('test content'); await expect( memoraiService.uploadFile(fileBuffer, 'test.txt') ).rejects.toThrow('Upload failed'); }); it('should support image processing and optimization', async () => { const imageBuffer = Buffer.from('fake image data'); const optimizedFile = { ...testFile, size: 512000 }; // Smaller size mockStorageService.uploadFile.mockResolvedValue(optimizedFile); const result = await memoraiService.uploadFile(imageBuffer, 'image.jpg'); expect(result.size).toBeLessThan(testFile.size); }); }); describe('Caching Operations', () => { it('should cache frequently accessed data', async () => { const cacheKey = 'user:user-123:preferences'; const testData = { theme: 'dark', language: 'en' }; mockCacheService.get.mockResolvedValue(null); mockCacheService.set.mockResolvedValue(true); await memoraiService.cacheSet(cacheKey, testData, 3600); expect(mockCacheService.set).toHaveBeenCalledWith(cacheKey, testData, 3600); }); it('should retrieve cached data when available', async () => { const cacheKey = 'user:user-123:preferences'; const cachedData = { theme: 'dark', language: 'en' }; mockCacheService.get.mockResolvedValue(cachedData); const result = await memoraiService.cacheGet(cacheKey); expect(mockCacheService.get).toHaveBeenCalledWith(cacheKey); expect(result).toEqual(cachedData); }); it('should invalidate expired cache entries', async () => { const expiredKeys = ['expired:key1', 'expired:key2']; mockCacheService.invalidateExpired.mockResolvedValue(expiredKeys); const result = await memoraiService.cleanupExpiredCache(); expect(mockCacheService.invalidateExpired).toHaveBeenCalled(); expect(result).toEqual(expiredKeys); }); }); describe('Real-time Synchronization', () => { it('should sync data across multiple clients', async () => { const syncOperation = { id: 'sync-123', type: 'memory-update', userId: 'user-123', data: testMemory }; mockSyncService.broadcast.mockResolvedValue(true); await memoraiService.broadcastSync(syncOperation); expect(mockSyncService.broadcast).toHaveBeenCalledWith(syncOperation); }); it('should handle sync conflicts gracefully', async () => { const conflictResolver = vi.fn().mockResolvedValue(testMemory); await memoraiService.configureSyncConflictResolver(conflictResolver); expect(mockSyncService.setConflictResolver).toHaveBeenCalledWith(conflictResolver); }); it('should maintain sync state consistency', async () => { const syncState = { lastSync: new Date(), pendingOperations: 0, connectedClients: 3 }; mockSyncService.getState.mockReturnValue(syncState); const state = memoraiService.getSyncState(); expect(state).toEqual(syncState); }); }); describe('Analytics and Monitoring', () => { it('should track service usage analytics', async () => { const analyticsEvent = { event: 'memory_stored', userId: 'user-123', metadata: { memorySize: 150 } }; await memoraiService.trackAnalytics(analyticsEvent); expect(mockAnalyticsService.track).toHaveBeenCalledWith(analyticsEvent); }); it('should generate performance reports', async () => { const mockReport = { memoryOperations: { total: 1000, avgTime: 45 }, storageOperations: { total: 200, avgTime: 120 }, cacheHitRate: 0.85 }; mockAnalyticsService.generateReport.mockResolvedValue(mockReport); const report = await memoraiService.getAnalyticsReport(); expect(report).toEqual(mockReport); }); it('should monitor system health metrics', async () => { const healthMetrics = { database: { status: 'healthy', responseTime: 15 }, storage: { status: 'healthy', responseTime: 25 }, cache: { status: 'healthy', hitRate: 0.85 } }; mockAnalyticsService.getHealthMetrics.mockResolvedValue(healthMetrics); const health = await memoraiService.getHealthStatus(); expect(health.database.status).toBe('healthy'); expect(health.storage.status).toBe('healthy'); expect(health.cache.status).toBe('healthy'); }); }); describe('Error Handling and Recovery', () => { it('should handle service failures gracefully', async () => { mockDatabaseService.query.mockRejectedValue(new Error('Database error')); const errorHandler = vi.fn(); memoraiService.on('error', errorHandler); await expect( memoraiService.query({ sql: 'SELECT 1' }) ).rejects.toThrow('Database error'); expect(errorHandler).toHaveBeenCalledWith( expect.objectContaining({ service: 'database', error: expect.any(Error) }) ); }); it('should implement circuit breaker pattern', async () => { // Simulate repeated failures mockStorageService.uploadFile.mockRejectedValue(new Error('Service unavailable')); const fileBuffer = Buffer.from('test'); // After several failures, circuit should open for (let i = 0; i < 5; i++) { await expect( memoraiService.uploadFile(fileBuffer, 'test.txt') ).rejects.toThrow(); } // Circuit should now be open, failing fast await expect( memoraiService.uploadFile(fileBuffer, 'test.txt') ).rejects.toThrow('Circuit breaker is open'); }); it('should retry failed operations with exponential backoff', async () => { let attemptCount = 0; mockMemoryService.storeMemory.mockImplementation(async () => { attemptCount++; if (attemptCount < 3) { throw new Error('Temporary failure'); } return testMemory; }); const result = await memoraiService.storeMemory(testMemory); expect(attemptCount).toBe(3); expect(result).toEqual(testMemory); }); }); describe('Performance and Scalability', () => { it('should handle high-volume memory operations efficiently', async () => { const startTime = Date.now(); const batchSize = 1000; mockMemoryService.storeMemoryBatch.mockImplementation(async (memories) => { return memories.map(m => ({ ...m, id: `batch-${Math.random()}` })); }); const memories = Array.from({ length: batchSize }, (_, i) => ({ ...testMemory, content: `High volume memory ${i}` })); await memoraiService.storeMemoryBatch(memories); const duration = Date.now() - startTime; expect(duration).toBeLessThan(5000); // Should complete within 5 seconds }); it('should maintain performance under concurrent load', async () => { const concurrentOperations = 100; mockMemoryService.searchMemories.mockImplementation(async () => []); const promises = Array.from({ length: concurrentOperations }, (_, i) => memoraiService.searchMemories({ query: `concurrent query ${i}`, userId: 'user-123' }) ); const startTime = Date.now(); await Promise.all(promises); const duration = Date.now() - startTime; expect(duration).toBeLessThan(3000); // Should handle 100 concurrent operations quickly }); it('should implement memory usage optimization', async () => { const initialMemory = process.memoryUsage().heapUsed; // Perform memory-intensive operations const largeDataset = Array.from({ length: 10000 }, (_, i) => ({ ...testMemory, content: `Large dataset item ${i}`.repeat(100) })); mockMemoryService.storeMemoryBatch.mockResolvedValue(largeDataset); await memoraiService.storeMemoryBatch(largeDataset); // Force garbage collection if available if (global.gc) { global.gc(); } const finalMemory = process.memoryUsage().heapUsed; const memoryIncrease = finalMemory - initialMemory; // Memory increase should be reasonable (less than 100MB) expect(memoryIncrease).toBeLessThan(100 * 1024 * 1024); }); }); describe('Security and Compliance', () => { it('should encrypt sensitive data before storage', async () => { const sensitiveMemory = { ...testMemory, content: 'User credit card ending in 1234', metadata: { ...testMemory.metadata, sensitive: true } }; mockMemoryService.storeMemory.mockImplementation(async (memory) => { expect(memory.content).not.toContain('1234'); // Should be encrypted return memory; }); await memoraiService.storeMemory(sensitiveMemory); }); it('should validate user permissions for data access', async () => { const restrictedQuery: MemoryQuery = { query: 'financial data', userId: 'user-123' }; // Mock permission check failure mockMemoryService.searchMemories.mockRejectedValue( new Error('Insufficient permissions') ); await expect( memoraiService.searchMemories(restrictedQuery) ).rejects.toThrow('Insufficient permissions'); }); it('should audit all data operations', async () => { const auditSpy = vi.fn(); memoraiService.on('audit', auditSpy); await memoraiService.storeMemory(testMemory); expect(auditSpy).toHaveBeenCalledWith({ action: 'memory_store', userId: 'user-123', resourceId: testMemory.id, timestamp: expect.any(Date) }); }); it('should implement data retention policies', async () => { const retentionPolicy = { maxAge: 365 * 24 * 60 * 60 * 1000, // 1 year autoDelete: true }; await memoraiService.configureRetentionPolicy(retentionPolicy); expect(mockMemoryService.setRetentionPolicy).toHaveBeenCalledWith(retentionPolicy); }); }); });