import { describe, it, expect, vi, beforeEach } from 'vitest'; import { AnalyticsService } from './AnalyticsService'; import { AnalyticsApiClient } from '../../api/analytics/AnalyticsApiClient'; import type { RecordPageViewInputDto, RecordPageViewOutputDto, RecordEngagementInputDto, RecordEngagementOutputDto } from '../../dtos'; describe('AnalyticsService', () => { let mockApiClient: AnalyticsApiClient; let service: AnalyticsService; beforeEach(() => { mockApiClient = { recordPageView: vi.fn(), recordEngagement: vi.fn(), getDashboardData: vi.fn(), getAnalyticsMetrics: vi.fn(), } as unknown as AnalyticsApiClient; service = new AnalyticsService(mockApiClient); }); describe('recordPageView', () => { it('should record page view via API client', async () => { // Arrange const input: RecordPageViewInputDto = { page: '/dashboard', timestamp: '2025-12-17T20:00:00Z', userId: 'user-1', }; const expectedOutput: RecordPageViewOutputDto = { success: true, }; vi.mocked(mockApiClient.recordPageView).mockResolvedValue(expectedOutput); // Act const result = await service.recordPageView(input); // Assert expect(mockApiClient.recordPageView).toHaveBeenCalledWith(input); expect(mockApiClient.recordPageView).toHaveBeenCalledTimes(1); expect(result).toBe(expectedOutput); }); it('should propagate API client errors', async () => { // Arrange const input: RecordPageViewInputDto = { page: '/dashboard', timestamp: '2025-12-17T20:00:00Z', userId: 'user-1', }; const error = new Error('API Error: Failed to record page view'); vi.mocked(mockApiClient.recordPageView).mockRejectedValue(error); // Act & Assert await expect(service.recordPageView(input)).rejects.toThrow( 'API Error: Failed to record page view' ); expect(mockApiClient.recordPageView).toHaveBeenCalledWith(input); expect(mockApiClient.recordPageView).toHaveBeenCalledTimes(1); }); it('should handle different input parameters', async () => { // Arrange const input: RecordPageViewInputDto = { page: '/races', timestamp: '2025-12-18T10:30:00Z', userId: 'user-2', }; const expectedOutput: RecordPageViewOutputDto = { success: true, }; vi.mocked(mockApiClient.recordPageView).mockResolvedValue(expectedOutput); // Act const result = await service.recordPageView(input); // Assert expect(mockApiClient.recordPageView).toHaveBeenCalledWith(input); expect(result).toBe(expectedOutput); }); }); describe('recordEngagement', () => { it('should record engagement event via API client', async () => { // Arrange const input: RecordEngagementInputDto = { event: 'button_click', element: 'register-race-btn', page: '/races', timestamp: '2025-12-17T20:00:00Z', userId: 'user-1', }; const expectedOutput: RecordEngagementOutputDto = { success: true, }; vi.mocked(mockApiClient.recordEngagement).mockResolvedValue(expectedOutput); // Act const result = await service.recordEngagement(input); // Assert expect(mockApiClient.recordEngagement).toHaveBeenCalledWith(input); expect(mockApiClient.recordEngagement).toHaveBeenCalledTimes(1); expect(result).toBe(expectedOutput); }); it('should propagate API client errors', async () => { // Arrange const input: RecordEngagementInputDto = { event: 'form_submit', element: 'contact-form', page: '/contact', timestamp: '2025-12-17T20:00:00Z', userId: 'user-1', }; const error = new Error('API Error: Failed to record engagement'); vi.mocked(mockApiClient.recordEngagement).mockRejectedValue(error); // Act & Assert await expect(service.recordEngagement(input)).rejects.toThrow( 'API Error: Failed to record engagement' ); expect(mockApiClient.recordEngagement).toHaveBeenCalledWith(input); expect(mockApiClient.recordEngagement).toHaveBeenCalledTimes(1); }); it('should handle different engagement types', async () => { // Arrange const input: RecordEngagementInputDto = { event: 'scroll', element: 'race-list', page: '/races', timestamp: '2025-12-18T10:30:00Z', userId: 'user-2', }; const expectedOutput: RecordEngagementOutputDto = { success: true, }; vi.mocked(mockApiClient.recordEngagement).mockResolvedValue(expectedOutput); // Act const result = await service.recordEngagement(input); // Assert expect(mockApiClient.recordEngagement).toHaveBeenCalledWith(input); expect(result).toBe(expectedOutput); }); }); describe('Constructor Dependency Injection', () => { it('should require apiClient', () => { // This test verifies the constructor signature expect(() => { new AnalyticsService(mockApiClient); }).not.toThrow(); }); it('should use injected apiClient', async () => { // Arrange const customApiClient = { recordPageView: vi.fn().mockResolvedValue({ success: true }), recordEngagement: vi.fn().mockResolvedValue({ success: true }), getDashboardData: vi.fn(), getAnalyticsMetrics: vi.fn(), } as unknown as AnalyticsApiClient; const customService = new AnalyticsService(customApiClient); const input: RecordPageViewInputDto = { page: '/test', timestamp: '2025-12-17T20:00:00Z', userId: 'user-1', }; // Act await customService.recordPageView(input); // Assert expect(customApiClient.recordPageView).toHaveBeenCalledWith(input); }); }); });