From 22ef79b05f0e7f159c141bdbe08fd28774003865 Mon Sep 17 00:00:00 2001 From: yassinedorbozgithub Date: Mon, 12 May 2025 18:18:02 +0100 Subject: [PATCH] fix(api): update WebsocketGateway unit tests --- api/src/websocket/websocket.gateway.spec.ts | 114 +++++++++++++++++--- api/src/websocket/websocket.gateway.ts | 12 +++ 2 files changed, 111 insertions(+), 15 deletions(-) diff --git a/api/src/websocket/websocket.gateway.spec.ts b/api/src/websocket/websocket.gateway.spec.ts index fc17f270..834eea81 100644 --- a/api/src/websocket/websocket.gateway.spec.ts +++ b/api/src/websocket/websocket.gateway.spec.ts @@ -8,6 +8,7 @@ import { INestApplication } from '@nestjs/common'; import { Socket, io } from 'socket.io-client'; +import { v4 as uuidv4 } from 'uuid'; import { closeInMongodConnection, @@ -16,12 +17,16 @@ import { import { buildTestingMocks } from '@/utils/test/utils'; import { SocketEventDispatcherService } from './services/socket-event-dispatcher.service'; +import { Room } from './types'; import { WebsocketGateway } from './websocket.gateway'; describe('WebsocketGateway', () => { let gateway: WebsocketGateway; let app: INestApplication; - let ioClient: Socket; + let createSocket: (index: number) => Socket; + let sockets: Socket[]; + let sessionIds: string[]; + let validSessionIds: string[]; beforeAll(async () => { // Instantiate the app @@ -39,12 +44,20 @@ describe('WebsocketGateway', () => { // Get the gateway instance from the app instance gateway = app.get(WebsocketGateway); // Create a new client that will interact with the gateway - ioClient = io('http://localhost:3000', { - autoConnect: false, - transports: ['websocket', 'polling'], - // path: '/socket.io/?EIO=4&transport=websocket&channel=web-channel', - query: { EIO: '4', transport: 'websocket', channel: 'web-channel' }, - }); + + sessionIds = [uuidv4(), uuidv4(), uuidv4()]; + validSessionIds = [sessionIds[0], sessionIds[2]]; + + createSocket = (index: number) => + io('http://localhost:3000', { + autoConnect: false, + transports: ['websocket', 'polling'], + // path: '/socket.io/?EIO=4&transport=websocket&channel=web-channel', + query: { EIO: '4', transport: 'websocket', channel: 'web-channel' }, + extraHeaders: { sessionid: sessionIds[index] }, + }); + + sockets = sessionIds.map((e, index) => createSocket(index)); await app.listen(3000); }); @@ -59,31 +72,102 @@ describe('WebsocketGateway', () => { }); it('should connect successfully', async () => { - const socketClient = ioClient.connect(); + const [socket1] = sockets; + socket1.connect(); await new Promise((resolve) => { - socketClient.on('connect', () => { + socket1.on('connect', async () => { expect(true).toBe(true); resolve(); }); }); - socketClient.disconnect(); + socket1.disconnect(); }); it('should emit "OK" on "healthcheck"', async () => { - const socketClient = ioClient.connect(); + const [socket1] = sockets; + + socket1.connect(); await new Promise((resolve) => { - socketClient.on('connect', () => { - socketClient.emit('healthcheck', 'Hello world!'); - socketClient.on('event', (data) => { + socket1.on('connect', () => { + socket1.emit('healthcheck', 'Hello world!'); + socket1.on('event', (data) => { expect(data).toBe('OK'); resolve(); }); }); }); - socketClient.disconnect(); + socket1.disconnect(); + }); + + describe('joinNotificationSockets', () => { + it('should join socket1 and socket3 to room MESSAGE', async () => { + const [socket1, , socket3] = sockets; + + [socket1, , socket3].forEach((socket) => socket?.connect()); + + for (const socket of [socket1, , socket3]) { + if (socket) { + await new Promise((resolve) => { + socket.on('connect', async () => { + resolve(); + }); + }); + } + } + + jest.spyOn(gateway, 'getNotificationSockets').mockResolvedValueOnce( + (await gateway.io.fetchSockets()).filter(({ handshake }) => { + const uuid = handshake.headers.sessionid?.toString() || ''; + + return validSessionIds.includes(uuid); + }), + ); + + await gateway.joinNotificationSockets('sessionId', Room.MESSAGE); + + expect(gateway.getNotificationSockets).toHaveBeenCalledWith('sessionId'); + + gateway.io.to(Room.MESSAGE).emit('message', { data: 'OK' }); + + for (const socket of [socket1, , socket3]) { + if (socket) + await new Promise((resolve) => { + socket.on('message', async ({ data }) => { + expect(data).toBe('OK'); + resolve(); + }); + }); + } + + sockets.forEach((socket) => socket.disconnect()); + }); + + it('should throw an error when socket array is empty', async () => { + jest.spyOn(gateway, 'getNotificationSockets').mockResolvedValueOnce([]); + + expect(gateway.getNotificationSockets).toHaveBeenCalledWith('sessionId'); + + await expect( + gateway.joinNotificationSockets('sessionId', Room.MESSAGE), + ).rejects.toThrow('No notification sockets found!'); + }); + + it('should throw an error with empty sessionId', async () => { + await expect( + gateway.joinNotificationSockets('', Room.MESSAGE), + ).rejects.toThrow('SessionId is required!'); + }); + }); + + describe('getNotificationSockets', () => { + it('should throw an error with empty sessionId', async () => { + await expect(gateway.getNotificationSockets('')).rejects.toThrow( + 'SessionId is required!', + ); + }); }); }); diff --git a/api/src/websocket/websocket.gateway.ts b/api/src/websocket/websocket.gateway.ts index 22b60681..03066397 100644 --- a/api/src/websocket/websocket.gateway.ts +++ b/api/src/websocket/websocket.gateway.ts @@ -416,6 +416,10 @@ export class WebsocketGateway async getNotificationSockets( sessionId: string, ): Promise[]> { + if (!sessionId) { + throw new Error('SessionId is required!'); + } + return (await this.io.fetchSockets()).filter( ({ handshake, data }) => !handshake.query.channel && data.sessionID === sessionId, @@ -429,8 +433,16 @@ export class WebsocketGateway * @param room - the joined room name */ async joinNotificationSockets(sessionId: string, room: Room): Promise { + if (!sessionId) { + throw new Error('SessionId is required!'); + } + const notificationSockets = await this.getNotificationSockets(sessionId); + if (!notificationSockets.length) { + throw new Error('No notification sockets found!'); + } + notificationSockets.forEach((notificationSocket) => notificationSocket.join(room), );