test: enhance test scenarios and include more cases in room API security tests

This commit is contained in:
juancarmore 2026-01-28 16:10:45 +01:00
parent 11f7ac1401
commit 7ff040864f
4 changed files with 212 additions and 48 deletions

View File

@ -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<string> => {
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<string> => {
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();

View File

@ -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<MeetRoomConfig>,
accessToken?: string
config?: Partial<MeetRoomConfig>
): Promise<RoomData> => {
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<string> => {
// 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<TestUsers> => {
*/
export const setupRoomMember = async (
roomId: string,
memberOptions: MeetRoomMemberOptions
memberOptions: MeetRoomMemberOptions,
accessToken?: string
): Promise<RoomMemberData> => {
// 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<MeetRoomMemberPermissions>,
accessToken?: string
): Promise<RoomMemberData> => {
// 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<RoomData> => {
export const setupTestUsersForRoom = async (roomData: RoomData): Promise<RoomData> => {
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<RoomData> => {
})
]);
// 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;

View File

@ -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!;
});

View File

@ -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;
}