feat(chat): 新增聊天业务模块

范围:src/business/chat/
- 实现 ChatService 聊天业务服务(登录/登出/消息发送/位置更新)
- 实现 ChatSessionService 会话管理服务(会话创建/销毁/上下文注入)
- 实现 ChatFilterService 消息过滤服务(频率限制/敏感词/权限验证)
- 实现 ChatCleanupService 会话清理服务(定时清理过期会话)
- 添加完整的单元测试覆盖
- 添加模块 README 文档
This commit is contained in:
moyin
2026-01-14 19:17:32 +08:00
parent 5bcf3cb678
commit 30a4a2813d
11 changed files with 3298 additions and 0 deletions

View File

@@ -0,0 +1,216 @@
/**
* 聊天业务模块测试
*
* 测试范围:
* - 模块配置验证
* - 服务提供者注册
* - 接口导出验证
*
* @author moyin
* @version 1.0.0
* @since 2026-01-14
* @lastModified 2026-01-14
*/
import { Test, TestingModule } from '@nestjs/testing';
import { Logger } from '@nestjs/common';
import { ChatService } from './chat.service';
import { ChatSessionService } from './services/chat_session.service';
import { ChatFilterService } from './services/chat_filter.service';
import { ChatCleanupService } from './services/chat_cleanup.service';
import { SESSION_QUERY_SERVICE } from '../../core/session_core/session_core.interfaces';
import { LoginCoreService } from '../../core/login_core/login_core.service';
describe('ChatModule', () => {
let module: TestingModule;
let chatService: ChatService;
let sessionService: ChatSessionService;
let filterService: ChatFilterService;
let cleanupService: ChatCleanupService;
// Mock依赖
const mockZulipClientPool = {
createUserClient: jest.fn(),
destroyUserClient: jest.fn(),
sendMessage: jest.fn(),
};
const mockZulipConfigService = {
getStreamByMap: jest.fn().mockReturnValue('Test Stream'),
findNearbyObject: jest.fn().mockReturnValue(null),
getAllMapIds: jest.fn().mockReturnValue(['novice_village', 'whale_port']),
};
const mockApiKeySecurityService = {
getApiKey: jest.fn(),
deleteApiKey: jest.fn(),
};
const mockRedisService = {
get: jest.fn(),
setex: jest.fn(),
del: jest.fn(),
sadd: jest.fn(),
srem: jest.fn(),
smembers: jest.fn(),
expire: jest.fn(),
incr: jest.fn(),
};
const mockLoginCoreService = {
verifyToken: jest.fn(),
};
beforeEach(async () => {
// 禁用日志输出
jest.spyOn(Logger.prototype, 'log').mockImplementation();
jest.spyOn(Logger.prototype, 'error').mockImplementation();
jest.spyOn(Logger.prototype, 'warn').mockImplementation();
module = await Test.createTestingModule({
providers: [
ChatService,
ChatSessionService,
ChatFilterService,
ChatCleanupService,
{
provide: SESSION_QUERY_SERVICE,
useExisting: ChatSessionService,
},
{
provide: 'ZULIP_CLIENT_POOL_SERVICE',
useValue: mockZulipClientPool,
},
{
provide: 'ZULIP_CONFIG_SERVICE',
useValue: mockZulipConfigService,
},
{
provide: 'API_KEY_SECURITY_SERVICE',
useValue: mockApiKeySecurityService,
},
{
provide: 'REDIS_SERVICE',
useValue: mockRedisService,
},
{
provide: LoginCoreService,
useValue: mockLoginCoreService,
},
],
}).compile();
chatService = module.get<ChatService>(ChatService);
sessionService = module.get<ChatSessionService>(ChatSessionService);
filterService = module.get<ChatFilterService>(ChatFilterService);
cleanupService = module.get<ChatCleanupService>(ChatCleanupService);
});
afterEach(async () => {
if (module) {
await module.close();
}
jest.clearAllMocks();
});
describe('模块配置', () => {
it('应该成功编译模块', () => {
expect(module).toBeDefined();
});
it('应该提供 ChatService', () => {
expect(chatService).toBeDefined();
expect(chatService).toBeInstanceOf(ChatService);
});
it('应该提供 ChatSessionService', () => {
expect(sessionService).toBeDefined();
expect(sessionService).toBeInstanceOf(ChatSessionService);
});
it('应该提供 ChatFilterService', () => {
expect(filterService).toBeDefined();
expect(filterService).toBeInstanceOf(ChatFilterService);
});
it('应该提供 ChatCleanupService', () => {
expect(cleanupService).toBeDefined();
expect(cleanupService).toBeInstanceOf(ChatCleanupService);
});
});
describe('接口导出', () => {
it('应该导出 SESSION_QUERY_SERVICE 接口', () => {
const queryService = module.get(SESSION_QUERY_SERVICE);
expect(queryService).toBeDefined();
});
it('SESSION_QUERY_SERVICE 应该指向 ChatSessionService', () => {
const queryService = module.get(SESSION_QUERY_SERVICE);
expect(queryService).toBe(sessionService);
});
it('SESSION_QUERY_SERVICE 应该实现 ISessionManagerService 接口', () => {
const queryService = module.get(SESSION_QUERY_SERVICE);
expect(typeof queryService.createSession).toBe('function');
expect(typeof queryService.getSession).toBe('function');
expect(typeof queryService.destroySession).toBe('function');
expect(typeof queryService.injectContext).toBe('function');
});
});
describe('服务依赖注入', () => {
it('ChatService 应该能够获取所有依赖', () => {
expect(chatService).toBeDefined();
// 验证私有依赖通过检查服务是否正常工作
expect(chatService['sessionService']).toBeDefined();
expect(chatService['filterService']).toBeDefined();
});
it('ChatSessionService 应该能够获取所有依赖', () => {
expect(sessionService).toBeDefined();
});
it('ChatFilterService 应该能够获取所有依赖', () => {
expect(filterService).toBeDefined();
});
it('ChatCleanupService 应该能够获取所有依赖', () => {
expect(cleanupService).toBeDefined();
expect(cleanupService['sessionService']).toBeDefined();
});
});
describe('服务协作', () => {
it('ChatService 应该能够调用 ChatSessionService', async () => {
mockRedisService.get.mockResolvedValue(null);
const session = await chatService.getSession('test_socket');
expect(session).toBeNull();
});
it('ChatCleanupService 应该能够调用 ChatSessionService', async () => {
mockRedisService.smembers.mockResolvedValue([]);
const result = await cleanupService.triggerCleanup();
expect(result.cleanedCount).toBe(0);
});
});
describe('模块导出验证', () => {
it('所有导出的服务应该可用', () => {
// ChatModule 导出的服务
expect(chatService).toBeDefined();
expect(sessionService).toBeDefined();
expect(filterService).toBeDefined();
expect(cleanupService).toBeDefined();
});
it('SESSION_QUERY_SERVICE 应该可供其他模块使用', () => {
const queryService = module.get(SESSION_QUERY_SERVICE);
expect(queryService).toBeDefined();
// 验证接口方法存在
expect(queryService.createSession).toBeDefined();
expect(queryService.getSession).toBeDefined();
expect(queryService.destroySession).toBeDefined();
});
});
});