UNPKG

@ahmedhegazee/nestjs-telescope

Version:

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

393 lines 16.8 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); const testing_1 = require("@nestjs/testing"); const exception_watcher_module_1 = require("./exception-watcher.module"); const exception_watcher_service_1 = require("./exception-watcher.service"); const telescope_service_1 = require("../../core/services/telescope.service"); const common_1 = require("@nestjs/common"); const request = require("supertest"); let TestController = class TestController { getSuccess() { return { message: 'success' }; } getHttpError() { throw new common_1.HttpException('Bad Request', common_1.HttpStatus.BAD_REQUEST); } getServerError() { throw new Error('Internal Server Error'); } getValidationError() { const error = new Error('Validation failed'); error.name = 'ValidationError'; throw error; } getDatabaseError() { const error = new Error('Database connection failed'); error.name = 'DatabaseError'; throw error; } }; __decorate([ (0, common_1.Get)('success'), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], TestController.prototype, "getSuccess", null); __decorate([ (0, common_1.Get)('http-error'), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], TestController.prototype, "getHttpError", null); __decorate([ (0, common_1.Get)('server-error'), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], TestController.prototype, "getServerError", null); __decorate([ (0, common_1.Get)('validation-error'), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], TestController.prototype, "getValidationError", null); __decorate([ (0, common_1.Get)('database-error'), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], TestController.prototype, "getDatabaseError", null); TestController = __decorate([ (0, common_1.Controller)('test') ], TestController); let TestModule = class TestModule { }; TestModule = __decorate([ (0, common_1.Module)({ controllers: [TestController], }) ], TestModule); describe('ExceptionWatcher Integration', () => { let app; let exceptionWatcherService; let telescopeService; beforeEach(async () => { const mockTelescopeService = { record: jest.fn(), getEntries: jest.fn(), clearEntries: jest.fn(), }; const module = await testing_1.Test.createTestingModule({ imports: [ exception_watcher_module_1.ExceptionWatcherModule.forRoot({ enabled: true, captureStackTrace: true, enableErrorClassification: true, groupSimilarErrors: true, enableRealTimeAlerts: true, captureHeaders: true, captureBody: false, captureParams: true, captureQuery: true, sampleRate: 100, }), TestModule, ], providers: [ { provide: telescope_service_1.TelescopeService, useValue: mockTelescopeService, }, ], }).compile(); app = module.createNestApplication(); exceptionWatcherService = module.get(exception_watcher_service_1.ExceptionWatcherService); telescopeService = module.get(telescope_service_1.TelescopeService); await app.init(); }); afterEach(async () => { await app.close(); }); describe('HTTP Exception Handling', () => { it('should track HTTP exceptions with full context', async () => { const response = await request(app.getHttpServer()) .get('/test/http-error') .expect(400); expect(response.body).toEqual({ statusCode: 400, timestamp: expect.any(String), path: '/test/http-error', message: 'Bad Request', error: 'Bad Request', traceId: undefined, requestId: undefined, }); expect(telescopeService.record).toHaveBeenCalledWith(expect.objectContaining({ type: 'exception', content: expect.objectContaining({ exception: expect.objectContaining({ type: 'HttpException', message: 'Bad Request', statusCode: 400, }), request: expect.objectContaining({ method: 'GET', url: '/test/http-error', path: '/test/http-error', }), response: expect.objectContaining({ statusCode: 400, }), }), })); }); it('should track server errors as 500', async () => { await request(app.getHttpServer()) .get('/test/server-error') .expect(500); expect(telescopeService.record).toHaveBeenCalledWith(expect.objectContaining({ type: 'exception', content: expect.objectContaining({ exception: expect.objectContaining({ type: 'Error', message: 'Internal Server Error', statusCode: 500, }), }), })); }); it('should capture request headers and parameters', async () => { await request(app.getHttpServer()) .get('/test/http-error?filter=test&sort=desc') .set('User-Agent', 'test-agent') .set('X-Custom-Header', 'custom-value') .expect(400); expect(telescopeService.record).toHaveBeenCalledWith(expect.objectContaining({ content: expect.objectContaining({ request: expect.objectContaining({ userAgent: 'test-agent', query: expect.objectContaining({ filter: 'test', sort: 'desc', }), headers: expect.objectContaining({ 'x-custom-header': 'custom-value', }), }), }), })); }); }); describe('Error Classification', () => { it('should classify validation errors correctly', async () => { await request(app.getHttpServer()) .get('/test/validation-error') .expect(500); expect(telescopeService.record).toHaveBeenCalledWith(expect.objectContaining({ content: expect.objectContaining({ exception: expect.objectContaining({ category: 'validation_error', severity: 'high', }), }), })); }); it('should classify database errors correctly', async () => { await request(app.getHttpServer()) .get('/test/database-error') .expect(500); expect(telescopeService.record).toHaveBeenCalledWith(expect.objectContaining({ content: expect.objectContaining({ exception: expect.objectContaining({ category: 'database_error', severity: 'high', }), }), })); }); }); describe('Error Grouping', () => { it('should group similar errors', async () => { await request(app.getHttpServer()).get('/test/database-error').expect(500); await request(app.getHttpServer()).get('/test/database-error').expect(500); await request(app.getHttpServer()).get('/test/database-error').expect(500); await new Promise(resolve => setTimeout(resolve, 100)); const groups = exceptionWatcherService.getExceptionGroups(); expect(groups).toHaveLength(1); expect(groups[0].count).toBe(3); expect(groups[0].errorType).toBe('Error'); expect(groups[0].errorMessage).toBe('Database connection failed'); }); }); describe('Metrics Collection', () => { it('should collect and update metrics', async () => { const initialMetrics = exceptionWatcherService.getMetrics(); expect(initialMetrics.totalExceptions).toBe(0); await request(app.getHttpServer()).get('/test/http-error').expect(400); await request(app.getHttpServer()).get('/test/server-error').expect(500); await request(app.getHttpServer()).get('/test/validation-error').expect(500); const updatedMetrics = exceptionWatcherService.getMetrics(); expect(updatedMetrics.totalExceptions).toBe(3); expect(updatedMetrics.uniqueExceptions).toBe(3); expect(updatedMetrics.highSeverityErrors).toBeGreaterThan(0); }); it('should track top errors', async () => { for (let i = 0; i < 5; i++) { await request(app.getHttpServer()).get('/test/database-error').expect(500); } await request(app.getHttpServer()).get('/test/validation-error').expect(500); await request(app.getHttpServer()).get('/test/validation-error').expect(500); const metrics = exceptionWatcherService.getMetrics(); expect(metrics.topErrors).toHaveLength(2); expect(metrics.topErrors[0].count).toBe(5); expect(metrics.topErrors[1].count).toBe(2); }); }); describe('Real-time Alerts', () => { it('should generate alerts for high error rates', (done) => { const alertConfig = { enabled: true, alertThresholds: { errorRate: 0.1, criticalErrors: 10, timeWindow: 60000, }, }; const alertModule = exception_watcher_module_1.ExceptionWatcherModule.forRoot(alertConfig); exceptionWatcherService.getAlertsStream().subscribe(alert => { expect(alert.type).toBe('error_rate'); expect(alert.severity).toBe('high'); done(); }); Promise.all([ request(app.getHttpServer()).get('/test/server-error'), request(app.getHttpServer()).get('/test/server-error'), request(app.getHttpServer()).get('/test/server-error'), ]); }); it('should generate alerts for new error types', (done) => { exceptionWatcherService.getAlertsStream().subscribe(alert => { if (alert.type === 'new_error') { expect(alert.severity).toBe('medium'); expect(alert.message).toContain('New error type detected'); done(); } }); request(app.getHttpServer()).get('/test/validation-error').expect(500); }); }); describe('Performance Impact', () => { it('should have minimal performance impact', async () => { const startTime = Date.now(); const requests = Array.from({ length: 100 }, () => request(app.getHttpServer()).get('/test/success').expect(200)); await Promise.all(requests); const endTime = Date.now(); const duration = endTime - startTime; expect(duration).toBeLessThan(5000); }); }); describe('Error Recovery', () => { it('should handle telescope service errors gracefully', async () => { telescopeService.record.mockImplementation(() => { throw new Error('Telescope service error'); }); await request(app.getHttpServer()) .get('/test/server-error') .expect(500); const response = await request(app.getHttpServer()) .get('/test/http-error') .expect(400); expect(response.body.statusCode).toBe(400); }); }); describe('Configuration', () => { it('should respect disabled configuration', async () => { const disabledApp = await testing_1.Test.createTestingModule({ imports: [ exception_watcher_module_1.ExceptionWatcherModule.forRoot({ enabled: false, }), TestModule, ], providers: [ { provide: telescope_service_1.TelescopeService, useValue: telescopeService, }, ], }).compile(); const testApp = disabledApp.createNestApplication(); await testApp.init(); telescopeService.record.mockClear(); await request(testApp.getHttpServer()) .get('/test/server-error') .expect(500); expect(telescopeService.record).not.toHaveBeenCalled(); await testApp.close(); }); it('should respect sampling rate', async () => { const samplingApp = await testing_1.Test.createTestingModule({ imports: [ exception_watcher_module_1.ExceptionWatcherModule.forRoot({ enabled: true, sampleRate: 0, }), TestModule, ], providers: [ { provide: telescope_service_1.TelescopeService, useValue: telescopeService, }, ], }).compile(); const testApp = samplingApp.createNestApplication(); await testApp.init(); telescopeService.record.mockClear(); await request(testApp.getHttpServer()) .get('/test/server-error') .expect(500); expect(telescopeService.record).not.toHaveBeenCalled(); await testApp.close(); }); }); describe('Async Configuration', () => { it('should support async configuration', async () => { const asyncApp = await testing_1.Test.createTestingModule({ imports: [ exception_watcher_module_1.ExceptionWatcherModule.forRootAsync({ useFactory: () => ({ enabled: true, captureStackTrace: true, enableErrorClassification: true, }), }), TestModule, ], providers: [ { provide: telescope_service_1.TelescopeService, useValue: telescopeService, }, ], }).compile(); const testApp = asyncApp.createNestApplication(); await testApp.init(); await request(testApp.getHttpServer()) .get('/test/server-error') .expect(500); expect(telescopeService.record).toHaveBeenCalled(); await testApp.close(); }); }); }); //# sourceMappingURL=exception-watcher.integration.spec.js.map