@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
JavaScript
;
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