From 7ff040864fc0c0f8dd05e6fa655f35238ce0ad28 Mon Sep 17 00:00:00 2001 From: juancarmore Date: Wed, 28 Jan 2026 16:10:45 +0100 Subject: [PATCH] test: enhance test scenarios and include more cases in room API security tests --- .../backend/tests/helpers/request-helpers.ts | 39 ++++- .../backend/tests/helpers/test-scenarios.ts | 136 +++++++++++++----- .../api/security/room-security.test.ts | 79 ++++++++-- meet-ce/backend/tests/interfaces/scenarios.ts | 6 + 4 files changed, 212 insertions(+), 48 deletions(-) diff --git a/meet-ce/backend/tests/helpers/request-helpers.ts b/meet-ce/backend/tests/helpers/request-helpers.ts index 36ae20a3..3ee9389c 100644 --- a/meet-ce/backend/tests/helpers/request-helpers.ts +++ b/meet-ce/backend/tests/helpers/request-helpers.ts @@ -558,13 +558,22 @@ export const bulkDeleteRoomMembers = async (roomId: string, memberIds: string[]) .query({ memberIds: memberIds.join(',') }); }; -export const generateRoomMemberTokenRequest = async (roomId: string, tokenOptions: MeetRoomMemberTokenOptions) => { +export const generateRoomMemberTokenRequest = async ( + roomId: string, + tokenOptions: MeetRoomMemberTokenOptions, + accessToken?: string +) => { checkAppIsRunning(); - const response = await request(app) + const req = request(app) .post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/rooms/${roomId}/members/token`) .send(tokenOptions); - return response; + + if (accessToken) { + req.set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, accessToken); + } + + return await req; }; /** @@ -572,9 +581,10 @@ export const generateRoomMemberTokenRequest = async (roomId: string, tokenOption */ export const generateRoomMemberToken = async ( roomId: string, - tokenOptions: MeetRoomMemberTokenOptions + tokenOptions: MeetRoomMemberTokenOptions, + accessToken?: string ): Promise => { - const response = await generateRoomMemberTokenRequest(roomId, tokenOptions); + const response = await generateRoomMemberTokenRequest(roomId, tokenOptions, accessToken); expect(response.status).toBe(200); expect(response.body).toHaveProperty('token'); @@ -785,6 +795,25 @@ export const getRecordingUrl = async (recordingId: string, privateAccess = false .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_ENV.INITIAL_API_KEY); }; +/** + * Retrieves the access secret for a specific recording by parsing the recording URL. + * + * @param recordingId - The unique identifier of the recording + * @param privateAccess - Whether to request a private access URL + * @returns A Promise that resolves to the access secret string + */ +export const getRecordingAccessSecret = async (recordingId: string, privateAccess = false): Promise => { + const response = await getRecordingUrl(recordingId, privateAccess); + expect(response.status).toBe(200); + const recordingUrl = response.body.url; + expect(recordingUrl).toBeDefined(); + + // Parse the URL to extract the secret from the query parameters + const parsedUrl = new URL(recordingUrl); + const secret = parsedUrl.searchParams.get('secret'); + return secret!; +}; + export const deleteRecording = async (recordingId: string) => { checkAppIsRunning(); diff --git a/meet-ce/backend/tests/helpers/test-scenarios.ts b/meet-ce/backend/tests/helpers/test-scenarios.ts index 6d504d36..10c223b4 100644 --- a/meet-ce/backend/tests/helpers/test-scenarios.ts +++ b/meet-ce/backend/tests/helpers/test-scenarios.ts @@ -2,6 +2,7 @@ import { MeetRoomConfig, MeetRoomMember, MeetRoomMemberOptions, + MeetRoomMemberPermissions, MeetRoomMemberRole, MeetUser, MeetUserOptions, @@ -10,7 +11,9 @@ import { import express, { Request, Response } from 'express'; import http from 'http'; import { StringValue } from 'ms'; +import { container } from '../../src/config/dependency-injector.config'; import { MeetRoomHelper } from '../../src/helpers/room.helper'; +import { RoomRepository } from '../../src/repositories/room.repository'; import { RoomData, RoomMemberData, RoomTestUsers, TestContext, TestUsers, UserData } from '../interfaces/scenarios'; import { expectValidStartRecordingResponse } from './assertion-helpers'; import { @@ -23,7 +26,8 @@ import { loginUser, sleep, startRecording, - stopRecording + stopRecording, + updateRoomMember } from './request-helpers'; let mockWebhookServer: http.Server; @@ -39,16 +43,12 @@ let mockWebhookServer: http.Server; export const setupSingleRoom = async ( withParticipant = false, roomName = 'TEST_ROOM', - config?: Partial, - accessToken?: string + config?: Partial ): Promise => { - const room = await createRoom( - { - roomName, - config - }, - accessToken - ); + const room = await createRoom({ + roomName, + config + }); // Extract the room secrets and generate room member tokens const { moderatorSecret, speakerSecret } = MeetRoomHelper.extractSecretsFromRoom(room); @@ -132,6 +132,34 @@ export const setupSingleRoomWithRecording = async ( return roomData; }; +/** + * Creates a completed recording in an existing room. + * Starts a recording, optionally waits for a delay, then stops it. + * + * Note: The room must already exist and have an active meeting with participants. + * + * @param roomData The room data where the recording will be created + * @param stopDelay Optional delay before stopping the recording + * @returns The recording ID of the completed recording + */ +export const setupCompletedRecording = async (roomData: RoomData, stopDelay?: StringValue): Promise => { + // Start recording + const response = await startRecording(roomData.room.roomId, roomData.moderatorToken); + expectValidStartRecordingResponse(response, roomData.room.roomId, roomData.room.roomName); + const recordingId = response.body.recordingId; + roomData.recordingId = recordingId; + + // Wait for the configured delay before stopping the recording + if (stopDelay) { + await sleep(stopDelay); + } + + // Stop recording + await stopRecording(recordingId, roomData.moderatorToken); + + return recordingId; +}; + /** * Quickly creates multiple recordings * Allows customizing how many recordings to start and how many to stop after a delay. @@ -293,7 +321,8 @@ export const setupTestUsers = async (): Promise => { */ export const setupRoomMember = async ( roomId: string, - memberOptions: MeetRoomMemberOptions + memberOptions: MeetRoomMemberOptions, + accessToken?: string ): Promise => { // Create the room member const createResponse = await createRoomMember(roomId, memberOptions); @@ -302,10 +331,42 @@ export const setupRoomMember = async ( // Generate room member token for this member const secret = member.memberId.startsWith('ext-') ? member.memberId : undefined; - const memberToken = await generateRoomMemberToken(roomId, { - secret, - joinMeeting: false - }); + const memberToken = await generateRoomMemberToken( + roomId, + { + secret, + joinMeeting: false + }, + accessToken + ); + + return { + member, + memberToken + }; +}; + +export const updateRoomMemberPermissions = async ( + roomId: string, + memberId: string, + permissions: Partial, + accessToken?: string +): Promise => { + // Update the room member + const updateResponse = await updateRoomMember(roomId, memberId, { customPermissions: permissions }); + expect(updateResponse.status).toBe(200); + const member = updateResponse.body as MeetRoomMember; + + // Generate room member token for this member + const secret = member.memberId.startsWith('ext-') ? member.memberId : undefined; + const memberToken = await generateRoomMemberToken( + roomId, + { + secret, + joinMeeting: false + }, + accessToken + ); return { member, @@ -314,11 +375,12 @@ export const setupRoomMember = async ( }; /** - * Sets up a room along with a comprehensive set of test users covering all authentication and permission scenarios. + * Sets up test users for a room, including owner, member, and room member. * - * @returns RoomData including the created room and associated test users. + * @param roomData The room data to set up users for + * @returns Updated RoomData with test users included */ -export const setupRoomWithTestUsers = async (): Promise => { +export const setupTestUsersForRoom = async (roomData: RoomData): Promise => { const timestamp = String(Date.now()).slice(-6); // Use last 6 digits to keep userId under 20 chars const [userOwner, userMember, roomMember] = await Promise.all([ @@ -345,26 +407,36 @@ export const setupRoomWithTestUsers = async (): Promise => { }) ]); - // Create room using the owner user access token - const roomData = await setupSingleRoom(false, `Test Room`, undefined, userOwner.accessToken); - const roomId = roomData.room.roomId; + // Change room ownership to userOwner + roomData.room.owner = userOwner.user.userId; + const roomRepository = container.get(RoomRepository); + await roomRepository.update(roomData.room); // Add userMember and roomMember as room members - await Promise.all([ - createRoomMember(roomId, { - userId: userMember.user.userId, - baseRole: MeetRoomMemberRole.MODERATOR - }), - createRoomMember(roomId, { - userId: roomMember.user.userId, - baseRole: MeetRoomMemberRole.SPEAKER - }) + const [userMemberDetails, roomMemberDetails] = await Promise.all([ + setupRoomMember( + roomData.room.roomId, + { + userId: userMember.user.userId, + baseRole: MeetRoomMemberRole.MODERATOR + }, + userOwner.accessToken + ), + setupRoomMember( + roomData.room.roomId, + { + userId: roomMember.user.userId, + baseRole: MeetRoomMemberRole.SPEAKER + }, + userOwner.accessToken + ) ]); - const testUsers: RoomTestUsers = { userOwner, userMember, - roomMember + userMemberDetails, + roomMember, + roomMemberDetails }; roomData.users = testUsers; return roomData; diff --git a/meet-ce/backend/tests/integration/api/security/room-security.test.ts b/meet-ce/backend/tests/integration/api/security/room-security.test.ts index 100343f0..ae2da4d4 100644 --- a/meet-ce/backend/tests/integration/api/security/room-security.test.ts +++ b/meet-ce/backend/tests/integration/api/security/room-security.test.ts @@ -4,7 +4,7 @@ import request from 'supertest'; import { INTERNAL_CONFIG } from '../../../../src/config/internal-config.js'; import { MEET_ENV } from '../../../../src/environment.js'; import { deleteAllRooms, deleteAllUsers, sleep, startTestServer } from '../../../helpers/request-helpers.js'; -import { setupRoomWithTestUsers, setupSingleRoom, setupTestUsers } from '../../../helpers/test-scenarios.js'; +import { setupSingleRoom, setupTestUsers, setupTestUsersForRoom } from '../../../helpers/test-scenarios.js'; import { RoomData, RoomTestUsers, TestUsers } from '../../../interfaces/scenarios.js'; const ROOMS_PATH = `${INTERNAL_CONFIG.API_BASE_PATH_V1}/rooms`; @@ -63,11 +63,24 @@ describe('Room API Security Tests', () => { }); describe('Get Rooms Tests', () => { + let roomData: RoomData; + let roomUsers: RoomTestUsers; + + beforeAll(async () => { + // Ensure no rooms exist before tests + await deleteAllRooms(); + + roomData = await setupSingleRoom(); + roomData = await setupTestUsersForRoom(roomData); + roomUsers = roomData.users!; + }); + it('should succeed when request includes API key', async () => { const response = await request(app) .get(ROOMS_PATH) .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_ENV.INITIAL_API_KEY); expect(response.status).toBe(200); + expect(response.body.rooms.length).toBe(1); }); it('should succeed when user is authenticated as ADMIN', async () => { @@ -75,20 +88,47 @@ describe('Room API Security Tests', () => { .get(ROOMS_PATH) .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.admin.accessToken); expect(response.status).toBe(200); + expect(response.body.rooms.length).toBe(1); }); - it('should succeed when user is authenticated as USER', async () => { + it('should succeed when user is authenticated as USER and is room owner', async () => { + const response = await request(app) + .get(ROOMS_PATH) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, roomUsers.userOwner.accessToken); + expect(response.status).toBe(200); + expect(response.body.rooms.length).toBe(1); + }); + + it('should succeed when user is authenticated as USER and is room member', async () => { + const response = await request(app) + .get(ROOMS_PATH) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, roomUsers.userMember.accessToken); + expect(response.status).toBe(200); + expect(response.body.rooms.length).toBe(1); + }); + + it('should not return rooms when user is authenticated as USER without access to any rooms', async () => { const response = await request(app) .get(ROOMS_PATH) .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.user.accessToken); expect(response.status).toBe(200); + expect(response.body.rooms.length).toBe(0); }); - it('should succeed when user is authenticated as ROOM_MEMBER', async () => { + it('should succeed when user is authenticated as ROOM_MEMBER and is room member', async () => { + const response = await request(app) + .get(ROOMS_PATH) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, roomUsers.roomMember.accessToken); + expect(response.status).toBe(200); + expect(response.body.rooms.length).toBe(1); + }); + + it('should not return rooms when user is authenticated as ROOM_MEMBER without access to any rooms', async () => { const response = await request(app) .get(ROOMS_PATH) .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.roomMember.accessToken); expect(response.status).toBe(200); + expect(response.body.rooms.length).toBe(0); }); it('should fail when user is not authenticated', async () => { @@ -111,7 +151,8 @@ describe('Room API Security Tests', () => { let roomUsers: RoomTestUsers; const recreateRoom = async () => { - roomData = await setupRoomWithTestUsers(); + roomData = await setupSingleRoom(); + roomData = await setupTestUsersForRoom(roomData); roomId = roomData.room.roomId; roomUsers = roomData.users!; }; @@ -162,6 +203,15 @@ describe('Room API Security Tests', () => { // No need to recreate - room was not deleted }); + it('should fail when user is authenticated as USER without access to the room', async () => { + const response = await request(app) + .delete(ROOMS_PATH) + .query({ roomIds: roomId }) + .set(INTERNAL_CONFIG.ACCESS_TOKEN_HEADER, testUsers.user.accessToken); + expect(response.status).toBe(400); + // No need to recreate - room was not deleted + }); + it('should fail when user is authenticated as ROOM_MEMBER and is room member', async () => { const response = await request(app) .delete(ROOMS_PATH) @@ -201,7 +251,8 @@ describe('Room API Security Tests', () => { let roomUsers: RoomTestUsers; beforeAll(async () => { - roomData = await setupRoomWithTestUsers(); + roomData = await setupSingleRoom(); + roomData = await setupTestUsersForRoom(roomData); roomId = roomData.room.roomId; roomUsers = roomData.users!; }); @@ -301,7 +352,8 @@ describe('Room API Security Tests', () => { let roomUsers: RoomTestUsers; const recreateRoom = async () => { - roomData = await setupRoomWithTestUsers(); + roomData = await setupSingleRoom(); + roomData = await setupTestUsersForRoom(roomData); roomId = roomData.room.roomId; roomUsers = roomData.users!; }; @@ -393,7 +445,8 @@ describe('Room API Security Tests', () => { let roomUsers: RoomTestUsers; beforeAll(async () => { - roomData = await setupRoomWithTestUsers(); + roomData = await setupSingleRoom(); + roomData = await setupTestUsersForRoom(roomData); roomId = roomData.room.roomId; roomUsers = roomData.users!; }); @@ -483,7 +536,8 @@ describe('Room API Security Tests', () => { let roomUsers: RoomTestUsers; beforeAll(async () => { - roomData = await setupRoomWithTestUsers(); + roomData = await setupSingleRoom(); + roomData = await setupTestUsersForRoom(roomData); roomId = roomData.room.roomId; roomUsers = roomData.users!; }); @@ -564,7 +618,8 @@ describe('Room API Security Tests', () => { let roomUsers: RoomTestUsers; beforeAll(async () => { - roomData = await setupRoomWithTestUsers(); + roomData = await setupSingleRoom(); + roomData = await setupTestUsersForRoom(roomData); roomId = roomData.room.roomId; roomUsers = roomData.users!; }); @@ -645,7 +700,8 @@ describe('Room API Security Tests', () => { let roomUsers: RoomTestUsers; beforeAll(async () => { - roomData = await setupRoomWithTestUsers(); + roomData = await setupSingleRoom(); + roomData = await setupTestUsersForRoom(roomData); roomId = roomData.room.roomId; roomUsers = roomData.users!; }); @@ -726,7 +782,8 @@ describe('Room API Security Tests', () => { let roomUsers: RoomTestUsers; beforeAll(async () => { - roomData = await setupRoomWithTestUsers(); + roomData = await setupSingleRoom(); + roomData = await setupTestUsersForRoom(roomData); roomId = roomData.room.roomId; roomUsers = roomData.users!; }); diff --git a/meet-ce/backend/tests/interfaces/scenarios.ts b/meet-ce/backend/tests/interfaces/scenarios.ts index 1bc23957..9ce5725d 100644 --- a/meet-ce/backend/tests/interfaces/scenarios.ts +++ b/meet-ce/backend/tests/interfaces/scenarios.ts @@ -53,6 +53,12 @@ export interface RoomTestUsers { /** User with USER role who is a member of the room (not owner) */ userMember: UserData; + /** Room member details for userMember */ + userMemberDetails: RoomMemberData; + /** User with ROOM_MEMBER role who is a member of the room */ roomMember: UserData; + + /** Room member details for roomMember */ + roomMemberDetails: RoomMemberData; }