UNPKG

@ahmedhegazee/nestjs-telescope

Version:

Advanced observability and monitoring solution for NestJS applications with ML-powered analytics, enterprise features, and production-ready scaling

496 lines 20.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const testing_1 = require("@nestjs/testing"); const analytics_service_1 = require("./analytics.service"); const telescope_service_1 = require("./telescope.service"); describe('AnalyticsService', () => { let service; let telescopeService; beforeEach(async () => { const mockTelescopeService = { getEntries: jest.fn(), clearEntries: jest.fn(), }; const module = await testing_1.Test.createTestingModule({ providers: [ analytics_service_1.AnalyticsService, { provide: telescope_service_1.TelescopeService, useValue: mockTelescopeService, }, ], }).compile(); service = module.get(analytics_service_1.AnalyticsService); telescopeService = module.get(telescope_service_1.TelescopeService); }); afterEach(() => { jest.clearAllMocks(); }); describe('initialization', () => { it('should be defined', () => { expect(service).toBeDefined(); }); it('should initialize with default analytics data', () => { const analytics = service.getAnalytics(); expect(analytics.overview).toBeDefined(); expect(analytics.performance).toBeDefined(); expect(analytics.errors).toBeDefined(); }); }); describe('analytics calculation', () => { it('should calculate user analytics correctly', () => { const mockEntries = [ { id: '1', type: 'request', timestamp: new Date(), content: { userId: 'user-1', request: { path: '/api/users', method: 'GET' }, }, }, { id: '2', type: 'request', timestamp: new Date(), content: { userId: 'user-2', request: { path: '/api/posts', method: 'GET' }, }, }, { id: '3', type: 'request', timestamp: new Date(), content: { userId: 'user-1', request: { path: '/api/users', method: 'POST' }, }, }, ]; telescopeService.getEntries.mockReturnValue(mockEntries); const analytics = service.getAnalytics(); expect(analytics.overview.totalRequests).toBe(3); expect(analytics.overview.activeUsers).toBe(2); expect(analytics.users.activeUsers).toHaveLength(2); expect(analytics.users.activeUsers[0].userId).toBe('user-1'); expect(analytics.users.activeUsers[0].requestCount).toBe(2); }); it('should calculate performance analytics correctly', () => { const mockEntries = [ { id: '1', type: 'request', timestamp: new Date(), content: { request: { path: '/api/users', method: 'GET', duration: 100 }, }, }, { id: '2', type: 'request', timestamp: new Date(), content: { request: { path: '/api/posts', method: 'GET', duration: 200 }, }, }, { id: '3', type: 'query', timestamp: new Date(), content: { query: { duration: 50 }, }, }, ]; telescopeService.getEntries.mockReturnValue(mockEntries); const analytics = service.getAnalytics(); expect(analytics.overview.averageResponseTime).toBe(150); expect(analytics.overview.totalRequests).toBe(2); expect(analytics.overview.totalQueries).toBe(1); expect(analytics.performance.slowestEndpoints).toHaveLength(2); }); it('should calculate error analytics correctly', () => { const mockEntries = [ { id: '1', type: 'exception', timestamp: new Date(), content: { exception: { type: 'ValidationError', message: 'Invalid input' }, }, }, { id: '2', type: 'exception', timestamp: new Date(), content: { exception: { type: 'ValidationError', message: 'Missing field' }, }, }, { id: '3', type: 'exception', timestamp: new Date(), content: { exception: { type: 'DatabaseError', message: 'Connection failed' }, }, }, ]; telescopeService.getEntries.mockReturnValue(mockEntries); const analytics = service.getAnalytics(); expect(analytics.overview.totalErrors).toBe(3); expect(analytics.errors.errorDistribution.byType).toHaveLength(2); expect(analytics.errors.errorDistribution.byType[0].type).toBe('ValidationError'); expect(analytics.errors.errorDistribution.byType[0].count).toBe(2); }); it('should calculate performance distribution correctly', () => { const mockEntries = [ { id: '1', type: 'request', timestamp: new Date(), content: { request: { duration: 50 }, }, }, { id: '2', type: 'request', timestamp: new Date(), content: { request: { duration: 150 }, }, }, { id: '3', type: 'request', timestamp: new Date(), content: { request: { duration: 300 }, }, }, { id: '4', type: 'request', timestamp: new Date(), content: { request: { duration: 800 }, }, }, ]; telescopeService.getEntries.mockReturnValue(mockEntries); const analytics = service.getAnalytics(); expect(analytics.performance.responseTimeDistribution).toBeDefined(); expect(analytics.performance.responseTimeDistribution.buckets).toHaveLength(4); expect(analytics.performance.responseTimeDistribution.buckets[0].count).toBe(1); expect(analytics.performance.responseTimeDistribution.buckets[1].count).toBe(1); expect(analytics.performance.responseTimeDistribution.buckets[2].count).toBe(1); expect(analytics.performance.responseTimeDistribution.buckets[3].count).toBe(1); }); }); describe('time-based analytics', () => { it('should calculate hourly trends', () => { const now = new Date(); const mockEntries = [ { id: '1', type: 'request', timestamp: new Date(now.getTime() - 3600000), content: { request: { duration: 100 } }, }, { id: '2', type: 'request', timestamp: new Date(now.getTime() - 1800000), content: { request: { duration: 200 } }, }, { id: '3', type: 'request', timestamp: now, content: { request: { duration: 150 } }, }, ]; telescopeService.getEntries.mockReturnValue(mockEntries); const analytics = service.getAnalytics(); expect(analytics.trends.performanceTrends).toBeDefined(); expect(analytics.trends.performanceTrends.length).toBeGreaterThan(0); }); it('should filter analytics by time range', () => { const now = new Date(); const oneHourAgo = new Date(now.getTime() - 3600000); const twoHoursAgo = new Date(now.getTime() - 7200000); const mockEntries = [ { id: '1', type: 'request', timestamp: twoHoursAgo, content: { request: { duration: 100 } }, }, { id: '2', type: 'request', timestamp: oneHourAgo, content: { request: { duration: 200 } }, }, { id: '3', type: 'request', timestamp: now, content: { request: { duration: 150 } }, }, ]; telescopeService.getEntries.mockReturnValue(mockEntries); const analytics = service.getAnalytics(oneHourAgo, now); expect(analytics.overview.totalRequests).toBe(2); expect(analytics.overview.averageResponseTime).toBe(175); }); }); describe('trend analysis', () => { it('should calculate trend predictions', () => { const mockEntries = Array.from({ length: 10 }, (_, i) => ({ id: `${i}`, type: 'request', timestamp: new Date(Date.now() - i * 3600000), content: { request: { duration: 100 + i * 10 }, }, })); telescopeService.getEntries.mockReturnValue(mockEntries); const analytics = service.getAnalytics(); expect(analytics.trends.performanceTrends).toBeDefined(); expect(analytics.trends.performanceTrends.length).toBeGreaterThan(0); }); it('should identify performance degradation', () => { const mockEntries = Array.from({ length: 20 }, (_, i) => ({ id: `${i}`, type: 'request', timestamp: new Date(Date.now() - i * 3600000), content: { request: { duration: i < 10 ? 100 : 500 }, }, })); telescopeService.getEntries.mockReturnValue(mockEntries); const analytics = service.getAnalytics(); expect(analytics.trends.performanceTrends).toBeDefined(); expect(analytics.trends.performanceTrends.length).toBeGreaterThan(0); }); it('should identify improvement trends', () => { const mockEntries = Array.from({ length: 20 }, (_, i) => ({ id: `${i}`, type: 'request', timestamp: new Date(Date.now() - i * 3600000), content: { request: { duration: i < 10 ? 500 : 100 }, }, })); telescopeService.getEntries.mockReturnValue(mockEntries); const analytics = service.getAnalytics(); expect(analytics.trends.performanceTrends).toBeDefined(); expect(analytics.trends.performanceTrends.length).toBeGreaterThan(0); }); }); describe('anomaly detection', () => { it('should detect performance anomalies', () => { const mockEntries = [ ...Array.from({ length: 10 }, (_, i) => ({ id: `normal-${i}`, type: 'request', timestamp: new Date(Date.now() - i * 3600000), content: { request: { duration: 100 }, }, })), { id: 'anomaly', type: 'request', timestamp: new Date(), content: { request: { duration: 5000 }, }, }, ]; telescopeService.getEntries.mockReturnValue(mockEntries); const analytics = service.getAnalytics(); expect(analytics.alerts.anomalies).toBeDefined(); expect(analytics.alerts.anomalies.length).toBeGreaterThan(0); }); it('should detect error rate anomalies', () => { const mockEntries = [ ...Array.from({ length: 100 }, (_, i) => ({ id: `request-${i}`, type: 'request', timestamp: new Date(Date.now() - i * 60000), content: { request: { duration: 100 } }, })), ...Array.from({ length: 20 }, (_, i) => ({ id: `error-${i}`, type: 'exception', timestamp: new Date(Date.now() - i * 60000), content: { exception: { type: 'Error', message: 'Anomaly error' }, }, })), ]; telescopeService.getEntries.mockReturnValue(mockEntries); const analytics = service.getAnalytics(); expect(analytics.alerts.anomalies).toBeDefined(); expect(analytics.alerts.anomalies.length).toBeGreaterThan(0); }); }); describe('comprehensive analysis', () => { it('should handle mixed entry types', () => { const mockEntries = [ { id: '1', type: 'request', timestamp: new Date(), content: { userId: 'user-1', request: { path: '/api/users', method: 'GET', duration: 100 }, }, }, { id: '2', type: 'query', timestamp: new Date(), content: { userId: 'user-1', query: { sql: 'SELECT * FROM users', duration: 50 }, }, }, { id: '3', type: 'exception', timestamp: new Date(), content: { userId: 'user-1', exception: { type: 'ValidationError', message: 'Invalid input' }, }, }, { id: '4', type: 'cache', timestamp: new Date(), content: { userId: 'user-1', cache: { operation: 'get', hit: true, duration: 5 }, }, }, { id: '5', type: 'job', timestamp: new Date(), content: { userId: 'user-1', job: { name: 'email-job', status: 'completed', duration: 1000 }, }, }, ]; telescopeService.getEntries.mockReturnValue(mockEntries); const analytics = service.getAnalytics(); expect(analytics.overview.totalRequests).toBe(1); expect(analytics.overview.totalQueries).toBe(1); expect(analytics.overview.totalErrors).toBe(1); expect(analytics.users.activeUsers).toHaveLength(1); }); it('should calculate system health score', () => { const mockEntries = [ { id: '1', type: 'request', timestamp: new Date(), content: { request: { duration: 100 }, }, }, { id: '2', type: 'exception', timestamp: new Date(), content: { exception: { type: 'Error', message: 'Test error' }, }, }, ]; telescopeService.getEntries.mockReturnValue(mockEntries); const analytics = service.getAnalytics(); expect(analytics.overview).toBeDefined(); expect(analytics.overview.totalRequests).toBe(1); expect(analytics.overview.totalErrors).toBe(1); expect(analytics.overview.errorRate).toBeGreaterThan(0); }); }); describe('performance optimization', () => { it('should handle large datasets efficiently', () => { const largeDataset = Array.from({ length: 10000 }, (_, i) => ({ id: `${i}`, type: 'request', timestamp: new Date(Date.now() - i * 1000), content: { userId: `user-${i % 100}`, request: { path: `/api/endpoint${i % 10}`, duration: 100 + (i % 500) }, }, })); telescopeService.getEntries.mockReturnValue(largeDataset); const startTime = Date.now(); const analytics = service.getAnalytics(); const endTime = Date.now(); expect(endTime - startTime).toBeLessThan(1000); expect(analytics.users.activeUsers).toHaveLength(100); expect(analytics.overview.totalRequests).toBe(10000); }); it('should cache analytics results', () => { const mockEntries = [ { id: '1', type: 'request', timestamp: new Date(), content: { request: { duration: 100 } }, }, ]; telescopeService.getEntries.mockReturnValue(mockEntries); const analytics1 = service.getAnalytics(); const analytics2 = service.getAnalytics(); expect(telescopeService.getEntries).toHaveBeenCalledTimes(1); expect(analytics1).toBe(analytics2); }); }); describe('error handling', () => { it('should handle empty data gracefully', () => { telescopeService.getEntries.mockReturnValue([]); const analytics = service.getAnalytics(); expect(analytics.overview.totalRequests).toBe(0); expect(analytics.overview.totalErrors).toBe(0); expect(analytics.users.activeUsers).toHaveLength(0); }); it('should handle malformed entries gracefully', () => { const mockEntries = [ { id: '1', type: 'request', timestamp: new Date(), content: null, }, { id: '2', type: 'request', timestamp: new Date(), content: { request: { duration: 'invalid' } }, }, { id: '3', type: 'request', timestamp: new Date(), content: { request: { duration: 100 } }, }, ]; telescopeService.getEntries.mockReturnValue(mockEntries); const analytics = service.getAnalytics(); expect(analytics.overview.totalRequests).toBe(1); expect(analytics.overview.averageResponseTime).toBe(100); }); }); }); //# sourceMappingURL=analytics.service.spec.js.map