UNPKG

@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
"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