UNPKG

@ahmedhegazee/nestjs-telescope

Version:

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

415 lines 15.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const testing_1 = require("@nestjs/testing"); const request_watcher_service_1 = require("./request-watcher.service"); const telescope_service_1 = require("../../core/services/telescope.service"); describe('RequestWatcherService', () => { let service; let telescopeService; beforeEach(async () => { const mockTelescopeService = { record: jest.fn(), }; const mockConfig = { enabled: true, environment: 'test', storage: { driver: 'memory', retention: { hours: 24, maxEntries: 10000 }, batch: { enabled: true, size: 100, flushInterval: 1000 } }, devtools: { enabled: true, port: 8001, features: { dependencyGraph: true, interactivePlayground: true, performanceMetrics: true } }, dashboard: { enabled: true, path: '/telescope', strategy: 'hybrid' }, features: { realTimeUpdates: true, analytics: true, customWatchers: true }, watchers: { request: { enabled: true, excludePaths: ['/health', '/metrics'], sampling: { enabled: true, rate: 100, rules: [ { path: '/api/health', rate: 10, priority: 1 }, { path: '/api', method: 'POST', rate: 100, priority: 3 } ] }, security: { maskSensitiveData: true, logResponseBodies: false, logSuccessfulResponseBodies: false, sensitiveKeys: ['password', 'token', 'secret'] }, performance: { slowRequestThreshold: 1000, collectMetrics: true } } } }; const module = await testing_1.Test.createTestingModule({ providers: [ request_watcher_service_1.RequestWatcherService, { provide: telescope_service_1.TelescopeService, useValue: mockTelescopeService, }, { provide: 'TELESCOPE_CONFIG', useValue: mockConfig, }, ], }).compile(); service = module.get(request_watcher_service_1.RequestWatcherService); telescopeService = module.get(telescope_service_1.TelescopeService); }); afterEach(() => { jest.clearAllMocks(); service.resetMetrics(); }); describe('initialization', () => { it('should be defined', () => { expect(service).toBeDefined(); }); it('should initialize with correct configuration', () => { const config = service.getConfig(); expect(config.enabled).toBe(true); expect(config.excludePaths).toContain('/health'); expect(config.excludePaths).toContain('/metrics'); expect(config.security.maskSensitiveData).toBe(true); }); it('should initialize with empty metrics', () => { const metrics = service.getMetrics(); expect(metrics.totalRequests).toBe(0); expect(metrics.slowRequests).toBe(0); expect(metrics.errorRequests).toBe(0); }); }); describe('request tracking', () => { it('should track successful request', () => { const requestContext = { id: 'req-1', startTime: Date.now(), method: 'GET', url: '/api/users', headers: { 'user-agent': 'test' }, query: {}, body: null, userAgent: 'test', ip: '127.0.0.1', sessionId: 'session-1', userId: 'user-1', traceId: 'trace-1' }; const responseContext = { statusCode: 200, headers: {}, body: null, size: 0, endTime: Date.now() + 100, duration: 100 }; service.trackRequest(requestContext, responseContext, null); expect(telescopeService.record).toHaveBeenCalledWith(expect.objectContaining({ type: 'request', content: expect.objectContaining({ request: expect.objectContaining({ method: 'GET', url: '/api/users', sessionId: 'session-1', userId: 'user-1' }), response: expect.objectContaining({ statusCode: 200, duration: 100 }) }) })); const metrics = service.getMetrics(); expect(metrics.totalRequests).toBe(1); }); it('should track error request', () => { const requestContext = { id: 'req-2', startTime: Date.now(), method: 'POST', url: '/api/users', headers: {}, query: {}, body: { name: 'test' }, userAgent: 'test', ip: '127.0.0.1' }; const responseContext = { statusCode: 500, headers: {}, body: { error: 'Internal Server Error' }, size: 100, endTime: Date.now() + 200, duration: 200 }; const error = new Error('Database connection failed'); service.trackRequest(requestContext, responseContext, error); expect(telescopeService.record).toHaveBeenCalledWith(expect.objectContaining({ type: 'request', content: expect.objectContaining({ error: expect.objectContaining({ message: 'Database connection failed', name: 'Error' }) }), tags: expect.arrayContaining(['error', 'server-error']) })); const metrics = service.getMetrics(); expect(metrics.errorRequests).toBe(1); }); it('should track slow request', () => { const requestContext = { id: 'req-3', startTime: Date.now(), method: 'GET', url: '/api/slow', headers: {}, query: {}, body: null, userAgent: 'test', ip: '127.0.0.1' }; const responseContext = { statusCode: 200, headers: {}, body: null, size: 0, endTime: Date.now() + 2000, duration: 2000 }; service.trackRequest(requestContext, responseContext, null); expect(telescopeService.record).toHaveBeenCalledWith(expect.objectContaining({ tags: expect.arrayContaining(['slow']), content: expect.objectContaining({ performance: expect.objectContaining({ slow: true }) }) })); const metrics = service.getMetrics(); expect(metrics.slowRequests).toBe(1); }); it('should not track when disabled', () => { const disabledConfig = { enabled: true, storage: { driver: 'memory' }, devtools: { enabled: true }, watchers: { request: { enabled: false } } }; const module = testing_1.Test.createTestingModule({ providers: [ request_watcher_service_1.RequestWatcherService, { provide: telescope_service_1.TelescopeService, useValue: { record: jest.fn() }, }, { provide: 'TELESCOPE_CONFIG', useValue: disabledConfig, }, ], }).compile(); const disabledService = module.get(request_watcher_service_1.RequestWatcherService); const requestContext = { id: 'req-4', startTime: Date.now(), method: 'GET', url: '/api/test', headers: {}, query: {}, body: null, userAgent: 'test', ip: '127.0.0.1' }; const responseContext = { statusCode: 200, headers: {}, body: null, size: 0, endTime: Date.now() + 100, duration: 100 }; disabledService.trackRequest(requestContext, responseContext, null); expect(telescopeService.record).not.toHaveBeenCalled(); }); }); describe('sampling', () => { it('should sample requests based on rules', () => { const mockRequest = { path: '/api/health', method: 'GET' }; const originalRandom = Math.random; Math.random = jest.fn(() => 0.5); expect(service.shouldSampleRequest(mockRequest)).toBe(false); mockRequest.path = '/api/users'; mockRequest.method = 'POST'; expect(service.shouldSampleRequest(mockRequest)).toBe(true); Math.random = originalRandom; }); it('should use default sampling rate when no rules match', () => { const mockRequest = { path: '/unknown', method: 'GET' }; const originalRandom = Math.random; Math.random = jest.fn(() => 0.5); expect(service.shouldSampleRequest(mockRequest)).toBe(true); Math.random = originalRandom; }); }); describe('security features', () => { it('should mask sensitive data in body', () => { const body = { username: 'test', password: 'secret123', token: 'abc123', data: 'normal data' }; expect(service.shouldMaskBody(body)).toBe(true); }); it('should not mask non-sensitive data', () => { const body = { username: 'test', data: 'normal data', count: 5 }; expect(service.shouldMaskBody(body)).toBe(false); }); }); describe('metrics collection', () => { beforeEach(() => { service.resetMetrics(); }); it('should collect request metrics', () => { for (let i = 0; i < 5; i++) { const requestContext = { id: `req-${i}`, startTime: Date.now(), method: 'GET', url: '/api/test', headers: {}, query: {}, body: null, userAgent: 'test', ip: '127.0.0.1' }; const responseContext = { statusCode: 200, headers: {}, body: null, size: 0, endTime: Date.now() + 100, duration: 100 }; service.trackRequest(requestContext, responseContext, null); } const metrics = service.getMetrics(); expect(metrics.totalRequests).toBe(5); expect(metrics.averageResponseTime).toBe(100); expect(metrics.statusCodeDistribution[200]).toBe(5); expect(metrics.methodDistribution['GET']).toBe(5); }); it('should calculate requests per second', () => { const now = Date.now(); for (let i = 0; i < 3; i++) { const requestContext = { id: `req-${i}`, startTime: now - (i * 10000), method: 'GET', url: '/api/test', headers: {}, query: {}, body: null, userAgent: 'test', ip: '127.0.0.1' }; const responseContext = { statusCode: 200, headers: {}, body: null, size: 0, endTime: now, duration: 100 }; service.trackRequest(requestContext, responseContext, null); } const metrics = service.getMetrics(); expect(metrics.requestsPerSecond).toBeGreaterThan(0); }); }); describe('configuration', () => { it('should return excluded paths', () => { const excludedPaths = service.getExcludedPaths(); expect(excludedPaths).toContain('/health'); expect(excludedPaths).toContain('/metrics'); }); it('should return configuration', () => { const config = service.getConfig(); expect(config.enabled).toBe(true); expect(config.security.maskSensitiveData).toBe(true); expect(config.performance.slowRequestThreshold).toBe(1000); }); }); describe('metrics reset', () => { it('should reset metrics to initial state', () => { const requestContext = { id: 'req-1', startTime: Date.now(), method: 'GET', url: '/api/test', headers: {}, query: {}, body: null, userAgent: 'test', ip: '127.0.0.1' }; const responseContext = { statusCode: 200, headers: {}, body: null, size: 0, endTime: Date.now() + 100, duration: 100 }; service.trackRequest(requestContext, responseContext, null); let metrics = service.getMetrics(); expect(metrics.totalRequests).toBe(1); service.resetMetrics(); metrics = service.getMetrics(); expect(metrics.totalRequests).toBe(0); expect(metrics.averageResponseTime).toBe(0); expect(Object.keys(metrics.statusCodeDistribution)).toHaveLength(0); expect(Object.keys(metrics.methodDistribution)).toHaveLength(0); }); }); }); //# sourceMappingURL=request-watcher.service.spec.js.map