import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { Repository, DataSource } from 'typeorm'; import { IndexManagerService } from '../../src/core/database/indexManagerService'; /** * IndexManagerService 单元测试 * 测试数据库索引管理服务的核心功能 */ describe('IndexManagerService', () => { let service: IndexManagerService; 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: [ IndexManagerService, { provide: DataSource, useValue: mockDataSource, }, ], }).compile(); service = module.get(IndexManagerService); dataSource = module.get(DataSource); }); afterEach(() => { jest.clearAllMocks(); }); it('should be defined', () => { expect(service).toBeDefined(); }); describe('checkIndexExists', () => { it('should return true when index exists', async () => { // 模拟索引存在的查询结果 mockQueryRunner.query.mockResolvedValue([{ count: 1 }]); const result = await service.checkIndexExists('test_table', 'test_index'); expect(result).toBe(true); expect(mockQueryRunner.query).toHaveBeenCalledWith( expect.stringContaining('SHOW INDEX FROM test_table'), ); }); it('should return false when index does not exist', async () => { // 模拟索引不存在的查询结果 mockQueryRunner.query.mockResolvedValue([]); const result = await service.checkIndexExists('test_table', 'test_index'); expect(result).toBe(false); }); it('should handle database errors gracefully', async () => { // 模拟数据库错误 mockQueryRunner.query.mockRejectedValue(new Error('Database error')); const result = await service.checkIndexExists('test_table', 'test_index'); expect(result).toBe(false); }); }); describe('createIndex', () => { it('should create single column index successfully', async () => { mockQueryRunner.query.mockResolvedValue(undefined); await service.createIndex('test_table', 'test_index', ['column1']); expect(mockQueryRunner.query).toHaveBeenCalledWith( 'CREATE INDEX test_index ON test_table (column1)', ); }); it('should create composite index successfully', async () => { mockQueryRunner.query.mockResolvedValue(undefined); await service.createIndex('test_table', 'test_composite_index', [ 'column1', 'column2', ]); expect(mockQueryRunner.query).toHaveBeenCalledWith( 'CREATE INDEX test_composite_index ON test_table (column1, column2)', ); }); it('should handle index creation errors gracefully', async () => { mockQueryRunner.query.mockRejectedValue( new Error('Index creation failed'), ); // 应该不抛出异常,而是记录日志 await expect( service.createIndex('test_table', 'test_index', ['column1']), ).resolves.not.toThrow(); }); }); describe('getTableIndexes', () => { it('should return table indexes', async () => { const mockIndexes = [ { Table: 'test_table', Non_unique: 0, Key_name: 'PRIMARY', Seq_in_index: 1, Column_name: 'id', Collation: 'A', Cardinality: 1000, Sub_part: null, Packed: null, Null: '', Index_type: 'BTREE', Comment: '', }, ]; mockQueryRunner.query.mockResolvedValue(mockIndexes); const result = await service.getTableIndexes('test_table'); expect(result).toEqual(mockIndexes); expect(mockQueryRunner.query).toHaveBeenCalledWith( 'SHOW INDEX FROM test_table', ); }); it('should handle errors when getting table indexes', async () => { mockQueryRunner.query.mockRejectedValue(new Error('Query failed')); const result = await service.getTableIndexes('test_table'); expect(result).toEqual([]); }); }); describe('analyzeTable', () => { it('should analyze table successfully', async () => { mockQueryRunner.query.mockResolvedValue(undefined); await service.analyzeTable('test_table'); expect(mockQueryRunner.query).toHaveBeenCalledWith( 'ANALYZE TABLE test_table', ); }); it('should handle analyze table errors gracefully', async () => { mockQueryRunner.query.mockRejectedValue(new Error('Analyze failed')); await expect(service.analyzeTable('test_table')).resolves.not.toThrow(); }); }); describe('getIndexUsageStats', () => { it('should return index usage statistics', async () => { const mockStats = [ { table_schema: 'test_db', table_name: 'test_table', index_name: 'test_index', count_read: 100, sum_timer_read: 1000000, }, ]; mockQueryRunner.query.mockResolvedValue(mockStats); const result = await service.getIndexUsageStats(); expect(result).toEqual(mockStats); expect(mockQueryRunner.query).toHaveBeenCalledWith( expect.stringContaining( 'performance_schema.table_io_waits_summary_by_index_usage', ), ); }); it('should handle errors when getting index usage stats', async () => { mockQueryRunner.query.mockRejectedValue(new Error('Query failed')); const result = await service.getIndexUsageStats(); expect(result).toEqual([]); }); }); describe('checkAndCreateIndexes', () => { it('should check and create all required indexes', async () => { // 模拟所有索引都不存在 mockQueryRunner.query.mockResolvedValue([]); const createIndexSpy = jest .spyOn(service, 'createIndex') .mockResolvedValue(undefined); const checkIndexSpy = jest .spyOn(service, 'checkIndexExists') .mockResolvedValue(false); await service.checkAndCreateIndexes(); // 验证检查了所有必要的索引 expect(checkIndexSpy).toHaveBeenCalledTimes(expect.any(Number)); expect(createIndexSpy).toHaveBeenCalledTimes(expect.any(Number)); createIndexSpy.mockRestore(); checkIndexSpy.mockRestore(); }); it('should skip creating existing indexes', async () => { const createIndexSpy = jest .spyOn(service, 'createIndex') .mockResolvedValue(undefined); const checkIndexSpy = jest .spyOn(service, 'checkIndexExists') .mockResolvedValue(true); await service.checkAndCreateIndexes(); // 如果所有索引都存在,则不应该创建任何索引 expect(createIndexSpy).not.toHaveBeenCalled(); createIndexSpy.mockRestore(); checkIndexSpy.mockRestore(); }); }); describe('analyzeHotTables', () => { it('should analyze all hot tables', async () => { const analyzeTableSpy = jest .spyOn(service, 'analyzeTable') .mockResolvedValue(undefined); await service.analyzeHotTables(); // 验证分析了所有热点表 expect(analyzeTableSpy).toHaveBeenCalledWith('member'); expect(analyzeTableSpy).toHaveBeenCalledWith('member_account_log'); expect(analyzeTableSpy).toHaveBeenCalledWith('pay'); expect(analyzeTableSpy).toHaveBeenCalledWith('pay_refund'); analyzeTableSpy.mockRestore(); }); }); });