import { Test, TestingModule } from '@nestjs/testing'; import { DataSource } from 'typeorm'; import { PerformanceMonitorService } from '../../src/core/database/performanceMonitorService'; /** * PerformanceMonitorService 单元测试 * 测试数据库性能监控服务的核心功能 */ describe('PerformanceMonitorService', () => { let service: PerformanceMonitorService; let dataSource: DataSource; let mockQueryRunner: any; beforeEach(async () => { // 创建模拟的查询运行器 mockQueryRunner = { query: jest.fn(), release: jest.fn(), }; // 创建模拟的数据源 const mockDataSource = { createQueryRunner: jest.fn().mockReturnValue(mockQueryRunner), query: jest.fn(), }; const module: TestingModule = await Test.createTestingModule({ providers: [ PerformanceMonitorService, { provide: DataSource, useValue: mockDataSource, }, ], }).compile(); service = module.get(PerformanceMonitorService); dataSource = module.get(DataSource); }); afterEach(() => { jest.clearAllMocks(); }); it('should be defined', () => { expect(service).toBeDefined(); }); describe('checkSlowQueries', () => { it('should return slow queries', async () => { const mockSlowQueries = [ { sql_text: 'SELECT * FROM member WHERE status = 1', exec_count: 100, avg_timer_wait: 5000000000, // 5秒 sum_timer_wait: 500000000000, sum_rows_examined: 10000, sum_rows_sent: 1000, }, ]; mockQueryRunner.query.mockResolvedValue(mockSlowQueries); const result = await service.checkSlowQueries(); expect(result).toEqual(mockSlowQueries); expect(mockQueryRunner.query).toHaveBeenCalledWith( expect.stringContaining( 'performance_schema.events_statements_summary_by_digest', ), ); }); it('should handle errors when checking slow queries', async () => { mockQueryRunner.query.mockRejectedValue(new Error('Query failed')); const result = await service.checkSlowQueries(); expect(result).toEqual([]); }); }); describe('checkTableSizes', () => { it('should return table sizes', async () => { const mockTableSizes = [ { table_name: 'member', size_mb: 150.5, rows: 50000, avg_row_length: 3200, data_length: 157286400, index_length: 52428800, }, ]; mockQueryRunner.query.mockResolvedValue(mockTableSizes); const result = await service.checkTableSizes(); expect(result).toEqual(mockTableSizes); expect(mockQueryRunner.query).toHaveBeenCalledWith( expect.stringContaining('information_schema.tables'), ); }); it('should handle errors when checking table sizes', async () => { mockQueryRunner.query.mockRejectedValue(new Error('Query failed')); const result = await service.checkTableSizes(); expect(result).toEqual([]); }); }); describe('checkIndexEfficiency', () => { it('should return index efficiency data', async () => { const mockIndexEfficiency = [ { table_name: 'member', index_name: 'idx_member_status', cardinality: 5, selectivity: 0.2, usage_count: 1000, efficiency_score: 75.5, }, ]; mockQueryRunner.query.mockResolvedValue(mockIndexEfficiency); const result = await service.checkIndexEfficiency(); expect(result).toEqual(mockIndexEfficiency); expect(mockQueryRunner.query).toHaveBeenCalledWith( expect.stringContaining('information_schema.statistics'), ); }); it('should handle errors when checking index efficiency', async () => { mockQueryRunner.query.mockRejectedValue(new Error('Query failed')); const result = await service.checkIndexEfficiency(); expect(result).toEqual([]); }); }); describe('getQueryExecutionPlan', () => { it('should return query execution plan', async () => { const mockExecutionPlan = [ { id: 1, select_type: 'SIMPLE', table: 'member', partitions: null, type: 'ref', possible_keys: 'idx_member_status', key: 'idx_member_status', key_len: '4', ref: 'const', rows: 1000, filtered: 100.0, Extra: 'Using index condition', }, ]; mockQueryRunner.query.mockResolvedValue(mockExecutionPlan); const sql = 'SELECT * FROM member WHERE status = 1'; const result = await service.getQueryExecutionPlan(sql); expect(result).toEqual(mockExecutionPlan); expect(mockQueryRunner.query).toHaveBeenCalledWith(`EXPLAIN ${sql}`); }); it('should handle errors when getting execution plan', async () => { mockQueryRunner.query.mockRejectedValue(new Error('Query failed')); const sql = 'SELECT * FROM member WHERE status = 1'; const result = await service.getQueryExecutionPlan(sql); expect(result).toEqual([]); }); }); describe('analyzeQueryPerformance', () => { it('should analyze query performance', async () => { const mockExecutionPlan = [ { id: 1, select_type: 'SIMPLE', table: 'member', type: 'ref', possible_keys: 'idx_member_status', key: 'idx_member_status', rows: 1000, filtered: 100.0, Extra: 'Using index condition', }, ]; jest .spyOn(service, 'getQueryExecutionPlan') .mockResolvedValue(mockExecutionPlan); const sql = 'SELECT * FROM member WHERE status = 1'; const result = await service.analyzeQueryPerformance(sql); expect(result).toHaveProperty('executionPlan'); expect(result).toHaveProperty('analysis'); expect(result).toHaveProperty('recommendations'); expect(result.executionPlan).toEqual(mockExecutionPlan); expect(result.analysis.estimatedRows).toBe(1000); expect(result.analysis.usesIndex).toBe(true); }); it('should detect full table scan', async () => { const mockExecutionPlan = [ { id: 1, select_type: 'SIMPLE', table: 'member', type: 'ALL', possible_keys: null, key: null, rows: 50000, filtered: 10.0, Extra: 'Using where', }, ]; jest .spyOn(service, 'getQueryExecutionPlan') .mockResolvedValue(mockExecutionPlan); const sql = 'SELECT * FROM member WHERE name LIKE "%test%"'; const result = await service.analyzeQueryPerformance(sql); expect(result.analysis.hasFullTableScan).toBe(true); expect(result.analysis.usesIndex).toBe(false); expect(result.recommendations).toContain( '查询执行了全表扫描,建议添加适当的索引', ); }); }); describe('getConnectionStatus', () => { it('should return connection status', async () => { const mockConnectionStatus = [ { Variable_name: 'Threads_connected', Value: '25' }, { Variable_name: 'Max_connections', Value: '151' }, { Variable_name: 'Threads_running', Value: '5' }, { Variable_name: 'Aborted_connects', Value: '10' }, ]; mockQueryRunner.query.mockResolvedValue(mockConnectionStatus); const result = await service.getConnectionStatus(); expect(result).toHaveProperty('threadsConnected'); expect(result).toHaveProperty('maxConnections'); expect(result).toHaveProperty('connectionUsage'); expect(result.threadsConnected).toBe(25); expect(result.maxConnections).toBe(151); expect(result.connectionUsage).toBeCloseTo(16.56, 1); }); it('should handle errors when getting connection status', async () => { mockQueryRunner.query.mockRejectedValue(new Error('Query failed')); const result = await service.getConnectionStatus(); expect(result).toEqual({}); }); }); describe('getPerformanceMetrics', () => { it('should return performance metrics', async () => { const mockMetrics = [ { Variable_name: 'Innodb_buffer_pool_read_requests', Value: '1000000' }, { Variable_name: 'Innodb_buffer_pool_reads', Value: '50000' }, { Variable_name: 'Slow_queries', Value: '100' }, { Variable_name: 'Questions', Value: '500000' }, { Variable_name: 'Uptime', Value: '86400' }, { Variable_name: 'Threads_created', Value: '200' }, { Variable_name: 'Connections', Value: '10000' }, ]; mockQueryRunner.query.mockResolvedValue(mockMetrics); const result = await service.getPerformanceMetrics(); expect(result).toHaveProperty('buffer_pool_hit_rate'); expect(result).toHaveProperty('slow_query_rate'); expect(result).toHaveProperty('qps'); expect(result).toHaveProperty('thread_cache_hit_rate'); expect(result.buffer_pool_hit_rate).toBeCloseTo(95, 0); expect(result.slow_query_rate).toBeCloseTo(0.02, 2); }); it('should handle errors when getting performance metrics', async () => { mockQueryRunner.query.mockRejectedValue(new Error('Query failed')); const result = await service.getPerformanceMetrics(); expect(result).toEqual({}); }); }); describe('performanceCheck', () => { it('should perform complete performance check', async () => { const checkSlowQueriesSpy = jest .spyOn(service, 'checkSlowQueries') .mockResolvedValue([]); const checkTableSizesSpy = jest .spyOn(service, 'checkTableSizes') .mockResolvedValue([]); const checkIndexEfficiencySpy = jest .spyOn(service, 'checkIndexEfficiency') .mockResolvedValue([]); const getConnectionStatusSpy = jest .spyOn(service, 'getConnectionStatus') .mockResolvedValue({}); const getPerformanceMetricsSpy = jest .spyOn(service, 'getPerformanceMetrics') .mockResolvedValue({}); await service.performanceCheck(); expect(checkSlowQueriesSpy).toHaveBeenCalled(); expect(checkTableSizesSpy).toHaveBeenCalled(); expect(checkIndexEfficiencySpy).toHaveBeenCalled(); expect(getConnectionStatusSpy).toHaveBeenCalled(); expect(getPerformanceMetricsSpy).toHaveBeenCalled(); checkSlowQueriesSpy.mockRestore(); checkTableSizesSpy.mockRestore(); checkIndexEfficiencySpy.mockRestore(); getConnectionStatusSpy.mockRestore(); getPerformanceMetricsSpy.mockRestore(); }); it('should handle errors during performance check', async () => { jest .spyOn(service, 'checkSlowQueries') .mockRejectedValue(new Error('Check failed')); jest.spyOn(service, 'checkTableSizes').mockResolvedValue([]); jest.spyOn(service, 'checkIndexEfficiency').mockResolvedValue([]); jest.spyOn(service, 'getConnectionStatus').mockResolvedValue({}); jest.spyOn(service, 'getPerformanceMetrics').mockResolvedValue({}); // 应该不抛出异常 await expect(service.performanceCheck()).resolves.not.toThrow(); }); }); });