From 7b85f5882f9cabe3f97a1cc053bfac8da36e6a8b Mon Sep 17 00:00:00 2001 From: Andres Pinto Date: Fri, 19 Sep 2025 12:11:49 -0400 Subject: [PATCH] chore: fix unit tests --- src/modules/call/call.controller.spec.ts | 28 +-- src/modules/call/call.controller.ts | 2 +- src/modules/call/call.usecase.spec.ts | 203 +++++++++++------ src/modules/call/fixtures.ts | 41 ++++ .../room-user.repository.spec.ts | 8 +- .../room.repository.spec.ts | 6 +- .../call/services/room.service.spec.ts | 214 +++++++++++++++++- src/modules/call/services/room.service.ts | 62 +---- .../jitsi/jitsi-webhook.controller.spec.ts | 23 +- .../jitsi/jitsi-webhook.service.spec.ts | 122 +--------- 10 files changed, 424 insertions(+), 285 deletions(-) rename src/modules/call/{ => infrastructure}/room-user.repository.spec.ts (97%) rename src/modules/call/{ => infrastructure}/room.repository.spec.ts (96%) diff --git a/src/modules/call/call.controller.spec.ts b/src/modules/call/call.controller.spec.ts index d00966f..faaf1dd 100644 --- a/src/modules/call/call.controller.spec.ts +++ b/src/modules/call/call.controller.spec.ts @@ -52,6 +52,7 @@ describe('Testing Call Endpoints', () => { const module: TestingModule = await Test.createTestingModule({ controllers: [CallController], }) + .setLogger(createMock()) .useMocker(createMock) .compile(); @@ -116,7 +117,7 @@ describe('Testing Call Endpoints', () => { }); describe('Joining a call', () => { - it('should join a call with authenticated user', async () => { + it('When joining a call with authenticated user, then it should join successfully', async () => { callUseCase.joinCall.mockResolvedValue(mockJoinCallResponse); const user = mockUserPayload; @@ -136,7 +137,7 @@ describe('Testing Call Endpoints', () => { }); }); - it('should join a call with anonymous user (no JWT)', async () => { + it('When joining a call with anonymous user (no JWT), then it should join successfully', async () => { callUseCase.joinCall.mockResolvedValue(mockJoinCallResponse); const result = await callController.joinCall( @@ -148,14 +149,15 @@ describe('Testing Call Endpoints', () => { expect(result).toEqual(mockJoinCallResponse); expect(callUseCase.joinCall).toHaveBeenCalledWith(mockRoomId, { userId: undefined, - email: mockJoinCallDto.email, + email: undefined, name: mockJoinCallDto.name, lastName: mockJoinCallDto.lastName, anonymous: true, + anonymousId: undefined, }); }); - it('should set anonymous=true if anonymous flag is explicitly set', async () => { + it('When anonymous flag is explicitly set, then user should join as anonymous', async () => { callUseCase.joinCall.mockResolvedValue(mockJoinCallResponse); const anonymousDto = { ...mockJoinCallDto, anonymous: true }; @@ -267,7 +269,7 @@ describe('Testing Call Endpoints', () => { }); describe('Getting users in a call', () => { - it('should get users in a call for authenticated user', async () => { + it('When getting users in a call for authenticated user, then it should return users list', async () => { roomService.getUsersInRoom.mockResolvedValue(mockUsersInRoom); const result = await callController.getUsersInCall(mockRoomId); @@ -276,7 +278,7 @@ describe('Testing Call Endpoints', () => { expect(roomService.getUsersInRoom).toHaveBeenCalledWith(mockRoomId); }); - it('should get users in a call for anonymous user', async () => { + it('When getting users in a call for anonymous user, then it should return users list', async () => { roomService.getUsersInRoom.mockResolvedValue(mockUsersInRoom); const result = await callController.getUsersInCall(mockRoomId); @@ -307,7 +309,7 @@ describe('Testing Call Endpoints', () => { expect(roomService.getUsersInRoom).toHaveBeenCalledWith(mockRoomId); }); - it('When no users are in the room, it should return an empty array', async () => { + it('When no users are in the room, then it should return an empty array', async () => { roomService.getUsersInRoom.mockResolvedValue([]); const result = await callController.getUsersInCall(mockRoomId); @@ -319,7 +321,7 @@ describe('Testing Call Endpoints', () => { }); describe('Leaving a call', () => { - it('should leave a call for authenticated user', async () => { + it('When leaving a call for authenticated user, then it should leave successfully', async () => { callUseCase.leaveCall.mockResolvedValue(); const result = await callController.leaveCall( @@ -334,7 +336,7 @@ describe('Testing Call Endpoints', () => { ); }); - it('should call leaveCall with userId from DTO when user is anonymous', async () => { + it('When user is anonymous, then it should call leaveCall with userId from DTO', async () => { const anonymousUserId = 'anonymous-user-id'; const leaveCallDto = new LeaveCallDto(); leaveCallDto.userId = anonymousUserId; @@ -349,7 +351,7 @@ describe('Testing Call Endpoints', () => { ); }); - it('should prioritize user UUID when both authenticated user and DTO are provided', async () => { + it('When both authenticated user and DTO are provided, then it should prioritize user UUID', async () => { const leaveCallDto = new LeaveCallDto(); leaveCallDto.userId = 'anonymous-user-id'; @@ -368,7 +370,7 @@ describe('Testing Call Endpoints', () => { ); }); - it('should pass undefined when neither authenticated user nor DTO with userId are provided', async () => { + it('When neither authenticated user nor DTO with userId are provided, then it should pass undefined', async () => { const emptyDto = new LeaveCallDto(); callUseCase.leaveCall.mockResolvedValue(); @@ -377,7 +379,7 @@ describe('Testing Call Endpoints', () => { expect(callUseCase.leaveCall).toHaveBeenCalledWith(mockRoomId, undefined); }); - it('should propagate NotFoundException when room is not found', async () => { + it('When room is not found, then it should propagate NotFoundException', async () => { const userToken = createMockUserToken(); callUseCase.leaveCall.mockRejectedValue( @@ -389,7 +391,7 @@ describe('Testing Call Endpoints', () => { ).rejects.toThrow(NotFoundException); }); - it('should propagate BadRequestException when no userId is provided', async () => { + it('When no userId is provided, then it should propagate BadRequestException', async () => { callUseCase.leaveCall.mockRejectedValue( new BadRequestException('User ID is required'), ); diff --git a/src/modules/call/call.controller.ts b/src/modules/call/call.controller.ts index b02f3a8..eb6fe63 100644 --- a/src/modules/call/call.controller.ts +++ b/src/modules/call/call.controller.ts @@ -120,7 +120,7 @@ export class CallController { ): Promise { const { uuid, email } = user || {}; const isUserAnonymous = - (!!joinCallDto?.anonymousId || joinCallDto?.anonymous) ?? !user; + !user || !!joinCallDto?.anonymousId || joinCallDto?.anonymous === true; return await this.callUseCase.joinCall(roomId, { userId: uuid, diff --git a/src/modules/call/call.usecase.spec.ts b/src/modules/call/call.usecase.spec.ts index 10ee906..8cf6099 100644 --- a/src/modules/call/call.usecase.spec.ts +++ b/src/modules/call/call.usecase.spec.ts @@ -14,8 +14,10 @@ import { CallUseCase } from './call.usecase'; import { mockRoomData, mockUserPayload } from './fixtures'; import { v4 } from 'uuid'; +const autoGeneratedUUID = 'generated-uuid'; + jest.mock('uuid', () => ({ - v4: jest.fn(() => 'generated-uuid'), + v4: jest.fn(() => autoGeneratedUUID), })); describe('CallUseCase', () => { @@ -36,7 +38,7 @@ describe('CallUseCase', () => { }); describe('validateUserHasNoActiveRoom', () => { - it('when user has no active room, then it should not throw', async () => { + it('When user has no active room, then it should not throw', async () => { roomService.getOpenRoomByHostId.mockResolvedValueOnce(null); await expect( @@ -51,7 +53,7 @@ describe('CallUseCase', () => { ); }); - it('when user already has an active room, then it should throw', async () => { + it('When user already has an active room, then it should throw', async () => { roomService.getOpenRoomByHostId.mockResolvedValueOnce( createMock(mockRoomData), ); @@ -77,7 +79,7 @@ describe('CallUseCase', () => { appId: 'jitsi-app-id', }; - it('when creating call and room and user does not have an active room, then should create a call token and room successfully', async () => { + it('When creating call and room and user does not have an active room, then it should create call token and room successfully', async () => { callService.createCallToken.mockResolvedValueOnce(mockCallToken); roomService.createRoom.mockResolvedValueOnce(undefined); jest @@ -122,7 +124,7 @@ describe('CallUseCase', () => { anonymous: true, }); - it('when joining call with registered user data, then should join successfully', async () => { + it('When joining call with registered user data, then it should join successfully with current user data', async () => { const userData = { userId, name: userName, @@ -130,18 +132,24 @@ describe('CallUseCase', () => { }; roomService.getRoomByRoomId.mockResolvedValueOnce(roomMock); - roomService.addUserToRoom.mockResolvedValueOnce(roomUserMock); + roomService.getUserInRoom.mockResolvedValueOnce(null); + roomService.countUsersInRoom.mockResolvedValueOnce(0); + roomService.createUserInRoom.mockResolvedValueOnce(roomUserMock); callService.createCallTokenForParticipant.mockReturnValueOnce(callToken); const result = await callUseCase.joinCall(roomId, userData); expect(roomService.getRoomByRoomId).toHaveBeenCalledWith(roomId); - expect(roomService.addUserToRoom).toHaveBeenCalledWith(roomId, { + expect(roomService.getUserInRoom).toHaveBeenCalledWith( + userId, + roomMock.id, + ); + expect(roomService.createUserInRoom).toHaveBeenCalledWith({ + roomId: roomMock.id, userId, name: userName, lastName: userLastName, anonymous: false, - email: undefined, }); expect(callService.createCallTokenForParticipant).toHaveBeenCalledWith( userId, @@ -151,9 +159,9 @@ describe('CallUseCase', () => { { anonymous: false, email: undefined, - lastName: 'Last Name', - name: 'Test User', - userId: 'test-user-id', + lastName: userData.lastName, + name: userData.name, + userId: userData.userId, }, ); expect(result).toEqual({ @@ -164,22 +172,29 @@ describe('CallUseCase', () => { }); }); - it('when joining call as anonymous user, then should join successfully', async () => { + it('When joining call as anonymous user without anonymousId, then it should auto-generate UUID', async () => { const userData = { anonymous: true, name: userName, }; roomService.getRoomByRoomId.mockResolvedValueOnce(roomMock); - roomService.addUserToRoom.mockResolvedValueOnce(anonymousUserMock); + roomService.getUserInRoom.mockResolvedValueOnce(null); + roomService.countUsersInRoom.mockResolvedValueOnce(0); + roomService.createUserInRoom.mockResolvedValueOnce(anonymousUserMock); callService.createCallTokenForParticipant.mockReturnValueOnce(callToken); const result = await callUseCase.joinCall(roomId, userData); expect(roomService.getRoomByRoomId).toHaveBeenCalledWith(roomId); - expect(roomService.addUserToRoom).toHaveBeenCalledWith( - roomId, + expect(roomService.getUserInRoom).toHaveBeenCalledWith( + autoGeneratedUUID, + roomMock.id, + ); + expect(roomService.createUserInRoom).toHaveBeenCalledWith( expect.objectContaining({ + roomId: roomMock.id, + userId: autoGeneratedUUID, name: userName, anonymous: true, }), @@ -205,12 +220,61 @@ describe('CallUseCase', () => { }); }); - it('when joining call as host, then should join successfully and open the room', async () => { + it('When joining call as anonymous user with provided anonymousId, then it should use provided ID', async () => { + const customAnonymousId = v4(); + const userData = { + anonymous: true, + anonymousId: customAnonymousId, + name: userName, + }; + + const customAnonymousUserMock = new RoomUser({ + id: 3, + roomId, + userId: customAnonymousId, + name: userName, + anonymous: true, + }); + + roomService.getRoomByRoomId.mockResolvedValueOnce(roomMock); + roomService.getUserInRoom.mockResolvedValueOnce(null); + roomService.countUsersInRoom.mockResolvedValueOnce(0); + roomService.createUserInRoom.mockResolvedValueOnce( + customAnonymousUserMock, + ); + callService.createCallTokenForParticipant.mockReturnValueOnce(callToken); + + const result = await callUseCase.joinCall(roomId, userData); + + expect(roomService.getRoomByRoomId).toHaveBeenCalledWith(roomId); + expect(roomService.getUserInRoom).toHaveBeenCalledWith( + customAnonymousId, + roomMock.id, + ); + expect(roomService.createUserInRoom).toHaveBeenCalledWith( + expect.objectContaining({ + roomId: roomMock.id, + userId: customAnonymousId, + name: userName, + anonymous: true, + }), + ); + expect(result).toEqual({ + token: callToken.token, + room: roomId, + userId: customAnonymousUserMock.userId, + appId: callToken.appId, + }); + }); + + it('When joining call as host and room is closed, then it should join successfully and open the room', async () => { const userData = { userId: roomMock.hostId }; const closedRoomMock = { ...roomMock, isClosed: true }; roomService.getRoomByRoomId.mockResolvedValueOnce(closedRoomMock); - roomService.addUserToRoom.mockResolvedValueOnce(roomUserMock); + roomService.getUserInRoom.mockResolvedValueOnce(null); + roomService.countUsersInRoom.mockResolvedValueOnce(0); + roomService.createUserInRoom.mockResolvedValueOnce(roomUserMock); callService.createCallTokenForParticipant.mockReturnValueOnce(callToken); roomService.openRoom.mockResolvedValueOnce(); @@ -219,7 +283,7 @@ describe('CallUseCase', () => { expect(roomService.openRoom).toHaveBeenCalledWith(roomId); }); - it('when room does not exist, then should throw NotFoundException', async () => { + it('When room does not exist, then it should throw', async () => { const userData = { userId }; roomService.getRoomByRoomId.mockResolvedValueOnce(null); @@ -230,7 +294,7 @@ describe('CallUseCase', () => { expect(roomService.getRoomByRoomId).toHaveBeenCalledWith(roomId); }); - it('when non-owner tries to join closed room, then should throw', async () => { + it('When non-owner tries to join closed room, then it should throw', async () => { const userData = { userId }; const closedRoomMock = { ...roomMock, @@ -247,55 +311,50 @@ describe('CallUseCase', () => { expect(roomService.getRoomByRoomId).toHaveBeenCalledWith(roomId); }); - it('when roomService throws BadRequestException, then should propagate error', async () => { + it('When room is full and user not already in room, then it should throw', async () => { const userData = { userId }; - const error = new BadRequestException('Invalid user data'); - const openRoomMock = { ...roomMock, isClosed: false }; + const openRoomMock = { ...roomMock, isClosed: false, maxUsersAllowed: 2 }; roomService.getRoomByRoomId.mockResolvedValueOnce(openRoomMock); - roomService.addUserToRoom.mockRejectedValueOnce(error); + roomService.getUserInRoom.mockResolvedValueOnce(null); + roomService.countUsersInRoom.mockResolvedValueOnce(2); await expect(callUseCase.joinCall(roomId, userData)).rejects.toThrow( BadRequestException, ); expect(roomService.getRoomByRoomId).toHaveBeenCalledWith(roomId); - expect(roomService.addUserToRoom).toHaveBeenCalledWith( - roomId, - expect.objectContaining({ - userId, - anonymous: false, - }), + expect(roomService.getUserInRoom).toHaveBeenCalledWith( + userId, + openRoomMock.id, + ); + expect(roomService.countUsersInRoom).toHaveBeenCalledWith( + openRoomMock.id, ); }); - it('when roomService throws ConflictException, then should propagate error', async () => { + it('When user already exists in room, then it should return existing user', async () => { const userData = { userId }; - const error = new ConflictException('User already in room'); const openRoomMock = { ...roomMock, isClosed: false }; roomService.getRoomByRoomId.mockResolvedValueOnce(openRoomMock); - roomService.addUserToRoom.mockRejectedValueOnce(error); - - await expect(callUseCase.joinCall(roomId, userData)).rejects.toThrow( - ConflictException, - ); - }); - - it('When there is an unknown error, then propagate the error', async () => { - const userData = { userId }; - const error = new Error('Unknown error'); + roomService.getUserInRoom.mockResolvedValueOnce(roomUserMock); + roomService.countUsersInRoom.mockResolvedValueOnce(1); + callService.createCallTokenForParticipant.mockReturnValueOnce(callToken); - roomService.getRoomByRoomId.mockRejectedValueOnce(error); + const result = await callUseCase.joinCall(roomId, userData); - await expect(callUseCase.joinCall(roomId, userData)).rejects.toThrow( - Error, + expect(roomService.getUserInRoom).toHaveBeenCalledWith( + userId, + openRoomMock.id, ); + expect(roomService.createUserInRoom).not.toHaveBeenCalled(); + expect(result.userId).toEqual(roomUserMock.userId); }); }); describe('processUserData', () => { - it('should handle registered user data correctly', async () => { + it('When handling registered user data, then it should process correctly', async () => { const roomId = 'test-room-id'; const userId = 'test-user-id'; const name = 'Test User'; @@ -319,7 +378,9 @@ describe('CallUseCase', () => { anonymous: false, }); - roomService.addUserToRoom.mockResolvedValueOnce(registeredRoomUser); + roomService.getUserInRoom.mockResolvedValueOnce(null); + roomService.countUsersInRoom.mockResolvedValueOnce(0); + roomService.createUserInRoom.mockResolvedValueOnce(registeredRoomUser); const createCallTokenForParticipantSpy = jest .spyOn(callService, 'createCallTokenForParticipant') .mockReturnValueOnce(callToken); @@ -346,7 +407,7 @@ describe('CallUseCase', () => { ); }); - it('should handle anonymous user data correctly', async () => { + it('When handling anonymous user data, then it should process correctly', async () => { const roomId = 'test-room-id'; const name = 'Anonymous User'; const callToken = { @@ -362,13 +423,15 @@ describe('CallUseCase', () => { const anonymousRoomUser = new RoomUser({ id: 1, - roomId, - userId: 'generated-id', + roomId: openRoomMock.id, + userId: autoGeneratedUUID, name, anonymous: true, }); - roomService.addUserToRoom.mockResolvedValueOnce(anonymousRoomUser); + roomService.getUserInRoom.mockResolvedValueOnce(null); + roomService.countUsersInRoom.mockResolvedValueOnce(0); + roomService.createUserInRoom.mockResolvedValueOnce(anonymousRoomUser); callService.createCallTokenForParticipant.mockReturnValueOnce(callToken); await callUseCase.joinCall(roomId, { @@ -376,16 +439,17 @@ describe('CallUseCase', () => { anonymous: true, }); - expect(roomService.addUserToRoom).toHaveBeenCalledWith( - roomId, + expect(roomService.createUserInRoom).toHaveBeenCalledWith( expect.objectContaining({ + roomId: openRoomMock.id, + userId: autoGeneratedUUID, name, anonymous: true, }), ); }); - it('should generate user ID when userId is not provided', async () => { + it('When user has no userId, then it should handle as anonymous with undefined userId', async () => { const roomId = 'test-room-id'; const name = 'User without ID'; const callToken = { @@ -401,22 +465,25 @@ describe('CallUseCase', () => { const userWithoutId = new RoomUser({ id: 1, - roomId, - userId: 'generated-id', + roomId: openRoomMock.id, + userId: undefined, name, anonymous: true, }); - roomService.addUserToRoom.mockResolvedValueOnce(userWithoutId); + roomService.getUserInRoom.mockResolvedValueOnce(null); + roomService.countUsersInRoom.mockResolvedValueOnce(0); + roomService.createUserInRoom.mockResolvedValueOnce(userWithoutId); callService.createCallTokenForParticipant.mockReturnValueOnce(callToken); await callUseCase.joinCall(roomId, { name, }); - expect(roomService.addUserToRoom).toHaveBeenCalledWith( - roomId, + expect(roomService.createUserInRoom).toHaveBeenCalledWith( expect.objectContaining({ + roomId: openRoomMock.id, + userId: undefined, name, anonymous: true, }), @@ -439,7 +506,7 @@ describe('CallUseCase', () => { roomService.removeRoom.mockResolvedValue(); }); - it('when a valid userId is provided, then should handle leave call normally', async () => { + it('When a valid userId is provided, then it should handle leave call normally', async () => { roomService.removeUserFromRoom.mockResolvedValueOnce(undefined); roomService.countUsersInRoom.mockResolvedValueOnce(0); @@ -453,7 +520,7 @@ describe('CallUseCase', () => { expect(roomService.removeRoom).toHaveBeenCalledWith(roomId); }); - it('when room does not exist, then should throw', async () => { + it('When room does not exist, then it should throw', async () => { roomService.getRoomByRoomId.mockResolvedValueOnce(null); await expect( @@ -465,7 +532,7 @@ describe('CallUseCase', () => { expect(roomService.removeRoom).not.toHaveBeenCalled(); }); - it('when host leaves a non-empty room, then should remove user and close room', async () => { + it('When host leaves a non-empty room, then it should remove user and close room', async () => { roomService.countUsersInRoom.mockResolvedValueOnce(1); await callUseCase.leaveCall(roomId, hostId); @@ -480,7 +547,7 @@ describe('CallUseCase', () => { expect(roomService.removeRoom).not.toHaveBeenCalled(); }); - it('when the last user (host) leaves, then should remove user and delete room', async () => { + it('When the last user (host) leaves, then it should remove user and delete room', async () => { roomService.countUsersInRoom.mockResolvedValueOnce(0); await callUseCase.leaveCall(roomId, hostId); @@ -495,7 +562,7 @@ describe('CallUseCase', () => { expect(roomService.closeRoom).not.toHaveBeenCalled(); }); - it('when the last user (participant) leaves, then should remove user and delete room', async () => { + it('When the last user (participant) leaves, then it should remove user and delete room', async () => { roomService.countUsersInRoom.mockResolvedValueOnce(0); await callUseCase.leaveCall(roomId, participantId); @@ -510,7 +577,7 @@ describe('CallUseCase', () => { expect(roomService.closeRoom).not.toHaveBeenCalled(); }); - it('when a participant leaves a non-empty room, then should remove user but not close or delete room', async () => { + it('When a participant leaves a non-empty room, then it should remove user but not close or delete room', async () => { roomService.countUsersInRoom.mockResolvedValueOnce(2); await callUseCase.leaveCall(roomId, participantId); @@ -525,7 +592,7 @@ describe('CallUseCase', () => { expect(roomService.removeRoom).not.toHaveBeenCalled(); }); - it('when host leaves call, then should leave successfully and close the room', async () => { + it('When host leaves call, then it should leave successfully and close the room', async () => { roomService.countUsersInRoom.mockResolvedValueOnce(1); roomMock.hostId = hostId; @@ -541,7 +608,7 @@ describe('CallUseCase', () => { expect(roomService.removeRoom).not.toHaveBeenCalled(); }); - it('when anonymous user leaves call, then should leave successfully', async () => { + it('When anonymous user leaves call, then it should leave successfully', async () => { roomService.countUsersInRoom.mockResolvedValueOnce(1); await callUseCase.leaveCall(roomId, anonymousUserId); @@ -554,7 +621,7 @@ describe('CallUseCase', () => { expect(roomService.countUsersInRoom).toHaveBeenCalledWith(roomId); }); - it('when error occurs during leave call operation, then should propagate error', async () => { + it('When error occurs during leave call operation, then it should propagate error', async () => { const error = new Error('Database error'); roomService.getRoomByRoomId.mockRejectedValueOnce(error); @@ -563,7 +630,7 @@ describe('CallUseCase', () => { ).rejects.toThrow(error); }); - it('when roomService throws, then should propagate error', async () => { + it('When roomService throws, then it should propagate error', async () => { const error = new BadRequestException('Invalid user'); roomService.getRoomByRoomId.mockResolvedValueOnce(roomMock); roomService.removeUserFromRoom.mockRejectedValueOnce(error); diff --git a/src/modules/call/fixtures.ts b/src/modules/call/fixtures.ts index a0bf81e..1bc4531 100644 --- a/src/modules/call/fixtures.ts +++ b/src/modules/call/fixtures.ts @@ -2,6 +2,8 @@ import { Chance } from 'chance'; import { UserTokenData } from '../auth/dto/user.dto'; import { Room } from './domain/room.domain'; import { CreateCallResponseDto } from './dto/create-call.dto'; +import { RoomUser, RoomUserAttributes } from './domain/room-user.domain'; +import { User } from '../../shared/user/user.domain'; const randomDataGenerator = new Chance(); @@ -58,3 +60,42 @@ export const createMockCallResponse = ( ...mockCallResponse, ...overrides, }); + +export const createMockRoomUser = ( + overrides?: Partial, +): RoomUser => { + const mockRoomUserData: RoomUserAttributes = { + id: randomDataGenerator.integer({ min: 1, max: 1000 }), + roomId: randomDataGenerator.guid(), + userId: randomDataGenerator.guid(), + name: randomDataGenerator.first(), + lastName: randomDataGenerator.last(), + anonymous: false, + createdAt: randomDataGenerator.date(), + updatedAt: randomDataGenerator.date(), + }; + + return new RoomUser({ + ...mockRoomUserData, + ...overrides, + }); +}; + +export const createMockUser = (overrides?: Partial): User => { + const mockUserData = { + id: randomDataGenerator.integer({ min: 1, max: 1000 }), + userId: randomDataGenerator.guid(), + name: randomDataGenerator.first(), + lastname: randomDataGenerator.last(), + email: randomDataGenerator.email(), + username: randomDataGenerator.name(), + uuid: randomDataGenerator.guid(), + avatar: randomDataGenerator.avatar(), + createdAt: randomDataGenerator.date(), + updatedAt: randomDataGenerator.date(), + }; + return new User({ + ...mockUserData, + ...overrides, + }); +}; diff --git a/src/modules/call/room-user.repository.spec.ts b/src/modules/call/infrastructure/room-user.repository.spec.ts similarity index 97% rename from src/modules/call/room-user.repository.spec.ts rename to src/modules/call/infrastructure/room-user.repository.spec.ts index be4dbfa..5637ce6 100644 --- a/src/modules/call/room-user.repository.spec.ts +++ b/src/modules/call/infrastructure/room-user.repository.spec.ts @@ -1,9 +1,9 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { Test, TestingModule } from '@nestjs/testing'; -import { SequelizeRoomUserRepository } from './infrastructure/room-user.repository'; -import { RoomUserModel } from './models/room-user.model'; import { getModelToken } from '@nestjs/sequelize'; -import { RoomUser } from './domain/room-user.domain'; +import { Test, TestingModule } from '@nestjs/testing'; +import { RoomUser } from '../domain/room-user.domain'; +import { RoomUserModel } from '../models/room-user.model'; +import { SequelizeRoomUserRepository } from './room-user.repository'; describe('SequelizeRoomUserRepository', () => { let repository: SequelizeRoomUserRepository; diff --git a/src/modules/call/room.repository.spec.ts b/src/modules/call/infrastructure/room.repository.spec.ts similarity index 96% rename from src/modules/call/room.repository.spec.ts rename to src/modules/call/infrastructure/room.repository.spec.ts index e7d0766..1492df2 100644 --- a/src/modules/call/room.repository.spec.ts +++ b/src/modules/call/infrastructure/room.repository.spec.ts @@ -1,9 +1,9 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; -import { SequelizeRoomRepository } from './infrastructure/room.repository'; -import { RoomModel } from './models/room.model'; import { getModelToken } from '@nestjs/sequelize'; -import { mockRoomData } from '../call/fixtures'; +import { RoomModel } from '../models/room.model'; +import { SequelizeRoomRepository } from './room.repository'; +import { mockRoomData } from '../fixtures'; describe('SequelizeRoomRepository', () => { let roomRepository: SequelizeRoomRepository; diff --git a/src/modules/call/services/room.service.spec.ts b/src/modules/call/services/room.service.spec.ts index ddc5f2d..5993f46 100644 --- a/src/modules/call/services/room.service.spec.ts +++ b/src/modules/call/services/room.service.spec.ts @@ -2,12 +2,19 @@ import { createMock } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; import { RoomService } from './room.service'; import { SequelizeRoomRepository } from '../infrastructure/room.repository'; +import { SequelizeRoomUserRepository } from '../infrastructure/room-user.repository'; +import { UserRepository } from '../../../shared/user/user.repository'; +import { AvatarService } from '../../../externals/avatar/avatar.service'; import { Room } from '../domain/room.domain'; -import { mockRoomData } from '../fixtures'; +import { mockRoomData, createMockRoomUser, createMockUser } from '../fixtures'; +import { v4 } from 'uuid'; describe('Room Service', () => { let roomService: RoomService; let roomRepository: SequelizeRoomRepository; + let roomUserRepository: SequelizeRoomUserRepository; + let userRepository: UserRepository; + let avatarService: AvatarService; let moduleRef: TestingModule; beforeEach(async () => { @@ -21,10 +28,15 @@ describe('Room Service', () => { roomRepository = moduleRef.get( SequelizeRoomRepository, ); + roomUserRepository = moduleRef.get( + SequelizeRoomUserRepository, + ); + userRepository = moduleRef.get(UserRepository); + avatarService = moduleRef.get(AvatarService); }); describe('Creating a room', () => { - it('should create a room successfully', async () => { + it('When valida data is passed, then it should create a room successfully', async () => { const mockRoom = createMock(mockRoomData); jest.spyOn(roomRepository, 'create').mockResolvedValueOnce(mockRoom); @@ -36,7 +48,7 @@ describe('Room Service', () => { }); describe('getRoomByRoomId', () => { - it('should return room when found', async () => { + it('When room exists, then it should return the room', async () => { const mockRoom = createMock(mockRoomData); jest.spyOn(roomRepository, 'findById').mockResolvedValueOnce(mockRoom); @@ -46,7 +58,7 @@ describe('Room Service', () => { expect(result).toEqual(mockRoom); }); - it('should return null when room not found', async () => { + it('When room does not exist, then it should return empty', async () => { jest.spyOn(roomRepository, 'findById').mockResolvedValueOnce(null); const result = await roomService.getRoomByRoomId(mockRoomData.id); @@ -57,7 +69,7 @@ describe('Room Service', () => { }); describe('getRoomByHostId', () => { - it('should return room when found', async () => { + it('When room is found by host ID, then it should return the room', async () => { const mockRoom = createMock(mockRoomData); jest .spyOn(roomRepository, 'findByHostId') @@ -71,7 +83,7 @@ describe('Room Service', () => { expect(result).toEqual(mockRoom); }); - it('should return null when room not found', async () => { + it('When room is not found by host ID, then it should return null', async () => { jest.spyOn(roomRepository, 'findByHostId').mockResolvedValueOnce(null); const result = await roomService.getRoomByHostId(mockRoomData.hostId); @@ -88,7 +100,7 @@ describe('Room Service', () => { maxUsersAllowed: 10, }; - it('should update room successfully', async () => { + it('When valid update data is provided, then it should update room successfully', async () => { const mockUpdatedRoom = createMock({ ...mockRoomData, maxUsersAllowed: 10, @@ -114,7 +126,7 @@ describe('Room Service', () => { }); describe('removeRoom', () => { - it('should remove room successfully', async () => { + it('When room ID is provided, then it should remove room successfully', async () => { jest.spyOn(roomRepository, 'delete').mockResolvedValueOnce(); await roomService.removeRoom(mockRoomData.id); @@ -124,7 +136,7 @@ describe('Room Service', () => { }); describe('closeRoom', () => { - it('should set isClosed to true', async () => { + it('When called, then it should mark room as closed', async () => { jest.spyOn(roomRepository, 'update').mockResolvedValueOnce(); const roomId = mockRoomData.id; await roomService.closeRoom(roomId); @@ -135,7 +147,7 @@ describe('Room Service', () => { }); describe('openRoom', () => { - it('should set isClosed to false', async () => { + it('When called, then it should mark room as open', async () => { jest.spyOn(roomRepository, 'update').mockResolvedValueOnce(); const roomId = mockRoomData.id; await roomService.openRoom(roomId); @@ -146,7 +158,7 @@ describe('Room Service', () => { }); describe('getOpenRoomByHostId', () => { - it('should return room when found', async () => { + it('When open room is found by host ID, then it should return the room', async () => { const mockRoom = createMock(mockRoomData); jest .spyOn(roomRepository, 'findByHostId') @@ -163,7 +175,7 @@ describe('Room Service', () => { expect(result).toEqual(mockRoom); }); - it('should return null when room not found', async () => { + it('When open room is not found by host ID, then it should return null', async () => { jest.spyOn(roomRepository, 'findByHostId').mockResolvedValueOnce(null); const result = await roomService.getOpenRoomByHostId(mockRoomData.hostId); @@ -177,4 +189,182 @@ describe('Room Service', () => { expect(result).toBeNull(); }); }); + + describe('createUserInRoom', () => { + it('When valid user data is provided, then it should create a user in room successfully', async () => { + const mockRoomUser = createMockRoomUser(); + const createData = { + roomId: mockRoomUser.roomId, + userId: mockRoomUser.userId, + name: mockRoomUser.name, + lastName: mockRoomUser.lastName, + anonymous: mockRoomUser.anonymous, + }; + + jest + .spyOn(roomUserRepository, 'create') + .mockResolvedValueOnce(mockRoomUser); + + const result = await roomService.createUserInRoom(createData); + + expect(roomUserRepository.create).toHaveBeenCalledWith(createData); + expect(result).toEqual(mockRoomUser); + }); + }); + + describe('getUsersInRoom', () => { + it('When no users exist in room, then it should return empty array', async () => { + jest + .spyOn(roomUserRepository, 'findAllByRoomId') + .mockResolvedValueOnce([]); + + const result = await roomService.getUsersInRoom(mockRoomData.id); + + expect(roomUserRepository.findAllByRoomId).toHaveBeenCalledWith( + mockRoomData.id, + ); + expect(result).toEqual([]); + }); + + it('When users exist in room with avatars, then it should return users with avatar URLs', async () => { + const userId = v4(); + const mockUser = createMockUser({ + uuid: userId, + avatar: 'avatar123.png', + }); + const mockRoomUser = createMockRoomUser({ userId }); + + const avatarUrl = 'https://example.com/avatar.png'; + + jest + .spyOn(roomUserRepository, 'findAllByRoomId') + .mockResolvedValueOnce([mockRoomUser]); + jest + .spyOn(userRepository, 'findManyByUuid') + .mockResolvedValueOnce([mockUser]); + jest + .spyOn(avatarService, 'getDownloadUrl') + .mockResolvedValueOnce(avatarUrl); + + const result = await roomService.getUsersInRoom(mockRoomData.id); + + expect(roomUserRepository.findAllByRoomId).toHaveBeenCalledWith( + mockRoomData.id, + ); + expect(userRepository.findManyByUuid).toHaveBeenCalledWith([ + mockRoomUser.userId, + ]); + expect(avatarService.getDownloadUrl).toHaveBeenCalledWith( + mockUser.avatar, + ); + expect(result).toEqual([ + { + id: mockRoomUser.userId, + name: mockRoomUser.name, + lastName: mockRoomUser.lastName, + anonymous: mockRoomUser.anonymous, + avatar: avatarUrl, + }, + ]); + }); + + it('When users exist without avatars, then it should handle users with null avatars', async () => { + const userId = v4(); + const mockRoomUser = createMockRoomUser({ userId }); + const mockUserWithoutAvatar = createMockUser({ + uuid: userId, + avatar: null, + }); + + jest + .spyOn(roomUserRepository, 'findAllByRoomId') + .mockResolvedValueOnce([mockRoomUser]); + jest + .spyOn(userRepository, 'findManyByUuid') + .mockResolvedValueOnce([mockUserWithoutAvatar]); + + const result = await roomService.getUsersInRoom(mockRoomData.id); + + expect(result).toEqual([ + { + id: mockRoomUser.userId, + name: mockRoomUser.name, + lastName: mockRoomUser.lastName, + anonymous: mockRoomUser.anonymous, + avatar: null, + }, + ]); + }); + }); + + describe('countUsersInRoom', () => { + it('When counting users in room, then it should return the correct count', async () => { + const expectedCount = 5; + jest + .spyOn(roomUserRepository, 'countByRoomId') + .mockResolvedValueOnce(expectedCount); + + const result = await roomService.countUsersInRoom(mockRoomData.id); + + expect(roomUserRepository.countByRoomId).toHaveBeenCalledWith( + mockRoomData.id, + ); + expect(result).toBe(expectedCount); + }); + }); + + describe('removeUserFromRoom', () => { + it('When removing user from room, then it should remove user successfully', async () => { + const mockRoom = createMock(mockRoomData); + const userId = v4(); + + jest + .spyOn(roomUserRepository, 'deleteByUserIdAndRoomId') + .mockResolvedValueOnce(); + + await roomService.removeUserFromRoom(userId, mockRoom); + + expect(roomUserRepository.deleteByUserIdAndRoomId).toHaveBeenCalledWith( + userId, + mockRoom.id, + ); + }); + }); + + describe('getUserInRoom', () => { + it('When user exists in room, then it should return the user', async () => { + const mockRoomUser = createMockRoomUser(); + const userId = v4(); + const roomId = mockRoomData.id; + + jest + .spyOn(roomUserRepository, 'findByUserIdAndRoomId') + .mockResolvedValueOnce(mockRoomUser); + + const result = await roomService.getUserInRoom(userId, roomId); + + expect(roomUserRepository.findByUserIdAndRoomId).toHaveBeenCalledWith( + userId, + roomId, + ); + expect(result).toEqual(mockRoomUser); + }); + + it('When user does not exist in room, then it should return null', async () => { + const userId = v4(); + const roomId = mockRoomData.id; + + jest + .spyOn(roomUserRepository, 'findByUserIdAndRoomId') + .mockResolvedValueOnce(null); + + const result = await roomService.getUserInRoom(userId, roomId); + + expect(roomUserRepository.findByUserIdAndRoomId).toHaveBeenCalledWith( + userId, + roomId, + ); + expect(result).toBeNull(); + }); + }); }); diff --git a/src/modules/call/services/room.service.ts b/src/modules/call/services/room.service.ts index c5d8155..d9b0eed 100644 --- a/src/modules/call/services/room.service.ts +++ b/src/modules/call/services/room.service.ts @@ -1,14 +1,8 @@ -import { - Injectable, - NotFoundException, - ConflictException, - BadRequestException, -} from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { SequelizeRoomRepository } from '../infrastructure/room.repository'; import { SequelizeRoomUserRepository } from '../infrastructure/room-user.repository'; import { Room, RoomAttributes } from '../domain/room.domain'; import { RoomUser, RoomUserAttributes } from '../domain/room-user.domain'; -import { v4 as uuidv4 } from 'uuid'; import { UsersInRoomDto } from '../dto/users-in-room.dto'; import { UserRepository } from '../../../shared/user/user.repository'; import { AvatarService } from '../../../externals/avatar/avatar.service'; @@ -62,46 +56,13 @@ export class RoomService { return this.roomRepository.update(id, { isClosed: false }); } - async addUserToRoom( - room: Room, - userData: { - userId?: string; - name?: string; - lastName?: string; - anonymous?: boolean; - }, - ): Promise { - const currentUsersCount = await this.roomUserRepository.countByRoomId( - room.id, - ); - - if (currentUsersCount >= room.maxUsersAllowed) { - throw new BadRequestException('The room is full'); - } - - const { userId, name, lastName, anonymous = false } = userData; - - const existingUser = await this.roomUserRepository.findByUserIdAndRoomId( - userId, - room.id, - ); + async getUsersInRoom(roomId: string): Promise { + const roomUsers = await this.roomUserRepository.findAllByRoomId(roomId); - if (existingUser) { - throw new ConflictException('User is already in this room'); + if (roomUsers.length === 0) { + return []; } - return this.roomUserRepository.create({ - roomId: room.id, - userId, - name, - lastName, - anonymous: Boolean(anonymous), - }); - } - - async getUsersInRoom(roomId: string): Promise { - const room = await this.getRoomOrThrow(roomId); - const roomUsers = await this.roomUserRepository.findAllByRoomId(room.id); const users = await this.getUsersByRoomUsers(roomUsers); const userAvatars = await this.getUserAvatars(users); @@ -115,11 +76,6 @@ export class RoomService { } async countUsersInRoom(roomId: string): Promise { - const room = await this.getRoomByRoomId(roomId); - if (!room) { - throw new NotFoundException(`Specified room not found`); - } - return this.roomUserRepository.countByRoomId(roomId); } @@ -127,14 +83,6 @@ export class RoomService { await this.roomUserRepository.deleteByUserIdAndRoomId(userId, room.id); } - private async getRoomOrThrow(roomId: string) { - const room = await this.getRoomByRoomId(roomId); - if (!room) { - throw new NotFoundException(`Specified room not found`); - } - return room; - } - private async getUsersByRoomUsers(roomUsers: RoomUser[]): Promise { return this.userRepository.findManyByUuid( roomUsers.map((roomUser) => roomUser.userId), diff --git a/src/modules/call/webhooks/jitsi/jitsi-webhook.controller.spec.ts b/src/modules/call/webhooks/jitsi/jitsi-webhook.controller.spec.ts index c551c4e..d8981e5 100644 --- a/src/modules/call/webhooks/jitsi/jitsi-webhook.controller.spec.ts +++ b/src/modules/call/webhooks/jitsi/jitsi-webhook.controller.spec.ts @@ -1,6 +1,6 @@ import { BadRequestException, UnauthorizedException } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; +import { createMock } from '@golevelup/ts-jest'; import { JitsiWebhookPayload } from './interfaces/JitsiGenericWebHookPayload'; import { JitsiWebhookController } from './jitsi-webhook.controller'; import { JitsiWebhookService } from './jitsi-webhook.service'; @@ -12,22 +12,11 @@ describe('JitsiWebhookController', () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [JitsiWebhookController], - providers: [ - { - provide: JitsiWebhookService, - useValue: { - validateWebhookRequest: jest.fn(), - handleParticipantLeft: jest.fn(), - }, - }, - { - provide: ConfigService, - useValue: { - get: jest.fn(), - }, - }, - ], - }).compile(); + providers: [], + }) + .useMocker(createMock) + .setLogger(createMock()) + .compile(); controller = module.get(JitsiWebhookController); service = module.get(JitsiWebhookService); diff --git a/src/modules/call/webhooks/jitsi/jitsi-webhook.service.spec.ts b/src/modules/call/webhooks/jitsi/jitsi-webhook.service.spec.ts index d540f63..5a60fe1 100644 --- a/src/modules/call/webhooks/jitsi/jitsi-webhook.service.spec.ts +++ b/src/modules/call/webhooks/jitsi/jitsi-webhook.service.spec.ts @@ -41,6 +41,7 @@ describe('JitsiWebhookService', () => { const module: TestingModule = await Test.createTestingModule({ providers: [JitsiWebhookService], }) + .setLogger(createMock()) .useMocker(createMock) .compile(); @@ -48,17 +49,11 @@ describe('JitsiWebhookService', () => { roomService = module.get>(RoomService); configService = module.get>(ConfigService); - // Default config mock setup configService.get.mockImplementation((key, defaultValue) => { if (key === 'jitsiWebhook.events.participantLeft') return true; if (key === 'jitsiWebhook.secret') return undefined; return defaultValue; }); - - // Mock logger - jest.spyOn(Logger.prototype, 'log').mockImplementation(() => undefined); - jest.spyOn(Logger.prototype, 'warn').mockImplementation(() => undefined); - jest.spyOn(Logger.prototype, 'error').mockImplementation(() => undefined); }); afterEach(() => { @@ -70,12 +65,7 @@ describe('JitsiWebhookService', () => { }); describe('handleParticipantLeft', () => { - it('should handle participant left event successfully', async () => { - configService.get.mockImplementation((key, defaultValue) => { - if (key === 'jitsiWebhook.events.participantLeft') return true; - return undefined; - }); - + it('When participant leaves, then it should remove user from room', async () => { roomService.getRoomByRoomId.mockResolvedValue(minimalRoom); roomService.removeUserFromRoom.mockResolvedValue(undefined); @@ -105,12 +95,7 @@ describe('JitsiWebhookService', () => { ); }); - it('should close room when participant owner left the call', async () => { - configService.get.mockImplementation((key) => { - if (key === 'jitsiWebhook.events.participantLeft') return true; - return undefined; - }); - + it('When room owner leaves, then it should close room and remove user', async () => { const ownerRoom = new Room({ id: 'test-room-id', hostId: 'test-participant-id', @@ -150,12 +135,7 @@ describe('JitsiWebhookService', () => { ); }); - it('should not close room when participant that is not the owner left the call', async () => { - configService.get.mockImplementation((key) => { - if (key === 'jitsiWebhook.events.participantLeft') return true; - return undefined; - }); - + it('When non-owner participant leaves, then it should not close room but remove user', async () => { const ownerRoom = new Room({ id: 'test-room-id', hostId: 'test-participant-id-not-owner', @@ -195,44 +175,7 @@ describe('JitsiWebhookService', () => { ); }); - it('should skip handling when participantLeftEnabled is false', async () => { - configService.get.mockImplementation((key, defaultValue) => { - if (key === 'jitsiWebhook.events.participantLeft') return false; - return undefined; - }); - - // Create a new service instance with the updated config mock - const testService = new JitsiWebhookService(configService, roomService); - - const mockEvent: JitsiParticipantLeftWebHookPayload = { - idempotencyKey: 'test-key', - customerId: 'customer-id', - appId: 'app-id', - eventType: JitsiGenericWebHookEvent.PARTICIPANT_LEFT, - sessionId: 'session-id', - timestamp: Date.now(), - fqn: 'app-id/test-room-id', - data: { - moderator: 'false', - name: 'Test User', - disconnectReason: 'left', - id: 'test-participant-id', - participantJid: 'test-jid', - participantId: 'test-participant-id', - }, - }; - - await testService.handleParticipantLeft(mockEvent); - - expect(roomService.removeUserFromRoom).not.toHaveBeenCalled(); - }); - - it('should handle missing room ID in FQN', async () => { - configService.get.mockImplementation((key, defaultValue) => { - if (key === 'jitsiWebhook.events.participantLeft') return true; - return undefined; - }); - + it('When FQN has missing room ID, then it should skip processing', async () => { const mockEvent: JitsiParticipantLeftWebHookPayload = { idempotencyKey: 'test-key', customerId: 'customer-id', @@ -256,12 +199,7 @@ describe('JitsiWebhookService', () => { expect(roomService.removeUserFromRoom).not.toHaveBeenCalled(); }); - it('should handle missing participant ID', async () => { - configService.get.mockImplementation((key, defaultValue) => { - if (key === 'jitsiWebhook.events.participantLeft') return true; - return undefined; - }); - + it('When participant ID is missing, then it should skip processing', async () => { const mockEvent: JitsiParticipantLeftWebHookPayload = { idempotencyKey: 'test-key', customerId: 'customer-id', @@ -284,49 +222,14 @@ describe('JitsiWebhookService', () => { expect(roomService.removeUserFromRoom).not.toHaveBeenCalled(); }); - - it('should handle errors thrown during processing', async () => { - configService.get.mockImplementation((key, defaultValue) => { - if (key === 'jitsiWebhook.events.participantLeft') return true; - return undefined; - }); - - roomService.getRoomByRoomId.mockResolvedValueOnce(minimalRoom); - - const mockEvent: JitsiParticipantLeftWebHookPayload = { - idempotencyKey: 'test-key', - customerId: 'customer-id', - appId: 'app-id', - eventType: JitsiGenericWebHookEvent.PARTICIPANT_LEFT, - sessionId: 'session-id', - timestamp: Date.now(), - fqn: 'app-id/test-room-id', - data: { - moderator: 'false', - name: 'Test User', - disconnectReason: 'left', - id: 'test-participant-id', - participantJid: 'test-jid', - participantId: 'test-participant-id', - }, - }; - - const error = new Error('Failed to process'); - roomService.getRoomByRoomId.mockResolvedValue(minimalRoom); - roomService.removeUserFromRoom.mockRejectedValue(error); - - await expect(service.handleParticipantLeft(mockEvent)).rejects.toThrow( - error, - ); - }); }); describe('validateWebhookRequest', () => { const mockPayload = { - eventType: 'PARTICIPANT_LEFT', + eventType: JitsiGenericWebHookEvent.PARTICIPANT_LEFT, } as unknown as JitsiWebhookPayload; - it('should skip validation if webhook secret is not configured', () => { + it('When webhook secret is not configured, then it should skip validation', () => { configService.get.mockImplementation((key, defaultValue) => { if (key === 'jitsiWebhook.secret') return undefined; return defaultValue; @@ -342,7 +245,7 @@ describe('JitsiWebhookService', () => { ); }); - it('should fail validation if signature is missing', () => { + it('When signature is missing, then it should fail validation', () => { configService.get.mockImplementation((key, defaultValue) => { if (key === 'jitsiWebhook.secret') return 'webhook-secret'; return defaultValue; @@ -350,14 +253,13 @@ describe('JitsiWebhookService', () => { const testService = new JitsiWebhookService(configService, roomService); const headers = { 'content-type': 'application/json' }; - const rawBody = JSON.stringify({ test: 'data' }); expect(testService.validateWebhookRequest(headers, mockPayload)).toBe( false, ); }); - it('should fail validation if raw body is missing', () => { + it('When raw body is missing, then it should fail validation', () => { configService.get.mockImplementation((key, defaultValue) => { if (key === 'jitsiWebhook.secret') return 'webhook-secret'; return defaultValue; @@ -374,7 +276,7 @@ describe('JitsiWebhookService', () => { ); }); - it('should validate correctly with valid signature', () => { + it('When signature is valid, then it should pass validation', () => { const secret = 'webhook-secret'; configService.get.mockImplementation((key, defaultValue) => { if (key === 'jitsiWebhook.secret') return secret; @@ -397,7 +299,7 @@ describe('JitsiWebhookService', () => { ); }); - it('should fail validation with invalid signature', () => { + it('When signature is invalid, then it should fail validation', () => { const secret = 'webhook-secret'; configService.get.mockImplementation((key, defaultValue) => { if (key === 'jitsiWebhook.secret') return secret;