265 lines
7.7 KiB
TypeScript
265 lines
7.7 KiB
TypeScript
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>(IndexManagerService);
|
|
dataSource = module.get<DataSource>(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();
|
|
});
|
|
});
|
|
});
|