diff --git a/server/src/utils/socketUtils.ts b/server/src/utils/socketUtils.ts index e5c96f1..9f0780a 100644 --- a/server/src/utils/socketUtils.ts +++ b/server/src/utils/socketUtils.ts @@ -29,13 +29,15 @@ export const disconnectUserFromLobby = async ( reason: string ): Promise => { const sockets = await io.in(lobbyId).fetchSockets(); - const targetSocket = sockets.find((s: any) => s.data.user?.id === userId); + const targetSockets = sockets.filter((s: any) => s.data.user?.id === userId); - if (!targetSocket) return false; + if (targetSockets.length === 0) return false; - targetSocket.emit('FORCE_DISCONNECT', { lobbyId, reason }); - io.to(lobbyId).except(targetSocket.id).emit('USER_LEFT', targetSocket.data.user); - targetSocket.leave(lobbyId); + for (const socket of targetSockets) { + socket.emit('FORCE_DISCONNECT', { lobbyId, reason }); + io.to(lobbyId).except(socket.id).emit('USER_LEFT', socket.data.user); + socket.leave(lobbyId); + } return true; }; diff --git a/server/test/socket.utils.test.ts b/server/test/socket.utils.test.ts new file mode 100644 index 0000000..303d2d0 --- /dev/null +++ b/server/test/socket.utils.test.ts @@ -0,0 +1,54 @@ +import { describe, it, expect, vi } from 'vitest'; +import { disconnectUserFromLobby } from '../src/utils/socketUtils.js'; + +describe('socketUtils - disconnectUserFromLobby', () => { + it('should disconnect ALL sockets belonging to the same user in a lobby', async () => { + const lobbyId = 'lobby-1'; + const userId = 'user-1'; + const reason = 'test-reason'; + + // Mock sockets + const mockSocket1 = { + id: 'socket-1', + data: { user: { id: userId, username: 'user1' } }, + emit: vi.fn(), + leave: vi.fn(), + }; + const mockSocket2 = { + id: 'socket-2', + data: { user: { id: userId, username: 'user1' } }, + emit: vi.fn(), + leave: vi.fn(), + }; + const mockSocket3 = { + id: 'socket-3', + data: { user: { id: 'user-2', username: 'user2' } }, + emit: vi.fn(), + leave: vi.fn(), + }; + + // Mock IO + const mockIo = { + in: vi.fn().mockReturnThis(), + fetchSockets: vi.fn().mockResolvedValue([mockSocket1, mockSocket2, mockSocket3]), + to: vi.fn().mockReturnThis(), + except: vi.fn().mockReturnThis(), + emit: vi.fn(), + }; + + const result = await disconnectUserFromLobby(mockIo as any, lobbyId, userId, reason); + + expect(result).toBe(true); + + // We want BOTH mockSocket1 and mockSocket2 to be disconnected + expect(mockSocket1.emit).toHaveBeenCalledWith('FORCE_DISCONNECT', { lobbyId, reason }); + expect(mockSocket1.leave).toHaveBeenCalledWith(lobbyId); + + expect(mockSocket2.emit).toHaveBeenCalledWith('FORCE_DISCONNECT', { lobbyId, reason }); + expect(mockSocket2.leave).toHaveBeenCalledWith(lobbyId); + + // mockSocket3 should NOT be disconnected + expect(mockSocket3.emit).not.toHaveBeenCalled(); + expect(mockSocket3.leave).not.toHaveBeenCalled(); + }); +});