UNPKG

@ahmedhegazee/nestjs-telescope

Version:

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

372 lines 15.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const testing_1 = require("@nestjs/testing"); const query_analyzer_service_1 = require("./query-analyzer.service"); const typeorm_1 = require("typeorm"); describe('QueryAnalyzerService', () => { let service; let dataSource; beforeEach(async () => { const mockDataSource = { options: { type: 'postgres' }, query: jest.fn() }; const module = await testing_1.Test.createTestingModule({ providers: [ query_analyzer_service_1.QueryAnalyzerService, { provide: typeorm_1.DataSource, useValue: mockDataSource, }, ], }).compile(); service = module.get(query_analyzer_service_1.QueryAnalyzerService); dataSource = module.get(typeorm_1.DataSource); }); afterEach(() => { jest.clearAllMocks(); service.clearPatterns(); }); describe('query analysis', () => { it('should analyze slow query and identify issues', async () => { const context = { id: 'query-1', sql: 'SELECT * FROM users WHERE UPPER(name) = $1', parameters: ['JOHN'], startTime: Date.now(), duration: 2000, operation: 'select', tableName: 'users' }; const analysis = await service.analyzeQuery(context); expect(analysis.queryId).toBe('query-1'); expect(analysis.duration).toBe(2000); expect(analysis.severity).toBe('slow'); expect(analysis.issues).toHaveLength(2); expect(analysis.issues).toContainEqual(expect.objectContaining({ type: 'full_table_scan', severity: 'medium', description: expect.stringContaining('SELECT *') })); expect(analysis.issues).toContainEqual(expect.objectContaining({ type: 'missing_index', severity: 'medium', description: expect.stringContaining('functions in WHERE clause') })); }); it('should detect missing WHERE clause', async () => { const context = { id: 'query-2', sql: 'SELECT id, name FROM users', parameters: [], startTime: Date.now(), duration: 3000, operation: 'select', tableName: 'users' }; const analysis = await service.analyzeQuery(context); expect(analysis.issues).toContainEqual(expect.objectContaining({ type: 'full_table_scan', severity: 'high', description: expect.stringContaining('lacks WHERE clause') })); }); it('should detect excessive JOINs', async () => { const context = { id: 'query-3', sql: `SELECT u.name, p.title, c.content, t.name, cat.name, auth.name FROM users u JOIN posts p ON u.id = p.user_id JOIN comments c ON p.id = c.post_id JOIN tags t ON p.id = t.post_id JOIN categories cat ON p.category_id = cat.id JOIN authors auth ON p.author_id = auth.id JOIN publishers pub ON auth.publisher_id = pub.id`, parameters: [], startTime: Date.now(), duration: 4000, operation: 'select', tableName: 'users' }; const analysis = await service.analyzeQuery(context); expect(analysis.issues).toContainEqual(expect.objectContaining({ type: 'excessive_joins', severity: 'high', description: expect.stringContaining('6 joins') })); }); it('should detect IN subquery that could be optimized', async () => { const context = { id: 'query-4', sql: 'SELECT * FROM users WHERE id IN (SELECT user_id FROM posts WHERE published = true)', parameters: [], startTime: Date.now(), duration: 1500, operation: 'select', tableName: 'users' }; const analysis = await service.analyzeQuery(context); expect(analysis.issues).toContainEqual(expect.objectContaining({ type: 'subquery_performance', severity: 'medium', description: expect.stringContaining('IN with subquery') })); }); it('should detect LIKE with leading wildcard', async () => { const context = { id: 'query-5', sql: 'SELECT * FROM users WHERE name LIKE \'%john%\'', parameters: [], startTime: Date.now(), duration: 1200, operation: 'select', tableName: 'users' }; const analysis = await service.analyzeQuery(context); expect(analysis.issues).toContainEqual(expect.objectContaining({ type: 'missing_index', severity: 'medium', description: expect.stringContaining('LIKE with leading wildcard') })); }); it('should detect OR conditions in WHERE clause', async () => { const context = { id: 'query-6', sql: 'SELECT * FROM users WHERE name = $1 OR email = $2', parameters: ['john', 'john@example.com'], startTime: Date.now(), duration: 1800, operation: 'select', tableName: 'users' }; const analysis = await service.analyzeQuery(context); expect(analysis.issues).toContainEqual(expect.objectContaining({ type: 'missing_index', severity: 'medium', description: expect.stringContaining('OR conditions') })); }); }); describe('optimization hints', () => { it('should generate index suggestion for slow queries', async () => { const context = { id: 'query-7', sql: 'SELECT * FROM users WHERE created_at > $1 AND status = $2', parameters: ['2023-01-01', 'active'], startTime: Date.now(), duration: 2000, operation: 'select', tableName: 'users' }; const analysis = await service.analyzeQuery(context); expect(analysis.optimizationHints).toContainEqual(expect.objectContaining({ type: 'index_suggestion', priority: 'high', description: expect.stringContaining('database indexes'), estimatedImpact: expect.stringContaining('50-95%') })); }); it('should generate query rewrite suggestion for subqueries', async () => { const context = { id: 'query-8', sql: 'SELECT * FROM users WHERE id IN (SELECT user_id FROM posts)', parameters: [], startTime: Date.now(), duration: 1500, operation: 'select', tableName: 'users' }; const analysis = await service.analyzeQuery(context); expect(analysis.optimizationHints).toContainEqual(expect.objectContaining({ type: 'query_rewrite', priority: 'medium', description: expect.stringContaining('Rewrite subqueries as JOINs') })); }); it('should generate caching suggestion for frequent queries', async () => { const sql = 'SELECT * FROM users WHERE status = $1'; for (let i = 0; i < 12; i++) { const context = { id: `query-${i}`, sql, parameters: ['active'], startTime: Date.now(), duration: 500, operation: 'select', tableName: 'users' }; await service.analyzeQuery(context); } const context = { id: 'query-final', sql, parameters: ['active'], startTime: Date.now(), duration: 500, operation: 'select', tableName: 'users' }; const analysis = await service.analyzeQuery(context); expect(analysis.optimizationHints).toContainEqual(expect.objectContaining({ type: 'caching_opportunity', priority: 'medium', description: expect.stringContaining('frequently executed query') })); }); }); describe('execution plan analysis', () => { it('should analyze PostgreSQL execution plan', async () => { const mockExecutionPlan = [ { 'QUERY PLAN': [ { 'Plan': { 'Node Type': 'Seq Scan', 'Relation Name': 'users', 'Total Cost': 15000, 'Actual Rows': 50000, 'Filter': 'name = $1' } } ] } ]; dataSource.query.mockResolvedValue(mockExecutionPlan); const context = { id: 'query-plan', sql: 'SELECT * FROM users WHERE name = $1', parameters: ['john'], startTime: Date.now(), duration: 3000, operation: 'select', tableName: 'users' }; const analysis = await service.analyzeQuery(context); expect(analysis.executionPlan).toBeDefined(); expect(analysis.executionPlan.totalCost).toBe(15000); expect(analysis.issues).toContainEqual(expect.objectContaining({ type: 'full_table_scan', severity: 'high', description: expect.stringContaining('high execution cost') })); }); it('should handle execution plan failures gracefully', async () => { dataSource.query.mockRejectedValue(new Error('Permission denied')); const context = { id: 'query-plan-fail', sql: 'SELECT * FROM users', parameters: [], startTime: Date.now(), duration: 1000, operation: 'select', tableName: 'users' }; const analysis = await service.analyzeQuery(context); expect(analysis.executionPlan).toBeUndefined(); expect(analysis.issues).toHaveLength(2); }); }); describe('query patterns', () => { it('should track query patterns', async () => { const queries = [ 'SELECT * FROM users WHERE id = $1', 'SELECT * FROM users WHERE id = $1', 'SELECT * FROM posts WHERE user_id = $1', 'SELECT * FROM users WHERE id = $1' ]; for (let i = 0; i < queries.length; i++) { const context = { id: `query-${i}`, sql: queries[i], parameters: [i + 1], startTime: Date.now(), duration: 100, operation: 'select' }; await service.analyzeQuery(context); } const patterns = service.getQueryPatterns(); expect(patterns).toHaveLength(2); const userPattern = patterns.find(p => p.pattern.includes('users')); expect(userPattern).toBeDefined(); expect(userPattern.count).toBe(3); }); it('should identify slow query patterns', async () => { const queries = [ { sql: 'SELECT * FROM users WHERE name LIKE $1', duration: 2000 }, { sql: 'SELECT * FROM users WHERE name LIKE $1', duration: 1800 }, { sql: 'SELECT * FROM posts WHERE title LIKE $1', duration: 500 }, ]; for (let i = 0; i < queries.length; i++) { const context = { id: `query-${i}`, sql: queries[i].sql, parameters: [`%test${i}%`], startTime: Date.now(), duration: queries[i].duration, operation: 'select' }; await service.analyzeQuery(context); } const slowPatterns = service.getSlowQueryPatterns(); expect(slowPatterns).toHaveLength(1); expect(slowPatterns[0].averageDuration).toBe(1900); }); }); describe('severity determination', () => { it('should determine correct severity levels', async () => { const testCases = [ { duration: 800, expectedSeverity: 'slow' }, { duration: 3000, expectedSeverity: 'slow' }, { duration: 7000, expectedSeverity: 'very_slow' }, { duration: 15000, expectedSeverity: 'critical' } ]; for (const testCase of testCases) { const context = { id: `query-${testCase.duration}`, sql: 'SELECT * FROM users', parameters: [], startTime: Date.now(), duration: testCase.duration, operation: 'select' }; const analysis = await service.analyzeQuery(context); expect(analysis.severity).toBe(testCase.expectedSeverity); } }); }); describe('optimization suggestions', () => { it('should return optimization suggestions', async () => { const context = { id: 'query-opt', sql: 'SELECT * FROM users WHERE name = $1', parameters: ['john'], startTime: Date.now(), duration: 2000, operation: 'select', tableName: 'users' }; await service.analyzeQuery(context); const suggestions = service.getOptimizationSuggestions(); expect(suggestions).toBeInstanceOf(Array); }); it('should clear patterns and suggestions', async () => { const context = { id: 'query-clear', sql: 'SELECT * FROM users', parameters: [], startTime: Date.now(), duration: 1000, operation: 'select' }; await service.analyzeQuery(context); expect(service.getQueryPatterns()).toHaveLength(1); service.clearPatterns(); expect(service.getQueryPatterns()).toHaveLength(0); expect(service.getOptimizationSuggestions()).toHaveLength(0); }); }); }); //# sourceMappingURL=query-analyzer.service.spec.js.map