refactor:重构Zulip模块按业务功能模块化架构

- 将技术实现服务从business层迁移到core层
- 创建src/core/zulip/核心服务模块,包含API客户端、连接池等技术服务
- 保留src/business/zulip/业务逻辑,专注游戏相关的业务规则
- 通过依赖注入实现业务层与核心层的解耦
- 更新模块导入关系,确保架构分层清晰

重构后的架构符合单一职责原则,提高了代码的可维护性和可测试性
This commit is contained in:
moyin
2025-12-31 15:44:36 +08:00
parent 5140bd1a54
commit 2d10131838
36 changed files with 2773 additions and 125 deletions

View File

@@ -0,0 +1,733 @@
/**
* 监控服务测试
*
* 功能描述:
* - 测试MonitoringService的核心功能
* - 包含属性测试验证操作确认和日志记录
* - 包含属性测试验证系统监控和告警
*
* **Feature: zulip-integration, Property 10: 操作确认和日志记录**
* **Validates: Requirements 4.5, 8.5, 9.1, 9.2, 9.3**
*
* **Feature: zulip-integration, Property 11: 系统监控和告警**
* **Validates: Requirements 9.4**
*
* @author angjustinl
* @version 1.0.0
* @since 2025-12-25
*/
import { Test, TestingModule } from '@nestjs/testing';
import { ConfigService } from '@nestjs/config';
import { Logger } from '@nestjs/common';
import * as fc from 'fast-check';
import {
MonitoringService,
ConnectionEventType,
ApiCallResult,
AlertLevel,
ConnectionLog,
ApiCallLog,
MessageForwardLog,
OperationConfirmation,
} from './monitoring.service';
describe('MonitoringService', () => {
let service: MonitoringService;
let mockConfigService: jest.Mocked<ConfigService>;
beforeEach(async () => {
jest.clearAllMocks();
// Mock NestJS Logger
jest.spyOn(Logger.prototype, 'log').mockImplementation();
jest.spyOn(Logger.prototype, 'error').mockImplementation();
jest.spyOn(Logger.prototype, 'warn').mockImplementation();
jest.spyOn(Logger.prototype, 'debug').mockImplementation();
mockConfigService = {
get: jest.fn((key: string, defaultValue?: any) => {
const config: Record<string, any> = {
'MONITORING_HEALTH_CHECK_INTERVAL': 60000,
'MONITORING_ERROR_RATE_THRESHOLD': 0.1,
'MONITORING_RESPONSE_TIME_THRESHOLD': 5000,
'MONITORING_MEMORY_THRESHOLD': 0.9,
};
return config[key] ?? defaultValue;
}),
} as any;
const module: TestingModule = await Test.createTestingModule({
providers: [
MonitoringService,
{
provide: ConfigService,
useValue: mockConfigService,
},
],
}).compile();
service = module.get<MonitoringService>(MonitoringService);
});
afterEach(async () => {
service.onModuleDestroy();
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('基础功能测试', () => {
it('应该正确初始化统计数据', () => {
const stats = service.getStats();
expect(stats.connections.total).toBe(0);
expect(stats.apiCalls.total).toBe(0);
expect(stats.messages.upstream).toBe(0);
expect(stats.alerts.total).toBe(0);
});
it('应该正确重置统计数据', () => {
// 添加一些数据
service.logConnection({
socketId: 'socket1',
eventType: ConnectionEventType.CONNECTED,
timestamp: new Date(),
});
// 重置
service.resetStats();
const stats = service.getStats();
expect(stats.connections.total).toBe(0);
});
});
describe('连接日志测试', () => {
it('应该正确记录连接事件', () => {
service.logConnection({
socketId: 'socket1',
userId: 'user1',
eventType: ConnectionEventType.CONNECTED,
timestamp: new Date(),
});
const stats = service.getStats();
expect(stats.connections.total).toBe(1);
expect(stats.connections.active).toBe(1);
});
it('应该正确记录断开事件', () => {
service.logConnection({
socketId: 'socket1',
eventType: ConnectionEventType.CONNECTED,
timestamp: new Date(),
});
service.logConnection({
socketId: 'socket1',
eventType: ConnectionEventType.DISCONNECTED,
timestamp: new Date(),
});
const stats = service.getStats();
expect(stats.connections.active).toBe(0);
});
it('应该正确记录错误事件', () => {
service.logConnection({
socketId: 'socket1',
eventType: ConnectionEventType.ERROR,
error: 'Connection failed',
timestamp: new Date(),
});
const stats = service.getStats();
expect(stats.connections.errors).toBe(1);
});
});
describe('API调用日志测试', () => {
it('应该正确记录成功的API调用', () => {
service.logApiCall({
operation: 'sendMessage',
userId: 'user1',
result: ApiCallResult.SUCCESS,
responseTime: 100,
timestamp: new Date(),
});
const stats = service.getStats();
expect(stats.apiCalls.total).toBe(1);
expect(stats.apiCalls.success).toBe(1);
});
it('应该正确记录失败的API调用', () => {
service.logApiCall({
operation: 'sendMessage',
userId: 'user1',
result: ApiCallResult.FAILURE,
responseTime: 100,
error: 'API error',
timestamp: new Date(),
});
const stats = service.getStats();
expect(stats.apiCalls.failures).toBe(1);
});
it('应该在响应时间过长时发送告警', () => {
const alertHandler = jest.fn();
service.on('alert', alertHandler);
service.logApiCall({
operation: 'sendMessage',
userId: 'user1',
result: ApiCallResult.SUCCESS,
responseTime: 10000, // 超过阈值
timestamp: new Date(),
});
expect(alertHandler).toHaveBeenCalled();
});
});
describe('消息转发日志测试', () => {
it('应该正确记录上行消息', () => {
service.logMessageForward({
fromUserId: 'user1',
toUserIds: ['user2'],
stream: 'test-stream',
topic: 'test-topic',
direction: 'upstream',
success: true,
latency: 50,
timestamp: new Date(),
});
const stats = service.getStats();
expect(stats.messages.upstream).toBe(1);
});
it('应该正确记录下行消息', () => {
service.logMessageForward({
fromUserId: 'user1',
toUserIds: ['user2', 'user3'],
stream: 'test-stream',
topic: 'test-topic',
direction: 'downstream',
success: true,
latency: 30,
timestamp: new Date(),
});
const stats = service.getStats();
expect(stats.messages.downstream).toBe(1);
});
});
describe('操作确认测试', () => {
it('应该正确记录操作确认', () => {
const confirmHandler = jest.fn();
service.on('operation_confirmed', confirmHandler);
service.confirmOperation({
operationId: 'op1',
operation: 'sendMessage',
userId: 'user1',
success: true,
timestamp: new Date(),
});
expect(confirmHandler).toHaveBeenCalled();
expect(Logger.prototype.log).toHaveBeenCalled();
});
});
describe('告警测试', () => {
it('应该正确发送告警', () => {
const alertHandler = jest.fn();
service.on('alert', alertHandler);
service.sendAlert({
id: 'alert1',
level: AlertLevel.WARNING,
title: 'Test Alert',
message: 'This is a test alert',
component: 'test',
timestamp: new Date(),
});
expect(alertHandler).toHaveBeenCalled();
const stats = service.getStats();
expect(stats.alerts.byLevel[AlertLevel.WARNING]).toBe(1);
});
it('应该正确获取最近的告警', () => {
service.sendAlert({
id: 'alert1',
level: AlertLevel.INFO,
title: 'Alert 1',
message: 'Message 1',
component: 'test',
timestamp: new Date(),
});
service.sendAlert({
id: 'alert2',
level: AlertLevel.WARNING,
title: 'Alert 2',
message: 'Message 2',
component: 'test',
timestamp: new Date(),
});
const recentAlerts = service.getRecentAlerts(10);
expect(recentAlerts.length).toBe(2);
});
});
describe('健康检查测试', () => {
it('应该返回健康状态', async () => {
const health = await service.checkSystemHealth();
expect(health).toBeDefined();
expect(health.status).toBeDefined();
expect(health.components).toBeDefined();
expect(health.timestamp).toBeInstanceOf(Date);
});
});
/**
* 属性测试: 操作确认和日志记录
*
* **Feature: zulip-integration, Property 10: 操作确认和日志记录**
* **Validates: Requirements 4.5, 8.5, 9.1, 9.2, 9.3**
*
* 对于任何重要的系统操作连接管理、API调用、消息转发
* 系统应该记录相应的日志信息,并向客户端返回操作确认
*/
describe('Property 10: 操作确认和日志记录', () => {
beforeEach(() => {
service.resetStats();
});
/**
* 属性: 对于任何连接事件,系统应该正确记录并更新统计
*/
it('对于任何连接事件,系统应该正确记录并更新统计', async () => {
await fc.assert(
fc.asyncProperty(
// 生成Socket ID
fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
// 生成用户ID可选
fc.option(fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0)),
// 生成事件类型
fc.constantFrom(
ConnectionEventType.CONNECTED,
ConnectionEventType.DISCONNECTED,
ConnectionEventType.ERROR,
ConnectionEventType.TIMEOUT
),
async (socketId, userId, eventType) => {
const initialStats = service.getStats();
const initialTotal = initialStats.connections.total;
const initialActive = initialStats.connections.active;
const initialErrors = initialStats.connections.errors;
const log: ConnectionLog = {
socketId,
userId: userId ?? undefined,
eventType,
timestamp: new Date(),
};
service.logConnection(log);
const newStats = service.getStats();
// 验证统计更新正确
if (eventType === ConnectionEventType.CONNECTED) {
expect(newStats.connections.total).toBe(initialTotal + 1);
expect(newStats.connections.active).toBe(initialActive + 1);
} else if (eventType === ConnectionEventType.DISCONNECTED) {
expect(newStats.connections.active).toBe(Math.max(0, initialActive - 1));
} else if (eventType === ConnectionEventType.ERROR || eventType === ConnectionEventType.TIMEOUT) {
expect(newStats.connections.errors).toBe(initialErrors + 1);
}
// 验证日志被调用
expect(Logger.prototype.log).toHaveBeenCalled();
}
),
{ numRuns: 100 }
);
}, 30000);
/**
* 属性: 对于任何API调用系统应该正确记录响应时间和结果
*/
it('对于任何API调用系统应该正确记录响应时间和结果', async () => {
await fc.assert(
fc.asyncProperty(
// 生成操作名称
fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
// 生成用户ID
fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
// 生成结果
fc.constantFrom(
ApiCallResult.SUCCESS,
ApiCallResult.FAILURE,
ApiCallResult.TIMEOUT,
ApiCallResult.RATE_LIMITED
),
// 生成响应时间
fc.integer({ min: 1, max: 10000 }),
async (operation, userId, result, responseTime) => {
const initialStats = service.getStats();
const initialTotal = initialStats.apiCalls.total;
const initialSuccess = initialStats.apiCalls.success;
const initialFailures = initialStats.apiCalls.failures;
const log: ApiCallLog = {
operation,
userId,
result,
responseTime,
timestamp: new Date(),
};
service.logApiCall(log);
const newStats = service.getStats();
// 验证总数增加
expect(newStats.apiCalls.total).toBe(initialTotal + 1);
// 验证成功/失败计数
if (result === ApiCallResult.SUCCESS) {
expect(newStats.apiCalls.success).toBe(initialSuccess + 1);
} else {
expect(newStats.apiCalls.failures).toBe(initialFailures + 1);
}
// 验证日志被调用
expect(Logger.prototype.log).toHaveBeenCalled();
}
),
{ numRuns: 100 }
);
}, 30000);
/**
* 属性: 对于任何消息转发,系统应该正确记录方向和延迟
*/
it('对于任何消息转发,系统应该正确记录方向和延迟', async () => {
await fc.assert(
fc.asyncProperty(
// 生成发送者ID
fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
// 生成接收者ID列表
fc.array(
fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
{ minLength: 1, maxLength: 10 }
),
// 生成Stream名称
fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
// 生成Topic名称
fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
// 生成方向
fc.constantFrom('upstream' as const, 'downstream' as const),
// 生成延迟
fc.integer({ min: 1, max: 5000 }),
async (fromUserId, toUserIds, stream, topic, direction, latency) => {
const initialStats = service.getStats();
const initialUpstream = initialStats.messages.upstream;
const initialDownstream = initialStats.messages.downstream;
const log: MessageForwardLog = {
fromUserId,
toUserIds,
stream,
topic,
direction,
success: true,
latency,
timestamp: new Date(),
};
service.logMessageForward(log);
const newStats = service.getStats();
// 验证方向计数
if (direction === 'upstream') {
expect(newStats.messages.upstream).toBe(initialUpstream + 1);
} else {
expect(newStats.messages.downstream).toBe(initialDownstream + 1);
}
// 验证日志被调用
expect(Logger.prototype.log).toHaveBeenCalled();
}
),
{ numRuns: 100 }
);
}, 30000);
/**
* 属性: 对于任何操作确认,系统应该记录并发出事件
*/
it('对于任何操作确认,系统应该记录并发出事件', async () => {
await fc.assert(
fc.asyncProperty(
// 生成操作ID
fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
// 生成操作名称
fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
// 生成用户ID
fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
// 生成成功状态
fc.boolean(),
async (operationId, operation, userId, success) => {
const eventHandler = jest.fn();
service.on('operation_confirmed', eventHandler);
const confirmation: OperationConfirmation = {
operationId,
operation,
userId,
success,
timestamp: new Date(),
};
service.confirmOperation(confirmation);
// 验证事件被发出
expect(eventHandler).toHaveBeenCalledWith(confirmation);
// 验证日志被调用
expect(Logger.prototype.log).toHaveBeenCalled();
service.removeListener('operation_confirmed', eventHandler);
}
),
{ numRuns: 100 }
);
}, 30000);
});
/**
* 属性测试: 系统监控和告警
*
* **Feature: zulip-integration, Property 11: 系统监控和告警**
* **Validates: Requirements 9.4**
*
* 对于任何系统资源异常或性能问题,系统应该检测异常情况并发送相应的告警通知
*/
describe('Property 11: 系统监控和告警', () => {
beforeEach(() => {
service.resetStats();
});
/**
* 属性: 对于任何告警,系统应该正确记录并更新统计
*/
it('对于任何告警,系统应该正确记录并更新统计', async () => {
await fc.assert(
fc.asyncProperty(
// 生成告警ID
fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
// 生成告警级别
fc.constantFrom(
AlertLevel.INFO,
AlertLevel.WARNING,
AlertLevel.ERROR,
AlertLevel.CRITICAL
),
// 生成标题
fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
// 生成消息
fc.string({ minLength: 1, maxLength: 500 }).filter(s => s.trim().length > 0),
// 生成组件名称
fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
async (id, level, title, message, component) => {
const initialStats = service.getStats();
const initialTotal = initialStats.alerts.total;
const initialByLevel = initialStats.alerts.byLevel[level];
const alertHandler = jest.fn();
service.on('alert', alertHandler);
service.sendAlert({
id,
level,
title,
message,
component,
timestamp: new Date(),
});
const newStats = service.getStats();
// 验证总数增加
expect(newStats.alerts.total).toBe(initialTotal + 1);
// 验证级别计数增加
expect(newStats.alerts.byLevel[level]).toBe(initialByLevel + 1);
// 验证事件被发出
expect(alertHandler).toHaveBeenCalled();
service.removeListener('alert', alertHandler);
}
),
{ numRuns: 100 }
);
}, 30000);
/**
* 属性: 健康检查应该返回完整的状态信息
*/
it('健康检查应该返回完整的状态信息', async () => {
await fc.assert(
fc.asyncProperty(
// 生成一些连接事件
fc.integer({ min: 0, max: 10 }),
// 生成一些API调用
fc.integer({ min: 0, max: 10 }),
async (connectionCount, apiCallCount) => {
// 模拟一些活动
for (let i = 0; i < connectionCount; i++) {
service.logConnection({
socketId: `socket-${i}`,
eventType: ConnectionEventType.CONNECTED,
timestamp: new Date(),
});
}
for (let i = 0; i < apiCallCount; i++) {
service.logApiCall({
operation: `operation-${i}`,
userId: `user-${i}`,
result: ApiCallResult.SUCCESS,
responseTime: 100,
timestamp: new Date(),
});
}
const health = await service.checkSystemHealth();
// 验证健康状态结构
expect(health).toBeDefined();
expect(health.status).toBeDefined();
expect(['healthy', 'degraded', 'unhealthy']).toContain(health.status);
expect(health.components).toBeDefined();
expect(health.components.websocket).toBeDefined();
expect(health.components.zulipApi).toBeDefined();
expect(health.components.redis).toBeDefined();
expect(health.components.memory).toBeDefined();
expect(health.timestamp).toBeInstanceOf(Date);
}
),
{ numRuns: 50 }
);
}, 30000);
/**
* 属性: 最近告警列表应该正确维护
*/
it('最近告警列表应该正确维护', async () => {
await fc.assert(
fc.asyncProperty(
// 生成告警数量
fc.integer({ min: 1, max: 20 }),
// 生成请求数量
fc.integer({ min: 1, max: 15 }),
async (alertCount, requestLimit) => {
// 重置统计以确保干净的状态
service.resetStats();
// 发送多个告警
for (let i = 0; i < alertCount; i++) {
service.sendAlert({
id: `alert-${i}`,
level: AlertLevel.INFO,
title: `Alert ${i}`,
message: `Message ${i}`,
component: 'test',
timestamp: new Date(),
});
}
const recentAlerts = service.getRecentAlerts(requestLimit);
// 验证返回数量不超过请求数量
expect(recentAlerts.length).toBeLessThanOrEqual(requestLimit);
// 验证返回数量不超过实际告警数量(在本次测试中发送的)
expect(recentAlerts.length).toBeLessThanOrEqual(Math.min(alertCount, requestLimit));
// 验证每个告警都有必要的字段
for (const alert of recentAlerts) {
expect(alert.id).toBeDefined();
expect(alert.level).toBeDefined();
expect(alert.title).toBeDefined();
expect(alert.message).toBeDefined();
expect(alert.component).toBeDefined();
expect(alert.timestamp).toBeInstanceOf(Date);
}
}
),
{ numRuns: 50 }
);
}, 30000);
/**
* 属性: 统计数据应该保持一致性
*/
it('统计数据应该保持一致性', async () => {
await fc.assert(
fc.asyncProperty(
// 生成成功API调用数
fc.integer({ min: 0, max: 50 }),
// 生成失败API调用数
fc.integer({ min: 0, max: 50 }),
async (successCount, failureCount) => {
// 重置统计以确保干净的状态
service.resetStats();
// 记录成功的API调用
for (let i = 0; i < successCount; i++) {
service.logApiCall({
operation: 'test',
userId: 'user1',
result: ApiCallResult.SUCCESS,
responseTime: 100,
timestamp: new Date(),
});
}
// 记录失败的API调用
for (let i = 0; i < failureCount; i++) {
service.logApiCall({
operation: 'test',
userId: 'user1',
result: ApiCallResult.FAILURE,
responseTime: 100,
timestamp: new Date(),
});
}
const stats = service.getStats();
// 验证总数等于成功数加失败数
expect(stats.apiCalls.total).toBe(successCount + failureCount);
expect(stats.apiCalls.success).toBe(successCount);
expect(stats.apiCalls.failures).toBe(failureCount);
}
),
{ numRuns: 50 }
);
}, 30000);
});
});