feat: integrate invitation access, world NPCs, and deployment

This commit is contained in:
2026-09-08 22:22:38 +08:00
parent 2a3125075f
commit 513a3eba31
75 changed files with 9566 additions and 1070 deletions

View File

@@ -0,0 +1,863 @@
/**
* 聊天会话管理服务测试
*
* 测试范围:
* - 会话创建和销毁
* - 位置更新和地图切换
* - 上下文注入和Stream/Topic映射
* - 过期会话清理
*
* @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 { ChatSessionService } from './chat_session.service';
describe('ChatSessionService', () => {
let service: ChatSessionService;
let redisService: any;
let configManager: any;
beforeEach(async () => {
const mockRedisService = {
set: jest.fn(),
get: jest.fn(),
setex: jest.fn(),
del: jest.fn(),
sadd: jest.fn(),
srem: jest.fn(),
smembers: jest.fn(),
expire: jest.fn(),
};
const mockConfigManager = {
getStreamByMap: jest.fn(),
findNearbyObject: jest.fn(),
getAllMapIds: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
providers: [
ChatSessionService,
{
provide: 'REDIS_SERVICE',
useValue: mockRedisService,
},
{
provide: 'ZULIP_CONFIG_SERVICE',
useValue: mockConfigManager,
},
],
}).compile();
service = module.get<ChatSessionService>(ChatSessionService);
redisService = module.get('REDIS_SERVICE');
configManager = module.get('ZULIP_CONFIG_SERVICE');
// 禁用日志输出
jest.spyOn(Logger.prototype, 'log').mockImplementation();
jest.spyOn(Logger.prototype, 'error').mockImplementation();
});
afterEach(() => {
jest.clearAllMocks();
});
describe('初始化', () => {
it('应该成功创建服务实例', () => {
expect(service).toBeDefined();
});
});
describe('createSession', () => {
const socketId = 'socket_123';
const userId = 'user_123';
const zulipQueueId = 'queue_123';
const username = 'testuser';
beforeEach(() => {
redisService.get.mockResolvedValue(null);
redisService.setex.mockResolvedValue('OK');
redisService.sadd.mockResolvedValue(1);
redisService.expire.mockResolvedValue(1);
});
it('应该成功创建会话', async () => {
const session = await service.createSession(socketId, userId, zulipQueueId, username);
expect(session).toBeDefined();
expect(session.socketId).toBe(socketId);
expect(session.userId).toBe(userId);
expect(session.username).toBe(username);
expect(session.zulipQueueId).toBe(zulipQueueId);
expect(redisService.setex).toHaveBeenCalled();
});
it('应该使用默认地图和位置', async () => {
const session = await service.createSession(socketId, userId, zulipQueueId);
expect(session.currentMap).toBe('novice_village');
expect(session.position).toEqual({ x: 400, y: 300 });
});
it('应该使用提供的初始地图和位置', async () => {
const initialMap = 'whale_port';
const initialPosition = { x: 500, y: 400 };
const session = await service.createSession(
socketId,
userId,
zulipQueueId,
username,
initialMap,
initialPosition
);
expect(session.currentMap).toBe(initialMap);
expect(session.position).toEqual(initialPosition);
});
it('应该保存初始外观到在线会话', async () => {
const appearance = {
skinId: 'girl_sailor_turnaround_v2_8x4',
avatarId: 'default',
};
const session = await service.createSession(
socketId,
userId,
zulipQueueId,
username,
'whale_port',
{ x: 500, y: 400 },
appearance
);
expect(session.appearance).toEqual(appearance);
});
it('应该拒绝空socketId', async () => {
await expect(service.createSession('', userId, zulipQueueId)).rejects.toThrow('参数不能为空');
});
it('应该拒绝空userId', async () => {
await expect(service.createSession(socketId, '', zulipQueueId)).rejects.toThrow('参数不能为空');
});
it('应该拒绝空zulipQueueId', async () => {
await expect(service.createSession(socketId, userId, '')).rejects.toThrow('参数不能为空');
});
it('应该清理旧会话', async () => {
const oldSocketId = 'old_socket_123';
redisService.get.mockResolvedValueOnce(oldSocketId);
redisService.get.mockResolvedValueOnce(JSON.stringify({
socketId: oldSocketId,
userId,
username,
zulipQueueId,
currentMap: 'novice_village',
position: { x: 400, y: 300 },
lastActivity: new Date().toISOString(),
createdAt: new Date().toISOString(),
}));
await service.createSession(socketId, userId, zulipQueueId, username);
expect(redisService.del).toHaveBeenCalled();
});
it('应该添加到地图玩家列表', async () => {
await service.createSession(socketId, userId, zulipQueueId, username);
expect(redisService.sadd).toHaveBeenCalledWith(
expect.stringContaining('chat:map_players:'),
socketId
);
});
it('应该生成默认用户名', async () => {
const session = await service.createSession(socketId, userId, zulipQueueId);
expect(session.username).toBe(`user_${userId}`);
});
});
describe('getSession', () => {
const socketId = 'socket_123';
const mockSessionData = {
socketId,
userId: 'user_123',
username: 'testuser',
zulipQueueId: 'queue_123',
currentMap: 'whale_port',
position: { x: 400, y: 300 },
lastActivity: new Date().toISOString(),
createdAt: new Date().toISOString(),
};
it('应该返回会话信息', async () => {
redisService.get.mockResolvedValue(JSON.stringify(mockSessionData));
redisService.setex.mockResolvedValue('OK');
const session = await service.getSession(socketId);
expect(session).toBeDefined();
expect(session?.socketId).toBe(socketId);
expect(session?.userId).toBe(mockSessionData.userId);
});
it('应该更新最后活动时间', async () => {
redisService.get.mockResolvedValue(JSON.stringify(mockSessionData));
redisService.setex.mockResolvedValue('OK');
await service.getSession(socketId);
expect(redisService.setex).toHaveBeenCalled();
});
it('应该处理会话不存在', async () => {
redisService.get.mockResolvedValue(null);
const session = await service.getSession(socketId);
expect(session).toBeNull();
});
it('应该拒绝空socketId', async () => {
const session = await service.getSession('');
expect(session).toBeNull();
});
it('应该处理Redis错误', async () => {
redisService.get.mockRejectedValue(new Error('Redis error'));
const session = await service.getSession(socketId);
expect(session).toBeNull();
});
});
describe('getSocketIdByUserId', () => {
const socketId = 'socket_123';
const userId = 'user_123';
const mockSessionData = {
socketId,
userId,
username: 'testuser',
zulipQueueId: 'queue_123',
currentMap: 'whale_port',
position: { x: 400, y: 300 },
lastActivity: new Date().toISOString(),
createdAt: new Date().toISOString(),
};
it('应该返回在线用户的Socket ID', async () => {
redisService.get
.mockResolvedValueOnce(socketId)
.mockResolvedValueOnce(JSON.stringify(mockSessionData));
redisService.setex.mockResolvedValue('OK');
const result = await service.getSocketIdByUserId(userId);
expect(result).toBe(socketId);
expect(redisService.get).toHaveBeenCalledWith(expect.stringContaining(`chat:user_session:${userId}`));
});
it('应该在用户没有在线映射时返回null', async () => {
redisService.get.mockResolvedValue(null);
const result = await service.getSocketIdByUserId(userId);
expect(result).toBeNull();
});
it('应该清理失效的用户会话映射', async () => {
redisService.get
.mockResolvedValueOnce(socketId)
.mockResolvedValueOnce(null);
const result = await service.getSocketIdByUserId(userId);
expect(result).toBeNull();
expect(redisService.del).toHaveBeenCalledWith(expect.stringContaining(`chat:user_session:${userId}`));
});
it('应该拒绝空userId', async () => {
const result = await service.getSocketIdByUserId('');
expect(result).toBeNull();
expect(redisService.get).not.toHaveBeenCalled();
});
});
describe('friends', () => {
const userId = 'user_123';
const friendUserId = 'user_friend';
it('应该添加好友并返回在线状态', async () => {
redisService.get
.mockResolvedValueOnce('friend_socket')
.mockResolvedValueOnce(JSON.stringify({
socketId: 'friend_socket',
userId: friendUserId,
username: 'friend',
zulipQueueId: 'queue_friend',
currentMap: 'whale_port',
position: { x: 100, y: 100 },
lastActivity: new Date().toISOString(),
createdAt: new Date().toISOString(),
}));
redisService.setex.mockResolvedValue('OK');
redisService.sadd.mockResolvedValue(1);
redisService.set.mockResolvedValue(undefined);
const friend = await service.addFriend(userId, friendUserId, 'friend');
expect(friend).toEqual({ userId: friendUserId, username: 'friend', online: true });
expect(redisService.sadd).toHaveBeenCalledWith(expect.stringContaining(`chat:friends:${userId}`), friendUserId);
expect(redisService.set).toHaveBeenCalledWith(
expect.stringContaining(`chat:friend_data:${userId}:${friendUserId}`),
expect.stringContaining('"username":"friend"'),
);
});
it('应该拒绝添加自己为好友', async () => {
await expect(service.addFriend(userId, userId, 'self')).rejects.toThrow('不能添加自己为好友');
});
it('应该创建好友请求并保存到目标用户待处理列表', async () => {
redisService.smembers.mockResolvedValue([]);
redisService.sadd.mockResolvedValue(1);
redisService.expire.mockResolvedValue(1);
redisService.setex.mockResolvedValue('OK');
const request = await service.createFriendRequest(userId, 'testuser', friendUserId);
expect(request.userId).toBe(userId);
expect(request.username).toBe('testuser');
expect(redisService.sadd).toHaveBeenCalledWith(expect.stringContaining(`chat:friend_requests:${friendUserId}`), userId);
expect(redisService.setex).toHaveBeenCalledWith(
expect.stringContaining(`chat:friend_request_data:${friendUserId}:${userId}`),
expect.any(Number),
expect.stringContaining('"username":"testuser"'),
);
});
it('应该接受好友请求并建立双向好友关系', async () => {
redisService.get
.mockResolvedValueOnce(JSON.stringify({
userId,
username: 'testuser',
createdAt: '2026-07-01T00:00:00.000Z',
}))
.mockResolvedValueOnce(null)
.mockResolvedValueOnce(null);
redisService.sadd.mockResolvedValue(1);
redisService.set.mockResolvedValue(undefined);
redisService.srem.mockResolvedValue(1);
redisService.del.mockResolvedValue(true);
const result = await service.acceptFriendRequest(friendUserId, userId, 'friend');
expect(result.friend).toEqual({ userId, username: 'testuser', online: false });
expect(result.reciprocalFriend).toEqual({ userId: friendUserId, username: 'friend', online: false });
expect(redisService.sadd).toHaveBeenCalledWith(expect.stringContaining(`chat:friends:${friendUserId}`), userId);
expect(redisService.sadd).toHaveBeenCalledWith(expect.stringContaining(`chat:friends:${userId}`), friendUserId);
expect(redisService.srem).toHaveBeenCalledWith(expect.stringContaining(`chat:friend_requests:${friendUserId}`), userId);
});
it('应该获取待处理好友请求', async () => {
redisService.smembers.mockResolvedValue([userId]);
redisService.get.mockResolvedValue(JSON.stringify({
userId,
username: 'testuser',
createdAt: '2026-07-01T00:00:00.000Z',
}));
const requests = await service.getFriendRequests(friendUserId);
expect(requests).toEqual([
{ userId, username: 'testuser', createdAt: '2026-07-01T00:00:00.000Z' },
]);
});
it('应该移除好友', async () => {
redisService.srem.mockResolvedValue(1);
redisService.del.mockResolvedValue(true);
await service.removeFriend(userId, friendUserId);
expect(redisService.srem).toHaveBeenCalledWith(expect.stringContaining(`chat:friends:${userId}`), friendUserId);
expect(redisService.del).toHaveBeenCalledWith(expect.stringContaining(`chat:friend_data:${userId}:${friendUserId}`));
});
it('应该按在线状态和用户名返回好友列表', async () => {
redisService.smembers.mockResolvedValue(['offline_friend', 'online_friend']);
redisService.get
.mockResolvedValueOnce(JSON.stringify({ userId: 'offline_friend', username: 'Beta' }))
.mockResolvedValueOnce(null)
.mockResolvedValueOnce(JSON.stringify({ userId: 'online_friend', username: 'Alpha' }))
.mockResolvedValueOnce('online_socket')
.mockResolvedValueOnce(JSON.stringify({
socketId: 'online_socket',
userId: 'online_friend',
username: 'Alpha',
zulipQueueId: 'queue_online',
currentMap: 'whale_port',
position: { x: 100, y: 100 },
lastActivity: new Date().toISOString(),
createdAt: new Date().toISOString(),
}));
redisService.setex.mockResolvedValue('OK');
const friends = await service.getFriends(userId);
expect(friends).toEqual([
{ userId: 'online_friend', username: 'Alpha', online: true },
{ userId: 'offline_friend', username: 'Beta', online: false },
]);
});
});
describe('injectContext', () => {
const socketId = 'socket_123';
const mockSessionData = {
socketId,
userId: 'user_123',
username: 'testuser',
zulipQueueId: 'queue_123',
currentMap: 'whale_port',
position: { x: 400, y: 300 },
lastActivity: new Date().toISOString(),
createdAt: new Date().toISOString(),
};
beforeEach(() => {
redisService.get.mockResolvedValue(JSON.stringify(mockSessionData));
redisService.setex.mockResolvedValue('OK');
configManager.getStreamByMap.mockReturnValue('Whale Port');
configManager.findNearbyObject.mockReturnValue(null);
});
it('应该返回正确的Stream', async () => {
const context = await service.injectContext(socketId);
expect(context.stream).toBe('Whale Port');
});
it('应该使用默认Topic', async () => {
const context = await service.injectContext(socketId);
expect(context.topic).toBe('General');
});
it('应该根据附近对象设置Topic', async () => {
configManager.findNearbyObject.mockReturnValue({
zulipTopic: 'Tavern',
});
const context = await service.injectContext(socketId);
expect(context.topic).toBe('Tavern');
});
it('应该支持指定地图ID', async () => {
configManager.getStreamByMap.mockReturnValue('Market');
const context = await service.injectContext(socketId, 'market');
expect(configManager.getStreamByMap).toHaveBeenCalledWith('market');
});
it('应该处理会话不存在', async () => {
redisService.get.mockResolvedValue(null);
const context = await service.injectContext(socketId);
expect(context.stream).toBe('General');
});
it('应该处理地图没有对应Stream', async () => {
configManager.getStreamByMap.mockReturnValue(null);
const context = await service.injectContext(socketId);
expect(context.stream).toBe('General');
});
});
describe('getSocketsInMap', () => {
const mapId = 'whale_port';
it('应该返回地图中的所有Socket', async () => {
const sockets = ['socket_1', 'socket_2', 'socket_3'];
redisService.smembers.mockResolvedValue(sockets);
const result = await service.getSocketsInMap(mapId);
expect(result).toEqual(sockets);
});
it('应该处理空地图', async () => {
redisService.smembers.mockResolvedValue([]);
const result = await service.getSocketsInMap(mapId);
expect(result).toEqual([]);
});
it('应该处理Redis错误', async () => {
redisService.smembers.mockRejectedValue(new Error('Redis error'));
const result = await service.getSocketsInMap(mapId);
expect(result).toEqual([]);
});
});
describe('getPlayersInMap', () => {
it('应该只返回同账号的当前会话并清理旧会话', async () => {
const mapId = 'whale_port';
const activeSocketId = 'socket_active';
const staleSocketId = 'socket_stale';
const createSessionData = (socketId: string, skinId: string) => JSON.stringify({
socketId,
userId: 'user_123',
username: 'testuser',
zulipQueueId: `queue_${socketId}`,
currentMap: mapId,
position: { x: 100, y: 200 },
appearance: { skinId },
lastActivity: new Date().toISOString(),
createdAt: new Date().toISOString(),
});
redisService.smembers.mockResolvedValue([staleSocketId, activeSocketId]);
redisService.get.mockImplementation(async (key: string) => {
if (key === `chat:session:${staleSocketId}`) return createSessionData(staleSocketId, 'old_skin');
if (key === `chat:session:${activeSocketId}`) return createSessionData(activeSocketId, 'generated_skin_1');
if (key === 'chat:user_session:user_123') return activeSocketId;
return null;
});
const players = await service.getPlayersInMap(mapId);
expect(players).toHaveLength(1);
expect(players[0]).toMatchObject({ socketId: activeSocketId, userId: 'user_123' });
expect(players[0].appearance?.skinId).toBe('generated_skin_1');
expect(redisService.srem).toHaveBeenCalledWith('chat:map_players:whale_port', staleSocketId);
expect(redisService.del).toHaveBeenCalledWith(`chat:session:${staleSocketId}`);
});
});
describe('updatePlayerPosition', () => {
const socketId = 'socket_123';
const mapId = 'whale_port';
const x = 500;
const y = 400;
const mockSessionData = {
socketId,
userId: 'user_123',
username: 'testuser',
zulipQueueId: 'queue_123',
currentMap: 'novice_village',
position: { x: 400, y: 300 },
lastActivity: new Date().toISOString(),
createdAt: new Date().toISOString(),
};
beforeEach(() => {
redisService.get.mockResolvedValue(JSON.stringify(mockSessionData));
redisService.setex.mockResolvedValue('OK');
redisService.srem.mockResolvedValue(1);
redisService.sadd.mockResolvedValue(1);
redisService.expire.mockResolvedValue(1);
});
it('应该成功更新位置', async () => {
const result = await service.updatePlayerPosition(socketId, mapId, x, y);
expect(result).toBe(true);
expect(redisService.setex).toHaveBeenCalled();
});
it('应该更新地图玩家列表当切换地图', async () => {
await service.updatePlayerPosition(socketId, mapId, x, y);
expect(redisService.srem).toHaveBeenCalled();
expect(redisService.sadd).toHaveBeenCalled();
});
it('应该不更新地图玩家列表当在同一地图', async () => {
const sameMapData = { ...mockSessionData, currentMap: mapId };
redisService.get.mockResolvedValue(JSON.stringify(sameMapData));
await service.updatePlayerPosition(socketId, mapId, x, y);
expect(redisService.srem).not.toHaveBeenCalled();
});
it('应该拒绝空socketId', async () => {
const result = await service.updatePlayerPosition('', mapId, x, y);
expect(result).toBe(false);
});
it('应该拒绝空mapId', async () => {
const result = await service.updatePlayerPosition(socketId, '', x, y);
expect(result).toBe(false);
});
it('应该处理会话不存在', async () => {
redisService.get.mockResolvedValue(null);
const result = await service.updatePlayerPosition(socketId, mapId, x, y);
expect(result).toBe(false);
});
it('应该处理Redis错误', async () => {
redisService.get.mockRejectedValue(new Error('Redis error'));
const result = await service.updatePlayerPosition(socketId, mapId, x, y);
expect(result).toBe(false);
});
});
describe('destroySession', () => {
const socketId = 'socket_123';
const mockSessionData = {
socketId,
userId: 'user_123',
username: 'testuser',
zulipQueueId: 'queue_123',
currentMap: 'whale_port',
position: { x: 400, y: 300 },
lastActivity: new Date().toISOString(),
createdAt: new Date().toISOString(),
};
it('旧连接销毁时不应删除同账号的新会话映射', async () => {
redisService.get.mockImplementation(async (key: string) => {
if (key === `chat:session:${socketId}`) return JSON.stringify(mockSessionData);
if (key === 'chat:user_session:user_123') return 'socket_new';
return null;
});
const result = await service.destroySession(socketId);
expect(result).toBe(true);
expect(redisService.del).toHaveBeenCalledWith(`chat:session:${socketId}`);
expect(redisService.del).not.toHaveBeenCalledWith('chat:user_session:user_123');
});
beforeEach(() => {
redisService.get.mockImplementation(async (key: string) => {
if (key === `chat:session:${socketId}`) return JSON.stringify(mockSessionData);
if (key === 'chat:user_session:user_123') return socketId;
return null;
});
redisService.srem.mockResolvedValue(1);
redisService.del.mockResolvedValue(1);
});
it('应该成功销毁会话', async () => {
const result = await service.destroySession(socketId);
expect(result).toBe(true);
expect(redisService.del).toHaveBeenCalledTimes(2);
});
it('应该从地图玩家列表移除', async () => {
await service.destroySession(socketId);
expect(redisService.srem).toHaveBeenCalled();
});
it('应该删除用户会话映射', async () => {
await service.destroySession(socketId);
expect(redisService.del).toHaveBeenCalledWith(
expect.stringContaining('chat:user_session:')
);
});
it('应该处理会话不存在', async () => {
redisService.get.mockResolvedValue(null);
const result = await service.destroySession(socketId);
expect(result).toBe(true);
});
it('应该拒绝空socketId', async () => {
const result = await service.destroySession('');
expect(result).toBe(false);
});
it('应该处理Redis错误', async () => {
redisService.get.mockRejectedValue(new Error('Redis error'));
const result = await service.destroySession(socketId);
expect(result).toBe(false);
});
});
describe('cleanupExpiredSessions', () => {
beforeEach(() => {
configManager.getAllMapIds.mockReturnValue(['novice_village', 'whale_port']);
});
it('应该清理过期会话', async () => {
const expiredSession = {
socketId: 'socket_123',
userId: 'user_123',
username: 'testuser',
zulipQueueId: 'queue_123',
currentMap: 'whale_port',
position: { x: 400, y: 300 },
lastActivity: new Date(Date.now() - 60 * 60 * 1000).toISOString(),
createdAt: new Date().toISOString(),
};
redisService.smembers.mockResolvedValue(['socket_123']);
redisService.get.mockResolvedValueOnce(JSON.stringify(expiredSession));
redisService.get.mockResolvedValueOnce(JSON.stringify(expiredSession));
redisService.srem.mockResolvedValue(1);
redisService.del.mockResolvedValue(1);
const result = await service.cleanupExpiredSessions(30);
expect(result.cleanedCount).toBeGreaterThanOrEqual(1);
expect(result.zulipQueueIds).toContain('queue_123');
});
it('应该不清理未过期会话', async () => {
const activeSession = {
socketId: 'socket_123',
userId: 'user_123',
username: 'testuser',
zulipQueueId: 'queue_123',
currentMap: 'whale_port',
position: { x: 400, y: 300 },
lastActivity: new Date().toISOString(),
createdAt: new Date().toISOString(),
};
redisService.smembers.mockResolvedValue(['socket_123']);
redisService.get.mockResolvedValue(JSON.stringify(activeSession));
const result = await service.cleanupExpiredSessions(30);
expect(result.cleanedCount).toBe(0);
});
it('应该处理多个地图', async () => {
redisService.smembers.mockResolvedValue([]);
const result = await service.cleanupExpiredSessions(30);
expect(redisService.smembers).toHaveBeenCalledTimes(2);
expect(result.cleanedCount).toBe(0);
});
it('应该使用默认地图当配置为空', async () => {
configManager.getAllMapIds.mockReturnValue([]);
redisService.smembers.mockResolvedValue([]);
const result = await service.cleanupExpiredSessions(30);
expect(result.cleanedCount).toBe(0);
});
it('应该处理清理过程中的错误', async () => {
redisService.smembers.mockRejectedValue(new Error('Redis error'));
const result = await service.cleanupExpiredSessions(30);
expect(result.cleanedCount).toBe(0);
expect(result.zulipQueueIds).toEqual([]);
});
it('应该清理不存在的会话数据', async () => {
redisService.smembers.mockResolvedValue(['socket_123']);
redisService.get.mockResolvedValue(null);
redisService.srem.mockResolvedValue(1);
const result = await service.cleanupExpiredSessions(30);
expect(redisService.srem).toHaveBeenCalled();
});
});
describe('边界情况', () => {
it('应该处理极大的坐标值', async () => {
const socketId = 'socket_123';
const userId = 'user_123';
const zulipQueueId = 'queue_123';
redisService.get.mockResolvedValue(null);
redisService.setex.mockResolvedValue('OK');
redisService.sadd.mockResolvedValue(1);
redisService.expire.mockResolvedValue(1);
const session = await service.createSession(
socketId,
userId,
zulipQueueId,
'testuser',
'whale_port',
{ x: 999999, y: 999999 }
);
expect(session.position).toEqual({ x: 999999, y: 999999 });
});
it('应该处理负坐标值', async () => {
const socketId = 'socket_123';
const userId = 'user_123';
const zulipQueueId = 'queue_123';
redisService.get.mockResolvedValue(null);
redisService.setex.mockResolvedValue('OK');
redisService.sadd.mockResolvedValue(1);
redisService.expire.mockResolvedValue(1);
const session = await service.createSession(
socketId,
userId,
zulipQueueId,
'testuser',
'whale_port',
{ x: -100, y: -100 }
);
expect(session.position).toEqual({ x: -100, y: -100 });
});
it('应该处理特殊字符的用户名', async () => {
const socketId = 'socket_123';
const userId = 'user_123';
const zulipQueueId = 'queue_123';
const username = 'test@user#123';
redisService.get.mockResolvedValue(null);
redisService.setex.mockResolvedValue('OK');
redisService.sadd.mockResolvedValue(1);
redisService.expire.mockResolvedValue(1);
const session = await service.createSession(socketId, userId, zulipQueueId, username);
expect(session.username).toBe(username);
});
});
});