test: enhance test scenarios and include more cases in room API security tests
This commit is contained in:
parent
11f7ac1401
commit
7ff040864f
@ -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();
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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!;
|
||||
});
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user