/** * Analytics Service Tests */ import { AnalyticsService, CommandUsage } from '../services/analytics'; describe('AnalyticsService', () => { let analytics: AnalyticsService; beforeEach(() => { analytics = new AnalyticsService(); analytics.clear(); }); afterEach(() => { analytics.clear(); }); describe('trackCommand', () => { it('should track command usage', () => { const usage: CommandUsage = { command: 'chat', timestamp: Date.now(), model: 'deepseek-coder:33b', success: true, durationMs: 1500, }; analytics.trackCommand(usage); const summary = analytics.getSummary(); expect(summary.totalCommands).toBe(1); expect(summary.commandFrequency['chat']).toBe(1); expect(summary.modelUsage['deepseek-coder:33b']).toBe(1); }); it('should not track when disabled', () => { analytics.setEnabled(false); const usage: CommandUsage = { command: 'chat', timestamp: Date.now(), success: true, }; analytics.trackCommand(usage); const summary = analytics.getSummary(); expect(summary.totalCommands).toBe(0); }); it('should limit stored events to MAX_EVENTS', () => { // Track more than MAX_EVENTS (1000) for (let i = 0; i < 1100; i++) { analytics.trackCommand({ command: 'chat', timestamp: Date.now(), success: true, }); } const summary = analytics.getSummary(); expect(summary.totalCommands).toBe(1000); }); }); describe('getSummary', () => { it('should return empty summary for no data', () => { const summary = analytics.getSummary(); expect(summary.totalCommands).toBe(0); expect(summary.successRate).toBe(0); expect(summary.averageResponseTime).toBe(0); expect(summary.mostUsedCommand).toBe(''); expect(summary.mostUsedModel).toBe(''); }); it('should calculate success rate correctly', () => { analytics.trackCommand({ command: 'chat', timestamp: Date.now(), success: true, }); analytics.trackCommand({ command: 'chat', timestamp: Date.now(), success: false, }); analytics.trackCommand({ command: 'chat', timestamp: Date.now(), success: true, }); const summary = analytics.getSummary(); expect(summary.successRate).toBeCloseTo(66.67, 1); }); it('should calculate average response time', () => { analytics.trackCommand({ command: 'chat', timestamp: Date.now(), success: true, durationMs: 1000, }); analytics.trackCommand({ command: 'chat', timestamp: Date.now(), success: true, durationMs: 2000, }); const summary = analytics.getSummary(); expect(summary.averageResponseTime).toBe(1500); }); it('should identify most used command', () => { analytics.trackCommand({ command: 'chat', timestamp: Date.now(), success: true, }); analytics.trackCommand({ command: 'chat', timestamp: Date.now(), success: true, }); analytics.trackCommand({ command: 'edit', timestamp: Date.now(), success: true, }); const summary = analytics.getSummary(); expect(summary.mostUsedCommand).toBe('chat'); }); it('should identify most used model', () => { analytics.trackCommand({ command: 'chat', timestamp: Date.now(), model: 'deepseek-coder:33b', success: true, }); analytics.trackCommand({ command: 'chat', timestamp: Date.now(), model: 'deepseek-coder:33b', success: true, }); analytics.trackCommand({ command: 'chat', timestamp: Date.now(), model: 'codellama:34b', success: true, }); const summary = analytics.getSummary(); expect(summary.mostUsedModel).toBe('deepseek-coder:33b'); }); }); describe('getModelRecommendation', () => { it('should return null for no data', () => { const recommendation = analytics.getModelRecommendation('code_generation'); expect(recommendation).toBeNull(); }); it('should recommend model with highest success rate', () => { // Track successful edits with deepseek for (let i = 0; i < 10; i++) { analytics.trackCommand({ command: 'edit', timestamp: Date.now(), model: 'deepseek-coder:33b', success: true, durationMs: 1000, }); } // Track mixed results with codellama for (let i = 0; i < 5; i++) { analytics.trackCommand({ command: 'edit', timestamp: Date.now(), model: 'codellama:34b', success: i < 2, // 2 success, 3 failures durationMs: 1000, }); } const recommendation = analytics.getModelRecommendation('code_generation'); expect(recommendation).toBe('deepseek-coder:33b'); }); it('should prefer faster model when success rates are similar', () => { // Both models with 100% success but different speeds analytics.trackCommand({ command: 'edit', timestamp: Date.now(), model: 'deepseek-coder:33b', success: true, durationMs: 2000, }); analytics.trackCommand({ command: 'edit', timestamp: Date.now(), model: 'codellama:34b', success: true, durationMs: 1000, }); const recommendation = analytics.getModelRecommendation('code_generation'); expect(recommendation).toBe('codellama:34b'); }); }); describe('getRecentEvents', () => { it('should return events from last N days', () => { const now = Date.now(); const oneDayAgo = now - (24 * 60 * 60 * 1000); const eightDaysAgo = now - (8 * 24 * 60 * 60 * 1000); analytics.trackCommand({ command: 'chat', timestamp: oneDayAgo, success: true, }); analytics.trackCommand({ command: 'chat', timestamp: eightDaysAgo, success: true, }); const recent = analytics.getRecentEvents(7); expect(recent.length).toBe(1); expect(recent[0].timestamp).toBe(oneDayAgo); }); }); describe('enable/disable', () => { it('should enable/disable analytics', () => { expect(analytics.isEnabled()).toBe(true); analytics.setEnabled(false); expect(analytics.isEnabled()).toBe(false); analytics.setEnabled(true); expect(analytics.isEnabled()).toBe(true); }); }); describe('export', () => { it('should export analytics data as JSON', () => { analytics.trackCommand({ command: 'chat', timestamp: Date.now(), success: true, }); const exported = analytics.export(); const data = JSON.parse(exported); expect(data.summary).toBeDefined(); expect(data.events).toBeDefined(); expect(data.modelPerformance).toBeDefined(); expect(data.summary.totalCommands).toBe(1); }); }); describe('clear', () => { it('should clear all analytics data', () => { analytics.trackCommand({ command: 'chat', timestamp: Date.now(), success: true, }); expect(analytics.getSummary().totalCommands).toBe(1); analytics.clear(); expect(analytics.getSummary().totalCommands).toBe(0); }); }); });