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