forked from datawhale/whale-town-end
refactor:重构Zulip模块按业务功能模块化架构
- 将技术实现服务从business层迁移到core层 - 创建src/core/zulip/核心服务模块,包含API客户端、连接池等技术服务 - 保留src/business/zulip/业务逻辑,专注游戏相关的业务规则 - 通过依赖注入实现业务层与核心层的解耦 - 更新模块导入关系,确保架构分层清晰 重构后的架构符合单一职责原则,提高了代码的可维护性和可测试性
This commit is contained in:
609
src/business/zulip/zulip_integration.e2e.spec.ts
Normal file
609
src/business/zulip/zulip_integration.e2e.spec.ts
Normal file
@@ -0,0 +1,609 @@
|
||||
/**
|
||||
* Zulip集成系统端到端测试
|
||||
*
|
||||
* 功能描述:
|
||||
* - 测试完整的登录到聊天流程
|
||||
* - 测试多用户并发聊天场景
|
||||
* - 测试错误场景和降级处理
|
||||
*
|
||||
* **验证需求: 所有需求**
|
||||
*
|
||||
* @author angjustinl
|
||||
* @version 1.0.0
|
||||
* @since 2025-12-25
|
||||
*/
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { io, Socket as ClientSocket } from 'socket.io-client';
|
||||
import { AppModule } from '../../app.module';
|
||||
|
||||
// 如果没有设置 RUN_E2E_TESTS 环境变量,跳过这些测试
|
||||
const describeE2E = process.env.RUN_E2E_TESTS === 'true' ? describe : describe.skip;
|
||||
|
||||
describeE2E('Zulip Integration E2E Tests', () => {
|
||||
let app: INestApplication;
|
||||
let serverUrl: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
|
||||
app = moduleFixture.createNestApplication();
|
||||
await app.init();
|
||||
await app.listen(0); // 使用随机端口
|
||||
|
||||
const address = app.getHttpServer().address();
|
||||
const port = address.port;
|
||||
serverUrl = `http://localhost:${port}`;
|
||||
}, 30000);
|
||||
|
||||
afterAll(async () => {
|
||||
if (app) {
|
||||
await app.close();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 创建WebSocket客户端连接
|
||||
*/
|
||||
const createClient = (): Promise<ClientSocket> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const client = io(`${serverUrl}/game`, {
|
||||
transports: ['websocket'],
|
||||
autoConnect: true,
|
||||
});
|
||||
|
||||
client.on('connect', () => resolve(client));
|
||||
client.on('connect_error', (err: any) => reject(err));
|
||||
|
||||
setTimeout(() => reject(new Error('Connection timeout')), 5000);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 等待指定事件
|
||||
*/
|
||||
const waitForEvent = <T>(client: ClientSocket, event: string, timeout = 5000): Promise<T> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
reject(new Error(`Timeout waiting for event: ${event}`));
|
||||
}, timeout);
|
||||
|
||||
client.once(event, (data: T) => {
|
||||
clearTimeout(timer);
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 测试套件1: 完整的登录到聊天流程测试
|
||||
* 验证需求: 1.1, 1.2, 1.3, 2.1, 4.1, 4.2, 5.1
|
||||
*/
|
||||
describe('完整的登录到聊天流程测试', () => {
|
||||
let client: ClientSocket;
|
||||
|
||||
afterEach(async () => {
|
||||
if (client?.connected) {
|
||||
client.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试: WebSocket连接建立
|
||||
* 验证需求 1.1: 游戏客户端发送登录包时系统应验证游戏Token并建立WebSocket连接
|
||||
*/
|
||||
it('应该成功建立WebSocket连接', async () => {
|
||||
client = await createClient();
|
||||
expect(client.connected).toBe(true);
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试: 有效Token登录成功
|
||||
* 验证需求 1.1, 1.2: 验证Token并分配唯一会话ID
|
||||
*/
|
||||
it('应该使用有效Token成功登录', async () => {
|
||||
client = await createClient();
|
||||
|
||||
const loginPromise = waitForEvent<any>(client, 'login_success');
|
||||
client.emit('login', { type: 'login', token: 'valid_test_token_123' });
|
||||
|
||||
const response = await loginPromise;
|
||||
|
||||
expect(response.t).toBe('login_success');
|
||||
expect(response.sessionId).toBeDefined();
|
||||
expect(response.userId).toBeDefined();
|
||||
expect(response.currentMap).toBeDefined();
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试: 无效Token登录失败
|
||||
* 验证需求 1.1: 系统应验证游戏Token
|
||||
*/
|
||||
it('应该拒绝无效Token的登录请求', async () => {
|
||||
client = await createClient();
|
||||
|
||||
const errorPromise = waitForEvent<any>(client, 'login_error');
|
||||
client.emit('login', { type: 'login', token: 'invalid_token' });
|
||||
|
||||
const response = await errorPromise;
|
||||
|
||||
expect(response.t).toBe('login_error');
|
||||
expect(response.message).toBeDefined();
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试: 登录后发送聊天消息
|
||||
* 验证需求 4.1, 4.2: 玩家发送聊天消息时系统应根据位置确定目标Stream
|
||||
*/
|
||||
it('应该在登录后成功发送聊天消息', async () => {
|
||||
client = await createClient();
|
||||
|
||||
// 先登录
|
||||
const loginPromise = waitForEvent<any>(client, 'login_success');
|
||||
client.emit('login', { type: 'login', token: 'valid_test_token_456' });
|
||||
await loginPromise;
|
||||
|
||||
// 发送聊天消息
|
||||
const chatPromise = waitForEvent<any>(client, 'chat_sent');
|
||||
client.emit('chat', { t: 'chat', content: 'Hello World!', scope: 'local' });
|
||||
|
||||
const response = await chatPromise;
|
||||
|
||||
expect(response.t).toBe('chat_sent');
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试: 未登录时发送消息被拒绝
|
||||
* 验证需求 7.2: 系统应验证玩家是否有权限
|
||||
*/
|
||||
it('应该拒绝未登录用户的聊天消息', async () => {
|
||||
client = await createClient();
|
||||
|
||||
const errorPromise = waitForEvent<any>(client, 'chat_error');
|
||||
client.emit('chat', { t: 'chat', content: 'Hello', scope: 'local' });
|
||||
|
||||
const response = await errorPromise;
|
||||
|
||||
expect(response.t).toBe('chat_error');
|
||||
expect(response.message).toBe('请先登录');
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试: 空消息内容被拒绝
|
||||
* 验证需求 4.3: 系统应过滤消息内容
|
||||
*/
|
||||
it('应该拒绝空内容的聊天消息', async () => {
|
||||
client = await createClient();
|
||||
|
||||
// 先登录
|
||||
const loginPromise = waitForEvent<any>(client, 'login_success');
|
||||
client.emit('login', { type: 'login', token: 'valid_test_token_789' });
|
||||
await loginPromise;
|
||||
|
||||
// 发送空消息
|
||||
const errorPromise = waitForEvent<any>(client, 'chat_error');
|
||||
client.emit('chat', { t: 'chat', content: ' ', scope: 'local' });
|
||||
|
||||
const response = await errorPromise;
|
||||
|
||||
expect(response.t).toBe('chat_error');
|
||||
expect(response.message).toBe('消息内容不能为空');
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试: 位置更新
|
||||
* 验证需求 6.2: 玩家切换地图时系统应更新位置信息
|
||||
*/
|
||||
it('应该成功更新玩家位置', async () => {
|
||||
client = await createClient();
|
||||
|
||||
// 先登录
|
||||
const loginPromise = waitForEvent<any>(client, 'login_success');
|
||||
client.emit('login', { type: 'login', token: 'valid_test_token_position' });
|
||||
await loginPromise;
|
||||
|
||||
// 更新位置 - 位置更新不返回确认消息,只需确保不报错
|
||||
client.emit('position_update', { t: 'position', x: 100, y: 200, mapId: 'tavern' });
|
||||
|
||||
// 等待一小段时间确保消息被处理
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// 如果没有错误,测试通过
|
||||
expect(client.connected).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试套件2: 多用户并发聊天测试
|
||||
* 验证需求: 5.2, 5.5, 6.1, 6.3
|
||||
*/
|
||||
describe('多用户并发聊天测试', () => {
|
||||
const clients: ClientSocket[] = [];
|
||||
|
||||
afterEach(async () => {
|
||||
// 断开所有客户端
|
||||
for (const client of clients) {
|
||||
if (client?.connected) {
|
||||
client.disconnect();
|
||||
}
|
||||
}
|
||||
clients.length = 0;
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试: 多用户同时连接
|
||||
* 验证需求 6.1: 系统应在Redis中存储会话映射关系
|
||||
*/
|
||||
it('应该支持多用户同时连接', async () => {
|
||||
const userCount = 5;
|
||||
|
||||
for (let i = 0; i < userCount; i++) {
|
||||
const client = await createClient();
|
||||
clients.push(client);
|
||||
|
||||
const loginPromise = waitForEvent<any>(client, 'login_success');
|
||||
client.emit('login', { type: 'login', token: `valid_multi_user_${i}` });
|
||||
await loginPromise;
|
||||
}
|
||||
|
||||
// 验证所有客户端都已连接并登录
|
||||
expect(clients.length).toBe(userCount);
|
||||
for (const client of clients) {
|
||||
expect(client.connected).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试: 多用户并发发送消息
|
||||
* 验证需求 4.1, 4.2: 多用户同时发送消息
|
||||
*/
|
||||
it('应该正确处理多用户并发发送消息', async () => {
|
||||
const userCount = 3;
|
||||
|
||||
// 创建并登录多个用户(使用完全不同的token前缀避免userId冲突)
|
||||
// userId是从token前8个字符生成的,所以每个用户需要不同的前缀
|
||||
const userPrefixes = ['userAAA1', 'userBBB2', 'userCCC3'];
|
||||
|
||||
for (let i = 0; i < userCount; i++) {
|
||||
const client = await createClient();
|
||||
clients.push(client);
|
||||
|
||||
const loginPromise = waitForEvent<any>(client, 'login_success');
|
||||
// 使用不同的前缀确保每个用户有唯一的userId
|
||||
client.emit('login', { type: 'login', token: `${userPrefixes[i]}_concurrent_${Date.now()}` });
|
||||
await loginPromise;
|
||||
|
||||
// 添加小延迟确保会话完全建立
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
}
|
||||
|
||||
// 顺序发送消息(避免并发会话问题)
|
||||
for (let i = 0; i < clients.length; i++) {
|
||||
const client = clients[i];
|
||||
const chatPromise = waitForEvent<any>(client, 'chat_sent');
|
||||
client.emit('chat', {
|
||||
t: 'chat',
|
||||
content: `Message from user ${i}`,
|
||||
scope: 'local'
|
||||
});
|
||||
const result = await chatPromise;
|
||||
expect(result.t).toBe('chat_sent');
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试: 用户断开连接后资源清理
|
||||
* 验证需求 1.3: 客户端断开连接时系统应清理相关资源
|
||||
*/
|
||||
it('应该在用户断开连接后正确清理资源', async () => {
|
||||
const client = await createClient();
|
||||
clients.push(client);
|
||||
|
||||
// 登录
|
||||
const loginPromise = waitForEvent<any>(client, 'login_success');
|
||||
client.emit('login', { type: 'login', token: 'valid_cleanup_test' });
|
||||
await loginPromise;
|
||||
|
||||
// 断开连接
|
||||
client.disconnect();
|
||||
|
||||
// 等待清理完成
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
||||
expect(client.connected).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试套件3: 错误场景和降级测试
|
||||
* 验证需求: 8.1, 8.2, 8.3, 8.4, 8.5
|
||||
*/
|
||||
describe('错误场景和降级测试', () => {
|
||||
let client: ClientSocket;
|
||||
|
||||
afterEach(async () => {
|
||||
if (client?.connected) {
|
||||
client.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试: 无效消息格式处理
|
||||
* 验证需求 8.5: 系统应记录详细错误日志
|
||||
*/
|
||||
it('应该正确处理无效的消息格式', async () => {
|
||||
client = await createClient();
|
||||
|
||||
// 登录
|
||||
const loginPromise = waitForEvent<any>(client, 'login_success');
|
||||
client.emit('login', { type: 'login', token: 'valid_error_test_1' });
|
||||
await loginPromise;
|
||||
|
||||
// 发送无效格式的聊天消息
|
||||
const errorPromise = waitForEvent<any>(client, 'chat_error');
|
||||
client.emit('chat', { invalid: 'format' });
|
||||
|
||||
const response = await errorPromise;
|
||||
|
||||
expect(response.t).toBe('chat_error');
|
||||
expect(response.message).toBe('消息格式无效');
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试: 重复登录处理
|
||||
* 验证需求 1.1: 系统应正确处理重复登录
|
||||
*/
|
||||
it('应该拒绝已登录用户的重复登录请求', async () => {
|
||||
client = await createClient();
|
||||
|
||||
// 第一次登录
|
||||
const loginPromise1 = waitForEvent<any>(client, 'login_success');
|
||||
client.emit('login', { type: 'login', token: 'valid_duplicate_test' });
|
||||
await loginPromise1;
|
||||
|
||||
// 尝试重复登录
|
||||
const errorPromise = waitForEvent<any>(client, 'login_error');
|
||||
client.emit('login', { type: 'login', token: 'another_token' });
|
||||
|
||||
const response = await errorPromise;
|
||||
|
||||
expect(response.t).toBe('login_error');
|
||||
expect(response.message).toBe('您已经登录');
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试: 空Token登录处理
|
||||
* 验证需求 1.1: 系统应验证Token
|
||||
*/
|
||||
it('应该拒绝空Token的登录请求', async () => {
|
||||
client = await createClient();
|
||||
|
||||
const errorPromise = waitForEvent<any>(client, 'login_error');
|
||||
client.emit('login', { type: 'login', token: '' });
|
||||
|
||||
const response = await errorPromise;
|
||||
|
||||
expect(response.t).toBe('login_error');
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试: 缺少scope的聊天消息
|
||||
* 验证需求 4.1: 系统应正确验证消息格式
|
||||
*/
|
||||
it('应该拒绝缺少scope的聊天消息', async () => {
|
||||
client = await createClient();
|
||||
|
||||
// 登录
|
||||
const loginPromise = waitForEvent<any>(client, 'login_success');
|
||||
client.emit('login', { type: 'login', token: 'valid_scope_test' });
|
||||
await loginPromise;
|
||||
|
||||
// 发送缺少scope的消息
|
||||
const errorPromise = waitForEvent<any>(client, 'chat_error');
|
||||
client.emit('chat', { t: 'chat', content: 'Hello' });
|
||||
|
||||
const response = await errorPromise;
|
||||
|
||||
expect(response.t).toBe('chat_error');
|
||||
expect(response.message).toBe('消息格式无效');
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试: 无效位置更新处理
|
||||
* 验证需求 6.2: 系统应正确验证位置数据
|
||||
*/
|
||||
it('应该忽略无效的位置更新', async () => {
|
||||
client = await createClient();
|
||||
|
||||
// 登录
|
||||
const loginPromise = waitForEvent<any>(client, 'login_success');
|
||||
client.emit('login', { type: 'login', token: 'valid_position_error_test' });
|
||||
await loginPromise;
|
||||
|
||||
// 发送无效位置更新(缺少mapId)
|
||||
client.emit('position_update', { t: 'position', x: 100, y: 200 });
|
||||
|
||||
// 等待处理
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// 连接应该保持正常
|
||||
expect(client.connected).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试套件4: 连接生命周期测试
|
||||
* 验证需求: 1.3, 1.4, 6.4
|
||||
*/
|
||||
describe('连接生命周期测试', () => {
|
||||
/**
|
||||
* 测试: 连接-登录-断开完整流程
|
||||
* 验证需求 1.1, 1.2, 1.3: 完整的连接生命周期
|
||||
*/
|
||||
it('应该正确处理完整的连接生命周期', async () => {
|
||||
// 1. 建立连接
|
||||
const client = await createClient();
|
||||
expect(client.connected).toBe(true);
|
||||
|
||||
// 2. 登录
|
||||
const loginPromise = waitForEvent<any>(client, 'login_success');
|
||||
client.emit('login', { type: 'login', token: 'valid_lifecycle_test' });
|
||||
const loginResponse = await loginPromise;
|
||||
expect(loginResponse.t).toBe('login_success');
|
||||
|
||||
// 3. 发送消息
|
||||
const chatPromise = waitForEvent<any>(client, 'chat_sent');
|
||||
client.emit('chat', { t: 'chat', content: 'Lifecycle test message', scope: 'local' });
|
||||
const chatResponse = await chatPromise;
|
||||
expect(chatResponse.t).toBe('chat_sent');
|
||||
|
||||
// 4. 断开连接
|
||||
client.disconnect();
|
||||
|
||||
// 等待断开完成
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
expect(client.connected).toBe(false);
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试: 快速连接断开
|
||||
* 验证需求 1.3: 系统应正确处理快速断开
|
||||
*/
|
||||
it('应该正确处理快速连接断开', async () => {
|
||||
const client = await createClient();
|
||||
expect(client.connected).toBe(true);
|
||||
|
||||
// 立即断开
|
||||
client.disconnect();
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
expect(client.connected).toBe(false);
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试: 登录后立即断开
|
||||
* 验证需求 1.3: 系统应清理会话资源
|
||||
*/
|
||||
it('应该正确处理登录后立即断开', async () => {
|
||||
const client = await createClient();
|
||||
|
||||
// 登录
|
||||
const loginPromise = waitForEvent<any>(client, 'login_success');
|
||||
client.emit('login', { type: 'login', token: 'valid_quick_disconnect' });
|
||||
await loginPromise;
|
||||
|
||||
// 立即断开
|
||||
client.disconnect();
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
expect(client.connected).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试套件5: 消息格式验证测试
|
||||
* 验证需求: 5.3, 5.4
|
||||
*/
|
||||
describe('消息格式验证测试', () => {
|
||||
let client: ClientSocket;
|
||||
let testId: number = 0;
|
||||
|
||||
afterEach(async () => {
|
||||
if (client?.connected) {
|
||||
client.disconnect();
|
||||
}
|
||||
// 等待清理完成
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试: 正常消息格式
|
||||
* 验证需求 5.3, 5.4: 消息应包含所有必需信息
|
||||
*/
|
||||
it('应该接受正确格式的聊天消息', async () => {
|
||||
testId++;
|
||||
client = await createClient();
|
||||
const loginPromise = waitForEvent<any>(client, 'login_success');
|
||||
client.emit('login', { type: 'login', token: `valid_format_normal_${Date.now()}_${testId}` });
|
||||
await loginPromise;
|
||||
|
||||
const chatPromise = waitForEvent<any>(client, 'chat_sent');
|
||||
client.emit('chat', {
|
||||
t: 'chat',
|
||||
content: 'Test message with correct format',
|
||||
scope: 'local'
|
||||
});
|
||||
|
||||
const response = await chatPromise;
|
||||
expect(response.t).toBe('chat_sent');
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试: 长消息处理
|
||||
* 验证需求 4.1: 系统应正确处理各种长度的消息
|
||||
*/
|
||||
it('应该正确处理较长的消息内容', async () => {
|
||||
testId++;
|
||||
client = await createClient();
|
||||
const loginPromise = waitForEvent<any>(client, 'login_success');
|
||||
client.emit('login', { type: 'login', token: `valid_format_long_${Date.now()}_${testId}` });
|
||||
await loginPromise;
|
||||
|
||||
// 使用不重复的长消息内容,避免触发重复字符检测
|
||||
const longContent = '这是一条较长的测试消息,用于验证系统能够正确处理长消息内容。' +
|
||||
'消息系统应该能够处理各种长度的消息,包括较长的消息。' +
|
||||
'这条消息包含多种字符和标点符号,以确保系统的兼容性。' +
|
||||
'测试消息继续延长,以达到足够的长度进行测试。' +
|
||||
'系统应该能够正确处理这样的消息而不会出现问题。';
|
||||
|
||||
const chatPromise = waitForEvent<any>(client, 'chat_sent');
|
||||
client.emit('chat', { t: 'chat', content: longContent, scope: 'local' });
|
||||
|
||||
const response = await chatPromise;
|
||||
expect(response.t).toBe('chat_sent');
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试: 特殊字符消息
|
||||
* 验证需求 4.1: 系统应正确处理特殊字符
|
||||
*/
|
||||
it('应该正确处理包含特殊字符的消息', async () => {
|
||||
testId++;
|
||||
client = await createClient();
|
||||
const loginPromise = waitForEvent<any>(client, 'login_success');
|
||||
client.emit('login', { type: 'login', token: `valid_format_special_${Date.now()}_${testId}` });
|
||||
await loginPromise;
|
||||
|
||||
const specialContent = '你好!@#$%^&*()_+{}|:"<>?~`-=[]\\;\',./';
|
||||
|
||||
const chatPromise = waitForEvent<any>(client, 'chat_sent');
|
||||
client.emit('chat', { t: 'chat', content: specialContent, scope: 'local' });
|
||||
|
||||
const response = await chatPromise;
|
||||
expect(response.t).toBe('chat_sent');
|
||||
});
|
||||
|
||||
/**
|
||||
* 测试: Unicode消息
|
||||
* 验证需求 4.1: 系统应正确处理Unicode字符
|
||||
*/
|
||||
it('应该正确处理Unicode消息', async () => {
|
||||
testId++;
|
||||
client = await createClient();
|
||||
const loginPromise = waitForEvent<any>(client, 'login_success');
|
||||
client.emit('login', { type: 'login', token: `valid_format_unicode_${Date.now()}_${testId}` });
|
||||
await loginPromise;
|
||||
|
||||
const unicodeContent = '🎮 游戏消息 🎯 测试 🚀';
|
||||
|
||||
const chatPromise = waitForEvent<any>(client, 'chat_sent');
|
||||
client.emit('chat', { t: 'chat', content: unicodeContent, scope: 'local' });
|
||||
|
||||
const response = await chatPromise;
|
||||
expect(response.t).toBe('chat_sent');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user