feat: 完成PHP到NestJS的100%功能迁移
- 迁移25个模块,包含95个控制器和160个服务 - 新增验证码管理、登录配置、云编译等模块 - 完善认证授权、会员管理、支付系统等核心功能 - 实现完整的队列系统、配置管理、监控体系 - 确保100%功能对齐和命名一致性 - 支持生产环境部署
This commit is contained in:
@@ -93,7 +93,10 @@ describe('IndexManagerService', () => {
|
||||
it('should create composite index successfully', async () => {
|
||||
mockQueryRunner.query.mockResolvedValue(undefined);
|
||||
|
||||
await service.createIndex('test_table', 'test_composite_index', ['column1', 'column2']);
|
||||
await service.createIndex('test_table', 'test_composite_index', [
|
||||
'column1',
|
||||
'column2',
|
||||
]);
|
||||
|
||||
expect(mockQueryRunner.query).toHaveBeenCalledWith(
|
||||
'CREATE INDEX test_composite_index ON test_table (column1, column2)',
|
||||
@@ -101,10 +104,14 @@ describe('IndexManagerService', () => {
|
||||
});
|
||||
|
||||
it('should handle index creation errors gracefully', async () => {
|
||||
mockQueryRunner.query.mockRejectedValue(new Error('Index creation failed'));
|
||||
mockQueryRunner.query.mockRejectedValue(
|
||||
new Error('Index creation failed'),
|
||||
);
|
||||
|
||||
// 应该不抛出异常,而是记录日志
|
||||
await expect(service.createIndex('test_table', 'test_index', ['column1'])).resolves.not.toThrow();
|
||||
await expect(
|
||||
service.createIndex('test_table', 'test_index', ['column1']),
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -182,7 +189,9 @@ describe('IndexManagerService', () => {
|
||||
|
||||
expect(result).toEqual(mockStats);
|
||||
expect(mockQueryRunner.query).toHaveBeenCalledWith(
|
||||
expect.stringContaining('performance_schema.table_io_waits_summary_by_index_usage'),
|
||||
expect.stringContaining(
|
||||
'performance_schema.table_io_waits_summary_by_index_usage',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -200,8 +209,12 @@ describe('IndexManagerService', () => {
|
||||
// 模拟所有索引都不存在
|
||||
mockQueryRunner.query.mockResolvedValue([]);
|
||||
|
||||
const createIndexSpy = jest.spyOn(service, 'createIndex').mockResolvedValue(undefined);
|
||||
const checkIndexSpy = jest.spyOn(service, 'checkIndexExists').mockResolvedValue(false);
|
||||
const createIndexSpy = jest
|
||||
.spyOn(service, 'createIndex')
|
||||
.mockResolvedValue(undefined);
|
||||
const checkIndexSpy = jest
|
||||
.spyOn(service, 'checkIndexExists')
|
||||
.mockResolvedValue(false);
|
||||
|
||||
await service.checkAndCreateIndexes();
|
||||
|
||||
@@ -214,8 +227,12 @@ describe('IndexManagerService', () => {
|
||||
});
|
||||
|
||||
it('should skip creating existing indexes', async () => {
|
||||
const createIndexSpy = jest.spyOn(service, 'createIndex').mockResolvedValue(undefined);
|
||||
const checkIndexSpy = jest.spyOn(service, 'checkIndexExists').mockResolvedValue(true);
|
||||
const createIndexSpy = jest
|
||||
.spyOn(service, 'createIndex')
|
||||
.mockResolvedValue(undefined);
|
||||
const checkIndexSpy = jest
|
||||
.spyOn(service, 'checkIndexExists')
|
||||
.mockResolvedValue(true);
|
||||
|
||||
await service.checkAndCreateIndexes();
|
||||
|
||||
@@ -229,7 +246,9 @@ describe('IndexManagerService', () => {
|
||||
|
||||
describe('analyzeHotTables', () => {
|
||||
it('should analyze all hot tables', async () => {
|
||||
const analyzeTableSpy = jest.spyOn(service, 'analyzeTable').mockResolvedValue(undefined);
|
||||
const analyzeTableSpy = jest
|
||||
.spyOn(service, 'analyzeTable')
|
||||
.mockResolvedValue(undefined);
|
||||
|
||||
await service.analyzeHotTables();
|
||||
|
||||
@@ -242,4 +261,4 @@ describe('IndexManagerService', () => {
|
||||
analyzeTableSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -65,7 +65,9 @@ describe('PerformanceMonitorService', () => {
|
||||
|
||||
expect(result).toEqual(mockSlowQueries);
|
||||
expect(mockQueryRunner.query).toHaveBeenCalledWith(
|
||||
expect.stringContaining('performance_schema.events_statements_summary_by_digest'),
|
||||
expect.stringContaining(
|
||||
'performance_schema.events_statements_summary_by_digest',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -196,7 +198,9 @@ describe('PerformanceMonitorService', () => {
|
||||
},
|
||||
];
|
||||
|
||||
jest.spyOn(service, 'getQueryExecutionPlan').mockResolvedValue(mockExecutionPlan);
|
||||
jest
|
||||
.spyOn(service, 'getQueryExecutionPlan')
|
||||
.mockResolvedValue(mockExecutionPlan);
|
||||
|
||||
const sql = 'SELECT * FROM member WHERE status = 1';
|
||||
const result = await service.analyzeQueryPerformance(sql);
|
||||
@@ -224,14 +228,18 @@ describe('PerformanceMonitorService', () => {
|
||||
},
|
||||
];
|
||||
|
||||
jest.spyOn(service, 'getQueryExecutionPlan').mockResolvedValue(mockExecutionPlan);
|
||||
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('查询执行了全表扫描,建议添加适当的索引');
|
||||
expect(result.recommendations).toContain(
|
||||
'查询执行了全表扫描,建议添加适当的索引',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -300,11 +308,21 @@ describe('PerformanceMonitorService', () => {
|
||||
|
||||
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({});
|
||||
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();
|
||||
|
||||
@@ -322,7 +340,9 @@ describe('PerformanceMonitorService', () => {
|
||||
});
|
||||
|
||||
it('should handle errors during performance check', async () => {
|
||||
jest.spyOn(service, 'checkSlowQueries').mockRejectedValue(new Error('Check failed'));
|
||||
jest
|
||||
.spyOn(service, 'checkSlowQueries')
|
||||
.mockRejectedValue(new Error('Check failed'));
|
||||
jest.spyOn(service, 'checkTableSizes').mockResolvedValue([]);
|
||||
jest.spyOn(service, 'checkIndexEfficiency').mockResolvedValue([]);
|
||||
jest.spyOn(service, 'getConnectionStatus').mockResolvedValue({});
|
||||
@@ -332,4 +352,4 @@ describe('PerformanceMonitorService', () => {
|
||||
await expect(service.performanceCheck()).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,8 +22,11 @@ describe('Queue System (e2e)', () => {
|
||||
await app.init();
|
||||
|
||||
testService = moduleFixture.get<TestService>(TestService);
|
||||
unifiedQueueService = moduleFixture.get<UnifiedQueueService>(UnifiedQueueService);
|
||||
databaseQueueProvider = moduleFixture.get<DatabaseQueueProvider>(DatabaseQueueProvider);
|
||||
unifiedQueueService =
|
||||
moduleFixture.get<UnifiedQueueService>(UnifiedQueueService);
|
||||
databaseQueueProvider = moduleFixture.get<DatabaseQueueProvider>(
|
||||
DatabaseQueueProvider,
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@@ -92,7 +95,7 @@ describe('Queue System (e2e)', () => {
|
||||
|
||||
it('should process task from queue', async () => {
|
||||
let processedData: any = null;
|
||||
|
||||
|
||||
await unifiedQueueService.processTask('test-queue', async (job: any) => {
|
||||
processedData = job.data;
|
||||
return { success: true };
|
||||
@@ -105,8 +108,8 @@ describe('Queue System (e2e)', () => {
|
||||
});
|
||||
|
||||
// Wait a bit for processing
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
expect(processedData).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -123,7 +126,9 @@ describe('Queue System (e2e)', () => {
|
||||
data: { test: 'event-data' },
|
||||
};
|
||||
|
||||
await expect(unifiedQueueService.publishEvent(event)).resolves.not.toThrow();
|
||||
await expect(
|
||||
unifiedQueueService.publishEvent(event),
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -143,7 +148,12 @@ describe('Queue System (e2e)', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const result = await databaseQueueProvider.add('test-db-queue', jobData.type, jobData.payload, jobData.options);
|
||||
const result = await databaseQueueProvider.add(
|
||||
'test-db-queue',
|
||||
jobData.type,
|
||||
jobData.payload,
|
||||
jobData.options,
|
||||
);
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -176,7 +186,7 @@ describe('Queue System (e2e)', () => {
|
||||
it('should handle complete queue workflow', async () => {
|
||||
// Test the complete workflow: add task -> process task -> publish event
|
||||
const taskData = { workflow: 'test', step: 1 };
|
||||
|
||||
|
||||
// Add task
|
||||
const taskResult = await unifiedQueueService.addTask('workflow-queue', {
|
||||
data: taskData,
|
||||
@@ -185,25 +195,28 @@ describe('Queue System (e2e)', () => {
|
||||
expect(taskResult).toBeDefined();
|
||||
|
||||
// Process task and publish event
|
||||
await unifiedQueueService.processTask('workflow-queue', async (job: any) => {
|
||||
const event = {
|
||||
eventType: 'workflow.completed',
|
||||
aggregateId: 'workflow-123',
|
||||
aggregateType: 'Workflow',
|
||||
version: '1.0',
|
||||
occurredAt: new Date().toISOString(),
|
||||
tenantId: 'tenant-1',
|
||||
idempotencyKey: 'workflow-key-123',
|
||||
traceId: 'workflow-trace-123',
|
||||
data: job.data,
|
||||
};
|
||||
await unifiedQueueService.processTask(
|
||||
'workflow-queue',
|
||||
async (job: any) => {
|
||||
const event = {
|
||||
eventType: 'workflow.completed',
|
||||
aggregateId: 'workflow-123',
|
||||
aggregateType: 'Workflow',
|
||||
version: '1.0',
|
||||
occurredAt: new Date().toISOString(),
|
||||
tenantId: 'tenant-1',
|
||||
idempotencyKey: 'workflow-key-123',
|
||||
traceId: 'workflow-trace-123',
|
||||
data: job.data,
|
||||
};
|
||||
|
||||
await unifiedQueueService.publishEvent(event);
|
||||
return { success: true, processed: job.data };
|
||||
});
|
||||
await unifiedQueueService.publishEvent(event);
|
||||
return { success: true, processed: job.data };
|
||||
},
|
||||
);
|
||||
|
||||
// Wait for processing
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
});
|
||||
|
||||
it('should handle error scenarios gracefully', async () => {
|
||||
@@ -223,10 +236,10 @@ describe('Queue System (e2e)', () => {
|
||||
});
|
||||
|
||||
// Wait for processing attempt
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
// The test passes if no unhandled errors are thrown
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,10 @@ import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { JobEntity } from '../../src/core/queue/entities/job.entity';
|
||||
import { EventEntity } from '../../src/core/queue/entities/event.entity';
|
||||
import { TASK_QUEUE_PROVIDER, EVENT_BUS_PROVIDER } from '../../src/core/interfaces/queue.interface';
|
||||
import {
|
||||
TASK_QUEUE_PROVIDER,
|
||||
EVENT_BUS_PROVIDER,
|
||||
} from '../../src/core/interfaces/queue.interface';
|
||||
|
||||
describe('Queue System Unit Tests', () => {
|
||||
let unifiedQueueService: UnifiedQueueService;
|
||||
@@ -69,7 +72,9 @@ describe('Queue System Unit Tests', () => {
|
||||
}).compile();
|
||||
|
||||
unifiedQueueService = module.get<UnifiedQueueService>(UnifiedQueueService);
|
||||
databaseQueueProvider = module.get<DatabaseQueueProvider>(DatabaseQueueProvider);
|
||||
databaseQueueProvider = module.get<DatabaseQueueProvider>(
|
||||
DatabaseQueueProvider,
|
||||
);
|
||||
});
|
||||
|
||||
describe('UnifiedQueueService', () => {
|
||||
@@ -170,11 +175,16 @@ describe('Queue System Unit Tests', () => {
|
||||
mockJobRepository.create.mockReturnValue(mockJob as any);
|
||||
mockJobRepository.save.mockResolvedValue(mockJob as any);
|
||||
|
||||
const result = await databaseQueueProvider.add('test-queue', 'test-job', { test: 'data' }, {
|
||||
priority: 1,
|
||||
delay: 0,
|
||||
attempts: 3,
|
||||
});
|
||||
const result = await databaseQueueProvider.add(
|
||||
'test-queue',
|
||||
'test-job',
|
||||
{ test: 'data' },
|
||||
{
|
||||
priority: 1,
|
||||
delay: 0,
|
||||
attempts: 3,
|
||||
},
|
||||
);
|
||||
|
||||
expect(mockJobRepository.create).toHaveBeenCalled();
|
||||
expect(mockJobRepository.save).toHaveBeenCalled();
|
||||
@@ -227,14 +237,21 @@ describe('Queue System Unit Tests', () => {
|
||||
|
||||
describe('Error Handling', () => {
|
||||
it('should handle database connection errors gracefully', async () => {
|
||||
mockJobRepository.save.mockRejectedValue(new Error('Database connection failed'));
|
||||
mockJobRepository.save.mockRejectedValue(
|
||||
new Error('Database connection failed'),
|
||||
);
|
||||
|
||||
try {
|
||||
await databaseQueueProvider.add('test-queue', 'test-job', { test: 'data' }, {
|
||||
priority: 1,
|
||||
delay: 0,
|
||||
attempts: 3,
|
||||
});
|
||||
await databaseQueueProvider.add(
|
||||
'test-queue',
|
||||
'test-job',
|
||||
{ test: 'data' },
|
||||
{
|
||||
priority: 1,
|
||||
delay: 0,
|
||||
attempts: 3,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect(error.message).toBe('Database connection failed');
|
||||
@@ -257,7 +274,7 @@ describe('Queue System Unit Tests', () => {
|
||||
expect(invalidEvent.version).toBeDefined();
|
||||
expect(invalidEvent.occurredAt).toBeDefined();
|
||||
expect(invalidEvent.data).toBeDefined();
|
||||
|
||||
|
||||
// These should be undefined, indicating invalid event
|
||||
expect((invalidEvent as any).aggregateId).toBeUndefined();
|
||||
expect((invalidEvent as any).aggregateType).toBeUndefined();
|
||||
@@ -281,7 +298,7 @@ describe('Queue System Unit Tests', () => {
|
||||
describe('Performance and Scalability', () => {
|
||||
it('should handle multiple concurrent operations', async () => {
|
||||
const operations = [];
|
||||
|
||||
|
||||
// Simulate multiple concurrent task additions
|
||||
for (let i = 0; i < 10; i++) {
|
||||
mockJobRepository.save.mockResolvedValueOnce({
|
||||
@@ -293,11 +310,16 @@ describe('Queue System Unit Tests', () => {
|
||||
} as any);
|
||||
|
||||
operations.push(
|
||||
databaseQueueProvider.add('concurrent-queue', 'concurrent-job', { index: i }, {
|
||||
priority: 1,
|
||||
delay: 0,
|
||||
attempts: 3,
|
||||
})
|
||||
databaseQueueProvider.add(
|
||||
'concurrent-queue',
|
||||
'concurrent-job',
|
||||
{ index: i },
|
||||
{
|
||||
priority: 1,
|
||||
delay: 0,
|
||||
attempts: 3,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -308,7 +330,7 @@ describe('Queue System Unit Tests', () => {
|
||||
|
||||
it('should handle batch event publishing', async () => {
|
||||
const events = [];
|
||||
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
events.push({
|
||||
eventType: 'batch.event',
|
||||
@@ -331,10 +353,12 @@ describe('Queue System Unit Tests', () => {
|
||||
}
|
||||
|
||||
// Test batch publishing
|
||||
const publishPromises = events.map(event => databaseQueueProvider.publish(event));
|
||||
const publishPromises = events.map((event) =>
|
||||
databaseQueueProvider.publish(event),
|
||||
);
|
||||
await Promise.all(publishPromises);
|
||||
|
||||
expect(mockEventRepository.save).toHaveBeenCalledTimes(5);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user