From d196f2da73b2f1bb961aa87c55e574f87cd327d7 Mon Sep 17 00:00:00 2001 From: Matteo Susca Date: Sun, 12 Apr 2026 19:15:22 +0200 Subject: [PATCH 1/2] fix(socket): disconnect all user sessions from lobby on kick/ban --- server/src/utils/socketUtils.ts | 12 ++++--- server/test/socket.utils.test.ts | 55 ++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 server/test/socket.utils.test.ts 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..da6827f --- /dev/null +++ b/server/test/socket.utils.test.ts @@ -0,0 +1,55 @@ +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); + + // RED PHASE: Currently it only disconnects the first one + // 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(); + }); +}); From 707bb9c8794297d8cb2fb0952d81130712b99c03 Mon Sep 17 00:00:00 2001 From: Matteo Susca Date: Sun, 12 Apr 2026 19:18:36 +0200 Subject: [PATCH 2/2] test: removed useless comment --- server/test/socket.utils.test.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/server/test/socket.utils.test.ts b/server/test/socket.utils.test.ts index da6827f..303d2d0 100644 --- a/server/test/socket.utils.test.ts +++ b/server/test/socket.utils.test.ts @@ -39,15 +39,14 @@ describe('socketUtils - disconnectUserFromLobby', () => { const result = await disconnectUserFromLobby(mockIo as any, lobbyId, userId, reason); expect(result).toBe(true); - - // RED PHASE: Currently it only disconnects the first one + // 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();