UNPKG

@ahmedhegazee/nestjs-telescope

Version:

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

399 lines 17.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const testing_1 = require("@nestjs/testing"); const connection_pool_monitor_service_1 = require("./connection-pool-monitor.service"); const typeorm_1 = require("typeorm"); describe('ConnectionPoolMonitorService', () => { let service; let dataSource; beforeEach(async () => { const mockPool = { config: { max: 10, min: 2 }, acquiredCount: 3, idleCount: 2, waitingCount: 0, on: jest.fn() }; const mockDataSource = { options: { type: 'postgres' }, driver: { pool: mockPool } }; const module = await testing_1.Test.createTestingModule({ providers: [ connection_pool_monitor_service_1.ConnectionPoolMonitorService, { provide: typeorm_1.DataSource, useValue: mockDataSource, }, ], }).compile(); service = module.get(connection_pool_monitor_service_1.ConnectionPoolMonitorService); dataSource = module.get(typeorm_1.DataSource); }); afterEach(() => { jest.clearAllMocks(); service.resetMetrics(); }); describe('initialization', () => { it('should be defined', () => { expect(service).toBeDefined(); }); it('should initialize with default metrics', () => { const metrics = service.getMetrics(); expect(metrics.totalConnections).toBe(0); expect(metrics.activeConnections).toBe(0); expect(metrics.healthScore).toBe(100); }); it('should setup monitoring on module init', async () => { await service.onModuleInit(); expect(dataSource.driver.pool.on).toHaveBeenCalled(); }); }); describe('metrics collection', () => { it('should collect basic pool metrics', () => { const pool = dataSource.driver.pool; pool.acquiredCount = 3; pool.idleCount = 2; pool.waitingCount = 0; service.collectMetrics(); const metrics = service.getMetrics(); expect(metrics.activeConnections).toBe(3); expect(metrics.idleConnections).toBe(2); expect(metrics.waitingConnections).toBe(0); expect(metrics.totalConnections).toBe(5); }); it('should handle missing pool gracefully', () => { dataSource.driver.pool = null; expect(() => service.collectMetrics()).not.toThrow(); const metrics = service.getMetrics(); expect(metrics.totalConnections).toBe(0); }); }); describe('event handling', () => { it('should handle connection acquire events', () => { const pool = dataSource.driver.pool; const mockClient = { id: 'test-client' }; service.setupPostgresPoolListeners(pool); const acquireHandler = pool.on.mock.calls.find(call => call[0] === 'acquire')?.[1]; expect(acquireHandler).toBeDefined(); acquireHandler(mockClient); const recentEvents = service.getRecentEvents(10); expect(recentEvents).toHaveLength(1); expect(recentEvents[0].type).toBe('acquire'); }); it('should handle connection release events', () => { const pool = dataSource.driver.pool; const mockClient = { id: 'test-client' }; service.setupPostgresPoolListeners(pool); const releaseHandler = pool.on.mock.calls.find(call => call[0] === 'release')?.[1]; expect(releaseHandler).toBeDefined(); releaseHandler(mockClient); const recentEvents = service.getRecentEvents(10); expect(recentEvents).toHaveLength(1); expect(recentEvents[0].type).toBe('release'); }); it('should handle connection error events', () => { const pool = dataSource.driver.pool; const mockClient = { id: 'test-client' }; const mockError = new Error('Connection failed'); service.setupPostgresPoolListeners(pool); const errorHandler = pool.on.mock.calls.find(call => call[0] === 'error')?.[1]; expect(errorHandler).toBeDefined(); errorHandler(mockError, mockClient); const recentEvents = service.getRecentEvents(10); expect(recentEvents).toHaveLength(1); expect(recentEvents[0].type).toBe('error'); expect(recentEvents[0].error).toBe('Connection failed'); }); it('should handle pools without event system', () => { const poolWithoutEvents = { config: { max: 10, min: 2 }, acquiredCount: 0, idleCount: 0, waitingCount: 0 }; expect(() => service.setupPoolEventListeners(poolWithoutEvents)).not.toThrow(); }); }); describe('health score calculation', () => { it('should calculate health score based on utilization', () => { const pool = dataSource.driver.pool; pool.acquiredCount = 9; pool.idleCount = 1; pool.waitingCount = 0; service.collectMetrics(); const metrics = service.getMetrics(); expect(metrics.healthScore).toBeLessThan(100); expect(metrics.healthScore).toBeGreaterThan(50); }); it('should penalize waiting connections', () => { const pool = dataSource.driver.pool; pool.acquiredCount = 5; pool.idleCount = 0; pool.waitingCount = 3; service.collectMetrics(); const metrics = service.getMetrics(); expect(metrics.healthScore).toBeLessThan(85); }); it('should penalize recent errors', () => { const pool = dataSource.driver.pool; const errorEvent = { type: 'error', timestamp: new Date(), error: 'Connection failed', poolState: { active: 5, idle: 2, waiting: 0 } }; service.recordEvent(errorEvent); service.collectMetrics(); const metrics = service.getMetrics(); expect(metrics.healthScore).toBeLessThan(95); }); }); describe('alert system', () => { it('should generate high usage alerts', (done) => { const pool = dataSource.driver.pool; pool.acquiredCount = 9; pool.idleCount = 1; pool.waitingCount = 0; service.getAlertStream().subscribe(alert => { expect(alert.type).toBe('high_usage'); expect(alert.severity).toBe('critical'); expect(alert.message).toContain('90.0%'); done(); }); service.collectMetrics(); service.checkForAlerts(); }); it('should generate pool exhausted alerts', (done) => { const pool = dataSource.driver.pool; pool.acquiredCount = 10; pool.idleCount = 0; pool.waitingCount = 3; pool.config.max = 10; service.getAlertStream().subscribe(alert => { if (alert.type === 'pool_exhausted') { expect(alert.severity).toBe('high'); expect(alert.message).toContain('3 connections waiting'); done(); } }); service.collectMetrics(); service.checkForAlerts(); }); it('should generate connection error alerts', (done) => { const now = Date.now(); for (let i = 0; i < 3; i++) { const errorEvent = { type: 'error', timestamp: new Date(now - i * 1000), error: 'Connection failed', poolState: { active: 5, idle: 2, waiting: 0 } }; service.recordEvent(errorEvent); } service.getAlertStream().subscribe(alert => { expect(alert.type).toBe('connection_error'); expect(alert.severity).toBe('medium'); expect(alert.message).toContain('3 connection errors'); done(); }); service.checkForAlerts(); }); it('should generate connection leak alerts', (done) => { const longTime = Date.now() - 400000; service.connectionCreationTimes.set('conn1', longTime); service.connectionCreationTimes.set('conn2', longTime); service.getAlertStream().subscribe(alert => { expect(alert.type).toBe('connection_leak'); expect(alert.severity).toBe('medium'); expect(alert.message).toContain('2 connections have been active'); done(); }); service.checkForAlerts(); }); }); describe('database driver support', () => { it('should support PostgreSQL pools', () => { const postgresPool = { config: { max: 10, min: 2 }, acquiredCount: 3, idleCount: 2, waitingCount: 0, on: jest.fn() }; expect(() => service.setupPostgresPoolListeners(postgresPool)).not.toThrow(); expect(postgresPool.on).toHaveBeenCalledWith('connect', expect.any(Function)); expect(postgresPool.on).toHaveBeenCalledWith('acquire', expect.any(Function)); expect(postgresPool.on).toHaveBeenCalledWith('release', expect.any(Function)); expect(postgresPool.on).toHaveBeenCalledWith('error', expect.any(Function)); }); it('should support MySQL pools', () => { const mysqlPool = { config: { max: 10, min: 2 }, acquiredCount: 3, idleCount: 2, waitingCount: 0, on: jest.fn() }; expect(() => service.setupMySQLPoolListeners(mysqlPool)).not.toThrow(); expect(mysqlPool.on).toHaveBeenCalledWith('connection', expect.any(Function)); expect(mysqlPool.on).toHaveBeenCalledWith('acquire', expect.any(Function)); expect(mysqlPool.on).toHaveBeenCalledWith('release', expect.any(Function)); expect(mysqlPool.on).toHaveBeenCalledWith('error', expect.any(Function)); }); it('should handle unsupported database types gracefully', () => { dataSource.options.type = 'sqlite'; expect(() => service.setupPoolEventListeners({})).not.toThrow(); }); }); describe('streams and observables', () => { it('should provide metrics stream', (done) => { const metricsStream = service.getMetricsStream(); let updateCount = 0; metricsStream.subscribe(metrics => { updateCount++; expect(metrics).toBeDefined(); expect(metrics.lastUpdate).toBeInstanceOf(Date); if (updateCount === 1) { done(); } }); service.collectMetrics(); }); it('should provide event stream', (done) => { const eventStream = service.getEventStream(); eventStream.subscribe(event => { expect(event.type).toBe('acquire'); expect(event.timestamp).toBeInstanceOf(Date); done(); }); const testEvent = { type: 'acquire', timestamp: new Date(), connectionId: 'test-conn', poolState: { active: 1, idle: 1, waiting: 0 } }; service.recordEvent(testEvent); }); }); describe('connection pool health', () => { it('should return healthy status for good metrics', () => { const pool = dataSource.driver.pool; pool.acquiredCount = 3; pool.idleCount = 5; pool.waitingCount = 0; pool.config.max = 10; service.collectMetrics(); const health = service.getConnectionPoolHealth(); expect(health.status).toBe('healthy'); expect(health.score).toBeGreaterThan(70); expect(health.issues.length).toBeGreaterThanOrEqual(0); }); it('should return warning status for degraded performance', () => { const pool = dataSource.driver.pool; pool.acquiredCount = 8; pool.idleCount = 2; pool.waitingCount = 0; pool.config.max = 10; service.collectMetrics(); const health = service.getConnectionPoolHealth(); expect(health.status).toBe('warning'); expect(health.score).toBeLessThan(100); expect(health.issues.length).toBeGreaterThan(0); }); it('should return critical status for severe issues', () => { const pool = dataSource.driver.pool; pool.acquiredCount = 10; pool.idleCount = 0; pool.waitingCount = 5; pool.config.max = 10; const errorEvent = { type: 'error', timestamp: new Date(), error: 'Connection failed', poolState: { active: 10, idle: 0, waiting: 5 } }; service.recordEvent(errorEvent); service.collectMetrics(); const health = service.getConnectionPoolHealth(); expect(health.status).toBe('critical'); expect(health.score).toBeLessThan(80); expect(health.issues.length).toBeGreaterThan(0); expect(health.recommendations.length).toBeGreaterThan(0); }); }); describe('cleanup and resource management', () => { it('should limit event history', () => { const maxEvents = 1000; for (let i = 0; i < maxEvents + 100; i++) { const event = { type: 'acquire', timestamp: new Date(), connectionId: `conn-${i}`, poolState: { active: 1, idle: 1, waiting: 0 } }; service.recordEvent(event); } const recentEvents = service.getRecentEvents(maxEvents + 100); expect(recentEvents.length).toBeLessThanOrEqual(maxEvents); }); it('should reset metrics and cleanup', () => { const event = { type: 'acquire', timestamp: new Date(), connectionId: 'test-conn', poolState: { active: 1, idle: 1, waiting: 0 } }; service.recordEvent(event); service.acquireTimestamps.set('test-conn', Date.now()); service.connectionCreationTimes.set('test-conn', Date.now()); expect(service.getRecentEvents()).toHaveLength(1); expect(service.acquireTimestamps.size).toBe(1); expect(service.connectionCreationTimes.size).toBe(1); service.resetMetrics(); expect(service.getRecentEvents()).toHaveLength(0); expect(service.acquireTimestamps.size).toBe(0); expect(service.connectionCreationTimes.size).toBe(0); }); it('should cleanup on module destroy', async () => { await service.onModuleDestroy(); expect(service.destroy$.isStopped).toBe(true); }); }); describe('average calculations', () => { it('should calculate average acquire time', () => { const now = Date.now(); const connections = ['conn1', 'conn2', 'conn3']; connections.forEach((connId, i) => { const acquireTime = now - (i + 1) * 1000; service.acquireTimestamps.set(connId, acquireTime); const releaseEvent = { type: 'release', timestamp: new Date(now), connectionId: connId, duration: (i + 1) * 1000, poolState: { active: 1, idle: 1, waiting: 0 } }; service.recordEvent(releaseEvent); }); const avgTime = service.calculateAverageAcquireTime(); expect(avgTime).toBe(2000); }); it('should calculate average connection lifetime', () => { const now = Date.now(); service.connectionCreationTimes.set('conn1', now - 30000); service.connectionCreationTimes.set('conn2', now - 60000); service.connectionCreationTimes.set('conn3', now - 90000); const avgLifetime = service.calculateAverageConnectionLifetime(); expect(avgLifetime).toBe(60000); }); }); }); //# sourceMappingURL=connection-pool-monitor.service.spec.js.map