@ahmedhegazee/nestjs-telescope
Version:
Advanced observability and monitoring solution for NestJS applications with ML-powered analytics, enterprise features, and production-ready scaling
406 lines • 19 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const testing_1 = require("@nestjs/testing");
const query_watcher_module_1 = require("./query-watcher.module");
const query_watcher_service_1 = require("./query-watcher.service");
const query_watcher_interceptor_1 = require("./query-watcher.interceptor");
const query_analyzer_service_1 = require("./query-analyzer.service");
const connection_pool_monitor_service_1 = require("./connection-pool-monitor.service");
const query_metrics_service_1 = require("./query-metrics.service");
const telescope_service_1 = require("../../core/services/telescope.service");
const typeorm_1 = require("typeorm");
describe('QueryWatcherModule', () => {
let module;
let queryWatcherService;
let queryWatcherInterceptor;
let queryAnalyzerService;
let connectionPoolMonitorService;
let queryMetricsService;
const mockTelescopeConfig = {
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: false,
customWatchers: true
},
watchers: {
query: {
enabled: true,
priority: 1,
tags: ['query', 'database'],
dependencies: ['typeorm']
}
}
};
const mockQueryWatcherConfig = {
enabled: true,
priority: 1,
tags: ['query', 'database'],
dependencies: ['typeorm'],
slowQueryThreshold: 1000,
verySlowQueryThreshold: 5000,
enableStackTrace: true,
enableQueryAnalysis: true,
enableOptimizationHints: true,
maxQueryLength: 10000,
excludeQueries: ['SELECT 1'],
sampleRate: 100,
connectionPoolMonitoring: true
};
beforeEach(async () => {
const mockTelescopeService = {
record: jest.fn(),
};
const mockDataSource = {
options: {
type: 'postgres'
},
createQueryRunner: jest.fn(),
createQueryBuilder: jest.fn(),
getRepository: jest.fn(),
manager: {
createQueryBuilder: jest.fn(),
getRepository: jest.fn(),
},
driver: {
pool: {
config: { max: 10, min: 2 },
acquiredCount: 0,
idleCount: 0,
waitingCount: 0,
on: jest.fn()
}
}
};
const queryWatcherModule = query_watcher_module_1.QueryWatcherModule.forRoot(mockQueryWatcherConfig);
module = await testing_1.Test.createTestingModule({
providers: [
query_watcher_service_1.QueryWatcherService,
query_watcher_interceptor_1.QueryWatcherInterceptor,
query_analyzer_service_1.QueryAnalyzerService,
connection_pool_monitor_service_1.ConnectionPoolMonitorService,
query_metrics_service_1.QueryMetricsService,
{
provide: telescope_service_1.TelescopeService,
useValue: mockTelescopeService,
},
{
provide: 'TELESCOPE_CONFIG',
useValue: mockTelescopeConfig,
},
{
provide: 'QUERY_WATCHER_CONFIG',
useValue: mockQueryWatcherConfig,
},
{
provide: typeorm_1.DataSource,
useValue: mockDataSource,
},
],
}).compile();
queryWatcherService = module.get(query_watcher_service_1.QueryWatcherService);
queryWatcherInterceptor = module.get(query_watcher_interceptor_1.QueryWatcherInterceptor);
queryAnalyzerService = module.get(query_analyzer_service_1.QueryAnalyzerService);
connectionPoolMonitorService = module.get(connection_pool_monitor_service_1.ConnectionPoolMonitorService);
queryMetricsService = module.get(query_metrics_service_1.QueryMetricsService);
});
afterEach(async () => {
if (module) {
await module.close();
}
});
describe('module configuration', () => {
it('should create module with forRoot configuration', () => {
const dynamicModule = query_watcher_module_1.QueryWatcherModule.forRoot(mockQueryWatcherConfig);
expect(dynamicModule.module).toBe(query_watcher_module_1.QueryWatcherModule);
expect(dynamicModule.providers).toContain(query_watcher_service_1.QueryWatcherService);
expect(dynamicModule.providers).toContain(query_watcher_interceptor_1.QueryWatcherInterceptor);
expect(dynamicModule.providers).toContain(query_analyzer_service_1.QueryAnalyzerService);
expect(dynamicModule.providers).toContain(connection_pool_monitor_service_1.ConnectionPoolMonitorService);
expect(dynamicModule.providers).toContain(query_metrics_service_1.QueryMetricsService);
});
it('should export all query watcher services', () => {
const dynamicModule = query_watcher_module_1.QueryWatcherModule.forRoot(mockQueryWatcherConfig);
expect(dynamicModule.exports).toContain(query_watcher_service_1.QueryWatcherService);
expect(dynamicModule.exports).toContain(query_analyzer_service_1.QueryAnalyzerService);
expect(dynamicModule.exports).toContain(connection_pool_monitor_service_1.ConnectionPoolMonitorService);
expect(dynamicModule.exports).toContain(query_metrics_service_1.QueryMetricsService);
});
});
describe('service instantiation', () => {
it('should create QueryWatcherService', () => {
expect(queryWatcherService).toBeDefined();
expect(queryWatcherService).toBeInstanceOf(query_watcher_service_1.QueryWatcherService);
});
it('should create QueryWatcherInterceptor', () => {
expect(queryWatcherInterceptor).toBeDefined();
expect(queryWatcherInterceptor).toBeInstanceOf(query_watcher_interceptor_1.QueryWatcherInterceptor);
});
it('should create QueryAnalyzerService', () => {
expect(queryAnalyzerService).toBeDefined();
expect(queryAnalyzerService).toBeInstanceOf(query_analyzer_service_1.QueryAnalyzerService);
});
it('should create ConnectionPoolMonitorService', () => {
expect(connectionPoolMonitorService).toBeDefined();
expect(connectionPoolMonitorService).toBeInstanceOf(connection_pool_monitor_service_1.ConnectionPoolMonitorService);
});
it('should create QueryMetricsService', () => {
expect(queryMetricsService).toBeDefined();
expect(queryMetricsService).toBeInstanceOf(query_metrics_service_1.QueryMetricsService);
});
});
describe('module initialization', () => {
it('should initialize query interception on module init', async () => {
const setupInterceptionSpy = jest.spyOn(queryWatcherInterceptor, 'setupInterception');
setupInterceptionSpy.mockResolvedValue();
const moduleInstance = module.get(query_watcher_module_1.QueryWatcherModule);
await moduleInstance.onModuleInit();
expect(setupInterceptionSpy).toHaveBeenCalled();
});
it('should handle initialization errors gracefully', async () => {
const setupInterceptionSpy = jest.spyOn(queryWatcherInterceptor, 'setupInterception');
setupInterceptionSpy.mockRejectedValue(new Error('Initialization failed'));
const moduleInstance = module.get(query_watcher_module_1.QueryWatcherModule);
await expect(moduleInstance.onModuleInit()).resolves.not.toThrow();
});
});
describe('service integration', () => {
it('should allow QueryWatcherService to track queries', () => {
expect(queryWatcherService.trackQuery).toBeDefined();
expect(typeof queryWatcherService.trackQuery).toBe('function');
});
it('should allow QueryAnalyzerService to analyze queries', async () => {
const testContext = {
id: 'test-query',
sql: 'SELECT * FROM users',
parameters: [],
startTime: Date.now(),
duration: 100,
operation: 'select'
};
const analysis = await queryAnalyzerService.analyzeQuery(testContext);
expect(analysis).toBeDefined();
expect(analysis.queryId).toBe('test-query');
});
it('should allow ConnectionPoolMonitorService to collect metrics', () => {
const metrics = connectionPoolMonitorService.getMetrics();
expect(metrics).toBeDefined();
expect(typeof metrics.totalConnections).toBe('number');
expect(typeof metrics.healthScore).toBe('number');
});
it('should allow QueryMetricsService to record queries', () => {
const testContext = {
id: 'test-query',
sql: 'SELECT * FROM users',
parameters: [],
startTime: Date.now(),
duration: 100,
operation: 'select'
};
queryMetricsService.recordQuery(testContext);
const metrics = queryMetricsService.getMetrics();
expect(metrics.totalQueries).toBe(1);
});
});
describe('configuration propagation', () => {
it('should propagate configuration to QueryWatcherService', () => {
const config = queryWatcherService.getConfig();
expect(config.enabled).toBe(true);
expect(config.slowQueryThreshold).toBe(1000);
expect(config.verySlowQueryThreshold).toBe(5000);
expect(config.enableStackTrace).toBe(true);
expect(config.enableQueryAnalysis).toBe(true);
expect(config.enableOptimizationHints).toBe(true);
});
it('should handle missing configuration gracefully', async () => {
const moduleWithoutConfig = await testing_1.Test.createTestingModule({
providers: [
query_watcher_module_1.QueryWatcherModule,
query_watcher_service_1.QueryWatcherService,
query_watcher_interceptor_1.QueryWatcherInterceptor,
query_analyzer_service_1.QueryAnalyzerService,
connection_pool_monitor_service_1.ConnectionPoolMonitorService,
query_metrics_service_1.QueryMetricsService,
{
provide: telescope_service_1.TelescopeService,
useValue: { record: jest.fn() },
},
{
provide: typeorm_1.DataSource,
useValue: {
options: { type: 'postgres' },
createQueryRunner: jest.fn(),
driver: { pool: { config: {}, on: jest.fn() } }
},
},
],
}).compile();
const service = moduleWithoutConfig.get(query_watcher_service_1.QueryWatcherService);
expect(service).toBeDefined();
await moduleWithoutConfig.close();
});
});
describe('dependency injection', () => {
it('should inject TelescopeService into QueryWatcherService', () => {
expect(queryWatcherService).toBeDefined();
expect(queryWatcherService.trackQuery).toBeDefined();
});
it('should inject DataSource into QueryWatcherInterceptor', () => {
expect(queryWatcherInterceptor).toBeDefined();
expect(queryWatcherInterceptor.setupInterception).toBeDefined();
});
it('should inject DataSource into QueryAnalyzerService', () => {
expect(queryAnalyzerService).toBeDefined();
expect(queryAnalyzerService.analyzeQuery).toBeDefined();
});
it('should inject DataSource into ConnectionPoolMonitorService', () => {
expect(connectionPoolMonitorService).toBeDefined();
expect(connectionPoolMonitorService.getMetrics).toBeDefined();
});
});
describe('lifecycle management', () => {
it('should handle module initialization lifecycle', async () => {
const moduleInstance = module.get(query_watcher_module_1.QueryWatcherModule);
const setupSpy = jest.spyOn(queryWatcherInterceptor, 'setupInterception');
setupSpy.mockResolvedValue();
await expect(moduleInstance.onModuleInit()).resolves.not.toThrow();
expect(setupSpy).toHaveBeenCalled();
});
it('should handle connection pool monitoring initialization', async () => {
const onModuleInitSpy = jest.spyOn(connectionPoolMonitorService, 'onModuleInit');
onModuleInitSpy.mockResolvedValue();
await connectionPoolMonitorService.onModuleInit();
expect(onModuleInitSpy).toHaveBeenCalled();
});
it('should handle module destruction lifecycle', async () => {
const onModuleDestroySpy = jest.spyOn(connectionPoolMonitorService, 'onModuleDestroy');
onModuleDestroySpy.mockResolvedValue();
await connectionPoolMonitorService.onModuleDestroy();
expect(onModuleDestroySpy).toHaveBeenCalled();
});
});
describe('error handling', () => {
it('should handle service initialization errors', async () => {
const moduleWithBadConfig = await testing_1.Test.createTestingModule({
providers: [
query_watcher_module_1.QueryWatcherModule,
query_watcher_service_1.QueryWatcherService,
query_watcher_interceptor_1.QueryWatcherInterceptor,
query_analyzer_service_1.QueryAnalyzerService,
connection_pool_monitor_service_1.ConnectionPoolMonitorService,
query_metrics_service_1.QueryMetricsService,
{
provide: telescope_service_1.TelescopeService,
useValue: { record: jest.fn() },
},
{
provide: 'QUERY_WATCHER_CONFIG',
useValue: null,
},
{
provide: typeorm_1.DataSource,
useValue: {
options: { type: 'postgres' },
createQueryRunner: jest.fn(),
driver: { pool: { config: {}, on: jest.fn() } }
},
},
],
}).compile();
const service = moduleWithBadConfig.get(query_watcher_service_1.QueryWatcherService);
expect(service).toBeDefined();
await moduleWithBadConfig.close();
});
it('should handle missing DataSource gracefully', async () => {
const moduleWithoutDataSource = await testing_1.Test.createTestingModule({
providers: [
query_watcher_module_1.QueryWatcherModule,
query_watcher_service_1.QueryWatcherService,
query_watcher_interceptor_1.QueryWatcherInterceptor,
query_analyzer_service_1.QueryAnalyzerService,
connection_pool_monitor_service_1.ConnectionPoolMonitorService,
query_metrics_service_1.QueryMetricsService,
{
provide: telescope_service_1.TelescopeService,
useValue: { record: jest.fn() },
},
{
provide: 'QUERY_WATCHER_CONFIG',
useValue: mockQueryWatcherConfig,
},
],
}).compile();
const interceptor = moduleWithoutDataSource.get(query_watcher_interceptor_1.QueryWatcherInterceptor);
expect(interceptor).toBeDefined();
await moduleWithoutDataSource.close();
});
});
describe('service coordination', () => {
it('should coordinate between QueryWatcherService and QueryAnalyzerService', async () => {
const testContext = {
id: 'test-query',
sql: 'SELECT * FROM users WHERE name = $1',
parameters: ['John'],
startTime: Date.now(),
duration: 1500,
operation: 'select',
tableName: 'users'
};
queryWatcherService.trackQuery(testContext);
const analysis = await queryAnalyzerService.analyzeQuery(testContext);
expect(analysis).toBeDefined();
expect(analysis.queryId).toBe('test-query');
expect(analysis.severity).toBe('slow');
});
it('should coordinate between QueryWatcherService and QueryMetricsService', () => {
const testContext = {
id: 'test-query',
sql: 'SELECT * FROM users',
parameters: [],
startTime: Date.now(),
duration: 100,
operation: 'select',
tableName: 'users'
};
queryWatcherService.trackQuery(testContext);
queryMetricsService.recordQuery(testContext);
const metrics = queryMetricsService.getMetrics();
expect(metrics.totalQueries).toBe(1);
});
it('should coordinate between ConnectionPoolMonitorService and QueryMetricsService', () => {
const connectionMetrics = connectionPoolMonitorService.getMetrics();
queryMetricsService.updateConnectionPoolMetrics(connectionMetrics);
const queryMetrics = queryMetricsService.getMetrics();
expect(queryMetrics.connectionPoolMetrics).toBeDefined();
expect(queryMetrics.connectionPoolMetrics).toEqual(connectionMetrics);
});
});
});
//# sourceMappingURL=query-watcher.module.spec.js.map