@codai/memorai
Version:
Universal Database & Storage Service for CODAI Ecosystem - CBD Backend
582 lines (453 loc) • 20 kB
text/typescript
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);
});
});
});