diff --git a/backend/tests/helpers/assertion-helpers.ts b/backend/tests/helpers/assertion-helpers.ts index a875cd4..0966e79 100644 --- a/backend/tests/helpers/assertion-helpers.ts +++ b/backend/tests/helpers/assertion-helpers.ts @@ -513,6 +513,40 @@ const getPermissions = (roomId: string, role: ParticipantRole) => { } }; +export const expectValidParticipantTokenResponse = ( + response: any, + roomId: string, + participantName: string, + participantRole: ParticipantRole +) => { + expect(response.status).toBe(200); + expect(response.body).toHaveProperty('token'); + + const token = response.body.token; + const decodedToken = decodeJWTToken(token); + + const permissions = getPermissions(roomId, participantRole); + + expect(decodedToken).toHaveProperty('sub', participantName); + expect(decodedToken).toHaveProperty('video', permissions.livekit); + expect(decodedToken).toHaveProperty('metadata'); + const metadata = JSON.parse(decodedToken.metadata); + expect(metadata).toHaveProperty('role', participantRole); + expect(metadata).toHaveProperty('permissions', permissions.openvidu); + + // Check that the token is included in a cookie + expect(response.headers['set-cookie']).toBeDefined(); + const cookies = response.headers['set-cookie'] as unknown as string[]; + const participantTokenCookie = cookies.find((cookie) => + cookie.startsWith(`${INTERNAL_CONFIG.PARTICIPANT_TOKEN_COOKIE_NAME}=`) + ) as string; + expect(participantTokenCookie).toBeDefined(); + expect(participantTokenCookie).toContain(token); + expect(participantTokenCookie).toContain('HttpOnly'); + expect(participantTokenCookie).toContain('SameSite=Strict'); + expect(participantTokenCookie).toContain('Path=/'); +}; + export const expectValidRecordingTokenResponse = ( response: any, roomId: string, @@ -523,7 +557,8 @@ export const expectValidRecordingTokenResponse = ( expect(response.status).toBe(200); expect(response.body).toHaveProperty('token'); - const decodedToken = decodeJWTToken(response.body.token); + const token = response.body.token; + const decodedToken = decodeJWTToken(token); expect(decodedToken).toHaveProperty('video', { room: roomId @@ -535,6 +570,18 @@ export const expectValidRecordingTokenResponse = ( canRetrieveRecordings, canDeleteRecordings }); + + // Check that the token is included in a cookie + expect(response.headers['set-cookie']).toBeDefined(); + const cookies = response.headers['set-cookie'] as unknown as string[]; + const participantTokenCookie = cookies.find((cookie) => + cookie.startsWith(`${INTERNAL_CONFIG.RECORDING_TOKEN_COOKIE_NAME}=`) + ) as string; + expect(participantTokenCookie).toBeDefined(); + expect(participantTokenCookie).toContain(token); + expect(participantTokenCookie).toContain('HttpOnly'); + expect(participantTokenCookie).toContain('SameSite=Strict'); + expect(participantTokenCookie).toContain('Path=/'); }; const decodeJWTToken = (token: string) => { diff --git a/backend/tests/helpers/request-helpers.ts b/backend/tests/helpers/request-helpers.ts index 12e77e7..1510180 100644 --- a/backend/tests/helpers/request-helpers.ts +++ b/backend/tests/helpers/request-helpers.ts @@ -307,14 +307,7 @@ export const getRoomRoleBySecret = async (roomId: string, secret: string) => { return response; }; -/** - * Generates a participant token for a room and returns the cookie containing the token - */ -export const generateParticipantToken = async ( - roomId: string, - participantName: string, - secret: string -): Promise => { +export const generateParticipantToken = async (participantOptions: any) => { checkAppIsRunning(); // Disable authentication to generate the token @@ -325,12 +318,25 @@ export const generateParticipantToken = async ( // Generate the participant token const response = await request(app) .post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/participants/token`) - .send({ - roomId, - participantName, - secret - }) - .expect(200); + .send(participantOptions); + return response; +}; + +/** + * Generates a participant token for a room and returns the cookie containing the token + */ +export const generateParticipantTokenCookie = async ( + roomId: string, + participantName: string, + secret: string +): Promise => { + // Generate the participant token + const response = await generateParticipantToken({ + roomId, + participantName, + secret + }); + expect(response.status).toBe(200); // Return the participant token cookie const cookies = response.headers['set-cookie'] as unknown as string[]; @@ -406,8 +412,6 @@ export const generateRecordingToken = async (roomId: string, secret: string) => * Generates a token for retrieving/deleting recordings from a room and returns the cookie containing the token */ export const generateRecordingTokenCookie = async (roomId: string, secret: string) => { - checkAppIsRunning(); - // Generate the recording token const response = await generateRecordingToken(roomId, secret); expect(response.status).toBe(200); diff --git a/backend/tests/helpers/test-scenarios.ts b/backend/tests/helpers/test-scenarios.ts index 5c18e68..97859f0 100644 --- a/backend/tests/helpers/test-scenarios.ts +++ b/backend/tests/helpers/test-scenarios.ts @@ -6,7 +6,7 @@ import { MeetRoom } from '../../src/typings/ce'; import { expectValidStartRecordingResponse } from './assertion-helpers'; import { createRoom, - generateParticipantToken, + generateParticipantTokenCookie, joinFakeParticipant, sleep, startRecording, @@ -44,8 +44,8 @@ export const setupSingleRoom = async (withParticipant = false): Promise { + let roomData: RoomData; + + beforeAll(async () => { + startTestServer(); + roomData = await setupSingleRoom(); + }); + + afterAll(async () => { + await deleteAllRooms(); + }); + + describe('Generate Participant Token Tests', () => { + it('should generate a participant token with moderator permissions when using the moderator secret', async () => { + const response = await generateParticipantToken({ + roomId: roomData.room.roomId, + participantName, + secret: roomData.moderatorSecret + }); + expectValidParticipantTokenResponse( + response, + roomData.room.roomId, + participantName, + ParticipantRole.MODERATOR + ); + }); + + it('should generate a participant token with publisher permissions when using the publisher secret', async () => { + const response = await generateParticipantToken({ + roomId: roomData.room.roomId, + participantName, + secret: roomData.publisherSecret + }); + expectValidParticipantTokenResponse( + response, + roomData.room.roomId, + participantName, + ParticipantRole.PUBLISHER + ); + }); + + it('should fail with 409 when participant already exists in the room', async () => { + roomData = await setupSingleRoom(true); + const response = await generateParticipantToken({ + roomId: roomData.room.roomId, + participantName, + secret: roomData.moderatorSecret + }); + expect(response.status).toBe(409); + + // Recreate the room without the participant + roomData = await setupSingleRoom(); + }); + + it('should fail with 404 when room does not exist', async () => { + const response = await generateParticipantToken({ + roomId: 'non_existent_room', + participantName, + secret: roomData.moderatorSecret + }); + expect(response.status).toBe(404); + }); + + it('should fail with 400 when secret is invalid', async () => { + const response = await generateParticipantToken({ + roomId: roomData.room.roomId, + participantName, + secret: 'invalid_secret' + }); + expect(response.status).toBe(400); + }); + }); + + describe('Generate Participant Token Validation Tests', () => { + it('should fail when roomId is not provided', async () => { + const response = await generateParticipantToken({ + participantName, + secret: roomData.moderatorSecret + }); + expectValidationError(response, 'roomId', 'Required'); + }); + + it('should fail when participantName is not provided', async () => { + const response = await generateParticipantToken({ + roomId: roomData.room.roomId, + secret: roomData.moderatorSecret + }); + expectValidationError(response, 'participantName', 'Required'); + }); + + it('should fail when secret is not provided', async () => { + const response = await generateParticipantToken({ + roomId: roomData.room.roomId, + participantName + }); + expectValidationError(response, 'secret', 'Required'); + }); + + it('should fail when participantName is empty', async () => { + const response = await generateParticipantToken({ + roomId: roomData.room.roomId, + participantName: '', + secret: roomData.moderatorSecret + }); + expectValidationError(response, 'participantName', 'Participant name is required'); + }); + + it('should fail when secret is empty', async () => { + const response = await generateParticipantToken({ + roomId: roomData.room.roomId, + participantName, + secret: '' + }); + expectValidationError(response, 'secret', 'Secret is required'); + }); + }); +});