Files
wwjcloud-nest-v1/wwjcloud/test/database/performance-monitor.service.spec.ts
万物街 1cd5d3bdef feat: 完成 NestJS 后端核心底座开发 (M1-M6) 和 Ant Design Vue 前端迁移
主要更新:
1. 后端核心底座完成 (M1-M6):
   - 健康检查、指标监控、分布式锁
   - 事件总线、队列系统、事务管理
   - 安全守卫、多租户隔离、存储适配器
   - 审计日志、配置管理、多语言支持

2. 前端迁移到 Ant Design Vue:
   - 从 Element Plus 迁移到 Ant Design Vue
   - 完善 system 模块 (role/menu/dept)
   - 修复依赖和配置问题

3. 文档完善:
   - AI 开发工作流文档
   - 架构约束和开发规范
   - 项目进度跟踪

4. 其他改进:
   - 修复编译错误和类型问题
   - 完善测试用例
   - 优化项目结构
2025-08-27 11:24:22 +08:00

335 lines
11 KiB
TypeScript

import { Test, TestingModule } from '@nestjs/testing';
import { DataSource } from 'typeorm';
import { PerformanceMonitorService } from '../../src/core/database/performance-monitor.service';
/**
* 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>(PerformanceMonitorService);
dataSource = module.get<DataSource>(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();
});
});
});