UNPKG

@ahmedhegazee/nestjs-telescope

Version:

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

457 lines 20.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const testing_1 = require("@nestjs/testing"); const query_watcher_interceptor_1 = require("./query-watcher.interceptor"); const query_watcher_service_1 = require("./query-watcher.service"); const typeorm_1 = require("typeorm"); describe('QueryWatcherInterceptor', () => { let interceptor; let queryWatcherService; let dataSource; beforeEach(async () => { const mockQueryWatcherService = { trackQuery: jest.fn(), getConfig: jest.fn().mockReturnValue({ enabled: true }), }; const mockDataSource = { createQueryRunner: jest.fn(), createQueryBuilder: jest.fn(), getRepository: jest.fn(), manager: { createQueryBuilder: jest.fn(), getRepository: jest.fn(), } }; const module = await testing_1.Test.createTestingModule({ providers: [ query_watcher_interceptor_1.QueryWatcherInterceptor, { provide: query_watcher_service_1.QueryWatcherService, useValue: mockQueryWatcherService, }, { provide: typeorm_1.DataSource, useValue: mockDataSource, }, ], }).compile(); interceptor = module.get(query_watcher_interceptor_1.QueryWatcherInterceptor); queryWatcherService = module.get(query_watcher_service_1.QueryWatcherService); dataSource = module.get(typeorm_1.DataSource); }); afterEach(() => { jest.clearAllMocks(); }); describe('initialization', () => { it('should be defined', () => { expect(interceptor).toBeDefined(); }); it('should setup interception when enabled', async () => { await interceptor.setupInterception(); expect(dataSource.createQueryRunner).toHaveBeenCalled(); }); it('should not setup interception when disabled', async () => { queryWatcherService.getConfig.mockReturnValue({ enabled: false, priority: 1, tags: ['query'], dependencies: ['typeorm'], slowQueryThreshold: 1000, verySlowQueryThreshold: 5000, enableStackTrace: true, enableQueryAnalysis: true, enableOptimizationHints: true, maxQueryLength: 10000, excludeQueries: ['SELECT 1'], sampleRate: 100, connectionPoolMonitoring: true }); await interceptor.setupInterception(); expect(dataSource.createQueryRunner).not.toHaveBeenCalled(); }); }); describe('QueryRunner interception', () => { let mockQueryRunner; let originalQuery; beforeEach(() => { originalQuery = jest.fn(); mockQueryRunner = { query: originalQuery, connection: { options: { type: 'postgres', host: 'localhost', port: 5432, database: 'test_db' } } }; dataSource.createQueryRunner.mockReturnValue(mockQueryRunner); }); it('should intercept query execution', async () => { await interceptor.setupInterception(); const testSql = 'SELECT * FROM users WHERE id = $1'; const testParams = [1]; const testResult = [{ id: 1, name: 'John' }]; originalQuery.mockResolvedValue(testResult); const result = await mockQueryRunner.query(testSql, testParams); expect(result).toEqual(testResult); expect(originalQuery).toHaveBeenCalledWith(testSql, testParams); expect(queryWatcherService.trackQuery).toHaveBeenCalledWith(expect.objectContaining({ sql: testSql, parameters: testParams, operation: 'select', resultCount: 1 })); }); it('should handle query errors', async () => { await interceptor.setupInterception(); const testSql = 'SELECT * FROM nonexistent_table'; const testError = new Error('Table does not exist'); originalQuery.mockRejectedValue(testError); try { await mockQueryRunner.query(testSql); } catch (error) { expect(error).toBe(testError); } expect(queryWatcherService.trackQuery).toHaveBeenCalledWith(expect.objectContaining({ sql: testSql, error: testError, operation: 'select' })); }); it('should detect different SQL operations', async () => { await interceptor.setupInterception(); const testCases = [ { sql: 'SELECT * FROM users', expectedOperation: 'select' }, { sql: 'INSERT INTO users (name) VALUES ($1)', expectedOperation: 'insert' }, { sql: 'UPDATE users SET name = $1 WHERE id = $2', expectedOperation: 'update' }, { sql: 'DELETE FROM users WHERE id = $1', expectedOperation: 'delete' }, { sql: 'SHOW TABLES', expectedOperation: 'raw' } ]; originalQuery.mockResolvedValue([]); for (const testCase of testCases) { await mockQueryRunner.query(testCase.sql); expect(queryWatcherService.trackQuery).toHaveBeenCalledWith(expect.objectContaining({ sql: testCase.sql, operation: testCase.expectedOperation })); } }); it('should extract table names from queries', async () => { await interceptor.setupInterception(); const testCases = [ { sql: 'SELECT * FROM users', expectedTable: 'users' }, { sql: 'INSERT INTO posts (title) VALUES ($1)', expectedTable: 'posts' }, { sql: 'UPDATE comments SET content = $1 WHERE id = $2', expectedTable: 'comments' }, { sql: 'DELETE FROM sessions WHERE expired_at < NOW()', expectedTable: 'sessions' } ]; originalQuery.mockResolvedValue([]); for (const testCase of testCases) { await mockQueryRunner.query(testCase.sql); expect(queryWatcherService.trackQuery).toHaveBeenCalledWith(expect.objectContaining({ sql: testCase.sql, tableName: testCase.expectedTable })); } }); it('should capture stack traces', async () => { await interceptor.setupInterception(); originalQuery.mockResolvedValue([]); await mockQueryRunner.query('SELECT 1'); expect(queryWatcherService.trackQuery).toHaveBeenCalledWith(expect.objectContaining({ stackTrace: expect.any(String) })); }); it('should measure query duration', async () => { await interceptor.setupInterception(); originalQuery.mockImplementation(() => new Promise(resolve => setTimeout(() => resolve([]), 100))); const startTime = Date.now(); await mockQueryRunner.query('SELECT 1'); const endTime = Date.now(); expect(queryWatcherService.trackQuery).toHaveBeenCalledWith(expect.objectContaining({ duration: expect.any(Number), startTime: expect.any(Number), endTime: expect.any(Number) })); const trackedQuery = queryWatcherService.trackQuery.mock.calls[0][0]; expect(trackedQuery.duration).toBeGreaterThan(0); expect(trackedQuery.startTime).toBeLessThanOrEqual(startTime); expect(trackedQuery.endTime).toBeGreaterThanOrEqual(endTime); }); }); describe('SelectQueryBuilder interception', () => { let mockQueryBuilder; let originalGetMany; let originalGetOne; let originalGetRawMany; beforeEach(() => { originalGetMany = jest.fn(); originalGetOne = jest.fn(); originalGetRawMany = jest.fn(); mockQueryBuilder = { getMany: originalGetMany, getOne: originalGetOne, getRawMany: originalGetRawMany, getSql: jest.fn().mockReturnValue('SELECT * FROM users'), getParameters: jest.fn().mockReturnValue({}), expressionMap: { mainAlias: { metadata: { tableName: 'users' } } } }; dataSource.createQueryBuilder.mockReturnValue(mockQueryBuilder); }); it('should intercept getMany calls', async () => { await interceptor.setupInterception(); const testResult = [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }]; originalGetMany.mockResolvedValue(testResult); const result = await mockQueryBuilder.getMany(); expect(result).toEqual(testResult); expect(queryWatcherService.trackQuery).toHaveBeenCalledWith(expect.objectContaining({ sql: 'SELECT * FROM users', operation: 'select', tableName: 'users', resultCount: 2 })); }); it('should intercept getOne calls', async () => { await interceptor.setupInterception(); const testResult = { id: 1, name: 'John' }; originalGetOne.mockResolvedValue(testResult); const result = await mockQueryBuilder.getOne(); expect(result).toEqual(testResult); expect(queryWatcherService.trackQuery).toHaveBeenCalledWith(expect.objectContaining({ sql: 'SELECT * FROM users', operation: 'select', tableName: 'users', resultCount: 1 })); }); it('should intercept getRawMany calls', async () => { await interceptor.setupInterception(); const testResult = [{ user_id: 1, user_name: 'John' }]; originalGetRawMany.mockResolvedValue(testResult); const result = await mockQueryBuilder.getRawMany(); expect(result).toEqual(testResult); expect(queryWatcherService.trackQuery).toHaveBeenCalledWith(expect.objectContaining({ sql: 'SELECT * FROM users', operation: 'select', resultCount: 1 })); }); it('should handle QueryBuilder errors', async () => { await interceptor.setupInterception(); const testError = new Error('Query failed'); originalGetMany.mockRejectedValue(testError); try { await mockQueryBuilder.getMany(); } catch (error) { expect(error).toBe(testError); } expect(queryWatcherService.trackQuery).toHaveBeenCalledWith(expect.objectContaining({ error: testError, operation: 'select' })); }); }); describe('Repository interception', () => { let mockRepository; let originalFind; let originalFindOne; let originalSave; beforeEach(() => { originalFind = jest.fn(); originalFindOne = jest.fn(); originalSave = jest.fn(); mockRepository = { find: originalFind, findOne: originalFindOne, save: originalSave, metadata: { tableName: 'users' } }; dataSource.getRepository.mockReturnValue(mockRepository); }); it('should intercept repository find calls', async () => { await interceptor.setupInterception(); const testResult = [{ id: 1, name: 'John' }]; originalFind.mockResolvedValue(testResult); const result = await mockRepository.find(); expect(result).toEqual(testResult); expect(queryWatcherService.trackQuery).toHaveBeenCalledWith(expect.objectContaining({ operation: 'select', tableName: 'users', resultCount: 1 })); }); it('should intercept repository findOne calls', async () => { await interceptor.setupInterception(); const testResult = { id: 1, name: 'John' }; originalFindOne.mockResolvedValue(testResult); const result = await mockRepository.findOne({ where: { id: 1 } }); expect(result).toEqual(testResult); expect(queryWatcherService.trackQuery).toHaveBeenCalledWith(expect.objectContaining({ operation: 'select', tableName: 'users', resultCount: 1 })); }); it('should intercept repository save calls', async () => { await interceptor.setupInterception(); const testEntity = { id: 1, name: 'John' }; originalSave.mockResolvedValue(testEntity); const result = await mockRepository.save(testEntity); expect(result).toEqual(testEntity); expect(queryWatcherService.trackQuery).toHaveBeenCalledWith(expect.objectContaining({ operation: 'insert', tableName: 'users', resultCount: 1 })); }); it('should handle repository errors', async () => { await interceptor.setupInterception(); const testError = new Error('Repository operation failed'); originalFind.mockRejectedValue(testError); try { await mockRepository.find(); } catch (error) { expect(error).toBe(testError); } expect(queryWatcherService.trackQuery).toHaveBeenCalledWith(expect.objectContaining({ error: testError, operation: 'select' })); }); }); describe('utility methods', () => { it('should generate unique query IDs', () => { const id1 = interceptor.generateQueryId(); const id2 = interceptor.generateQueryId(); expect(id1).toBeDefined(); expect(id2).toBeDefined(); expect(id1).not.toBe(id2); expect(id1).toMatch(/^query_\d+_[a-z0-9]+$/); }); it('should detect SQL operations correctly', () => { const testCases = [ { sql: 'SELECT * FROM users', expected: 'select' }, { sql: ' select id from posts ', expected: 'select' }, { sql: 'INSERT INTO users (name) VALUES ($1)', expected: 'insert' }, { sql: 'UPDATE users SET name = $1', expected: 'update' }, { sql: 'DELETE FROM users WHERE id = $1', expected: 'delete' }, { sql: 'SHOW TABLES', expected: 'raw' }, { sql: 'EXPLAIN SELECT * FROM users', expected: 'raw' }, { sql: 'CREATE TABLE test (id INT)', expected: 'raw' }, { sql: '', expected: 'raw' } ]; for (const testCase of testCases) { const result = interceptor.detectOperation(testCase.sql); expect(result).toBe(testCase.expected); } }); it('should extract table names correctly', () => { const testCases = [ { sql: 'SELECT * FROM users', expected: 'users' }, { sql: 'INSERT INTO posts (title) VALUES ($1)', expected: 'posts' }, { sql: 'UPDATE comments SET content = $1 WHERE id = $2', expected: 'comments' }, { sql: 'DELETE FROM sessions WHERE expired_at < NOW()', expected: 'sessions' }, { sql: 'SELECT * FROM users u JOIN posts p ON u.id = p.user_id', expected: 'users' }, { sql: 'select id from `table_with_backticks`', expected: 'table_with_backticks' }, { sql: 'SELECT * FROM "quoted_table"', expected: 'quoted_table' }, { sql: 'SHOW TABLES', expected: undefined }, { sql: 'invalid sql', expected: undefined } ]; for (const testCase of testCases) { const result = interceptor.extractTableName(testCase.sql); expect(result).toBe(testCase.expected); } }); it('should extract connection information', () => { const mockQueryRunner = { connection: { options: { type: 'postgres', host: 'localhost', port: 5432, database: 'test_db' } } }; const connectionInfo = interceptor.extractConnectionInfo(mockQueryRunner); expect(connectionInfo).toEqual({ type: 'postgres', host: 'localhost', port: 5432, database: 'test_db' }); }); it('should handle missing connection information', () => { const mockQueryRunner = { connection: { options: {} } }; const connectionInfo = interceptor.extractConnectionInfo(mockQueryRunner); expect(connectionInfo).toEqual({ type: undefined, host: undefined, port: undefined, database: undefined }); }); it('should count results correctly', () => { const testCases = [ { result: [], expected: 0 }, { result: [{ id: 1 }], expected: 1 }, { result: [{ id: 1 }, { id: 2 }, { id: 3 }], expected: 3 }, { result: { id: 1 }, expected: 1 }, { result: null, expected: 0 }, { result: undefined, expected: 0 }, { result: 'string', expected: 0 }, { result: 42, expected: 0 } ]; for (const testCase of testCases) { const result = interceptor.countResults(testCase.result); expect(result).toBe(testCase.expected); } }); it('should capture stack traces', () => { const stackTrace = interceptor.captureStackTrace(); expect(stackTrace).toBeDefined(); expect(typeof stackTrace).toBe('string'); expect(stackTrace).toContain('QueryWatcherInterceptor'); }); }); describe('error handling', () => { it('should handle interception setup errors gracefully', async () => { dataSource.createQueryRunner.mockImplementation(() => { throw new Error('Cannot create query runner'); }); await expect(interceptor.setupInterception()).resolves.not.toThrow(); }); it('should handle missing DataSource gracefully', async () => { const interceptorWithoutDataSource = new query_watcher_interceptor_1.QueryWatcherInterceptor(queryWatcherService, null); await expect(interceptorWithoutDataSource.setupInterception()).resolves.not.toThrow(); }); it('should handle malformed SQL gracefully', async () => { await interceptor.setupInterception(); const mockQueryRunner = { query: jest.fn().mockResolvedValue([]), connection: { options: { type: 'postgres' } } }; dataSource.createQueryRunner.mockReturnValue(mockQueryRunner); await mockQueryRunner.query('INVALID SQL STATEMENT'); expect(queryWatcherService.trackQuery).toHaveBeenCalledWith(expect.objectContaining({ sql: 'INVALID SQL STATEMENT', operation: 'raw' })); }); }); }); //# sourceMappingURL=query-watcher.interceptor.spec.js.map