backend: Add integration tests for recording API and enhance assertion helpers
This commit is contained in:
parent
9d42242ba0
commit
dae12bcbe4
212
backend/tests/integration/api/recordings/start-recording.test.ts
Normal file
212
backend/tests/integration/api/recordings/start-recording.test.ts
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
import { describe, it, expect, beforeAll, afterAll, afterEach } from '@jest/globals';
|
||||||
|
import {
|
||||||
|
deleteAllRecordings,
|
||||||
|
deleteAllRooms,
|
||||||
|
disconnectFakeParticipants,
|
||||||
|
joinFakeParticipant,
|
||||||
|
startRecording,
|
||||||
|
startTestServer,
|
||||||
|
stopAllRecordings,
|
||||||
|
stopRecording,
|
||||||
|
stopTestServer
|
||||||
|
} from '../../../utils/helpers.js';
|
||||||
|
import { setInternalConfig } from '../../../../src/config/internal-config.js';
|
||||||
|
|
||||||
|
import { errorRoomNotFound } from '../../../../src/models/error.model.js';
|
||||||
|
import {
|
||||||
|
expectValidRecordingLocationHeader,
|
||||||
|
expectValidStartRecordingResponse,
|
||||||
|
expectValidStopRecordingResponse
|
||||||
|
} from '../../../utils/assertion-helpers.js';
|
||||||
|
import { setupMultiRoomTestContext, TestContext } from '../../../utils/test-scenarios.js';
|
||||||
|
import { MeetRoom } from '../../../../src/typings/ce/room.js';
|
||||||
|
|
||||||
|
describe('Recording API Tests', () => {
|
||||||
|
let context: TestContext | null = null;
|
||||||
|
let room: MeetRoom, moderatorCookie: string;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await startTestServer();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await stopAllRecordings(moderatorCookie);
|
||||||
|
await disconnectFakeParticipants();
|
||||||
|
await deleteAllRooms();
|
||||||
|
await deleteAllRecordings();
|
||||||
|
await stopTestServer();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Start Recording Tests', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
// Create a room and join a participant
|
||||||
|
context = await setupMultiRoomTestContext(1, true);
|
||||||
|
({ room, moderatorCookie } = context.getRoomByIndex(0)!);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await disconnectFakeParticipants();
|
||||||
|
await deleteAllRooms();
|
||||||
|
await deleteAllRecordings();
|
||||||
|
context = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 201 with proper response and location header when recording starts successfully', async () => {
|
||||||
|
const response = await startRecording(room.roomId, moderatorCookie);
|
||||||
|
const recordingId = response.body.recordingId;
|
||||||
|
expectValidStartRecordingResponse(response, room.roomId);
|
||||||
|
|
||||||
|
expectValidRecordingLocationHeader(response);
|
||||||
|
const stopResponse = await stopRecording(recordingId, moderatorCookie);
|
||||||
|
expectValidStopRecordingResponse(stopResponse, recordingId, room.roomId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should successfully start recording, stop it, and start again (sequential operations)', async () => {
|
||||||
|
const firstStartResponse = await startRecording(room.roomId, moderatorCookie);
|
||||||
|
const firstRecordingId = firstStartResponse.body.recordingId;
|
||||||
|
|
||||||
|
expectValidStartRecordingResponse(firstStartResponse, room.roomId);
|
||||||
|
|
||||||
|
const firstStopResponse = await stopRecording(firstRecordingId, moderatorCookie);
|
||||||
|
expectValidStopRecordingResponse(firstStopResponse, firstRecordingId, room.roomId);
|
||||||
|
|
||||||
|
const secondStartResponse = await startRecording(room.roomId, moderatorCookie);
|
||||||
|
expectValidStartRecordingResponse(secondStartResponse, room.roomId);
|
||||||
|
const secondRecordingId = secondStartResponse.body.recordingId;
|
||||||
|
|
||||||
|
const secondStopResponse = await stopRecording(secondRecordingId, moderatorCookie);
|
||||||
|
expectValidStopRecordingResponse(secondStopResponse, secondRecordingId, room.roomId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle simultaneous recordings in different rooms correctly', async () => {
|
||||||
|
const context = await setupMultiRoomTestContext(2, true);
|
||||||
|
|
||||||
|
const roomDataA = context.getRoomByIndex(0)!;
|
||||||
|
const roomDataB = context.getRoomByIndex(1)!;
|
||||||
|
|
||||||
|
const firstResponse = await startRecording(roomDataA.room.roomId, roomDataA.moderatorCookie);
|
||||||
|
const secondResponse = await startRecording(roomDataB.room.roomId, roomDataB.moderatorCookie);
|
||||||
|
|
||||||
|
expectValidStartRecordingResponse(firstResponse, roomDataA.room.roomId);
|
||||||
|
expectValidStartRecordingResponse(secondResponse, roomDataB.room.roomId);
|
||||||
|
|
||||||
|
const firstRecordingId = firstResponse.body.recordingId;
|
||||||
|
const secondRecordingId = secondResponse.body.recordingId;
|
||||||
|
|
||||||
|
const [firstStopResponse, secondStopResponse] = await Promise.all([
|
||||||
|
stopRecording(firstRecordingId, roomDataA.moderatorCookie),
|
||||||
|
stopRecording(secondRecordingId, roomDataB.moderatorCookie)
|
||||||
|
]);
|
||||||
|
expectValidStopRecordingResponse(firstStopResponse, firstRecordingId, roomDataA.room.roomId);
|
||||||
|
expectValidStopRecordingResponse(secondStopResponse, secondRecordingId, roomDataB.room.roomId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Start Recording Validation failures', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
// Create a room without participants
|
||||||
|
context = await setupMultiRoomTestContext(1, false);
|
||||||
|
({ room, moderatorCookie } = context.getRoomByIndex(0)!);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await disconnectFakeParticipants();
|
||||||
|
await stopAllRecordings(moderatorCookie);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept valid roomId but reject with 409', async () => {
|
||||||
|
const response = await startRecording(room.roomId, moderatorCookie);
|
||||||
|
// Room exists but it has no participants
|
||||||
|
expect(response.status).toBe(409);
|
||||||
|
expect(response.body.message).toContain(`The room '${room.roomId}' has no participants`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should sanitize roomId and reject the request with 409 due to no participants', async () => {
|
||||||
|
const malformedRoomId = ' .<!?' + room.roomId + ' ';
|
||||||
|
const response = await startRecording(malformedRoomId, moderatorCookie);
|
||||||
|
|
||||||
|
console.log('Response:', response.body);
|
||||||
|
expect(response.status).toBe(409);
|
||||||
|
expect(response.body.message).toContain(`The room '${room.roomId}' has no participants`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject request with roomId that becomes empty after sanitization', async () => {
|
||||||
|
const response = await startRecording('!@#$%^&*()', moderatorCookie);
|
||||||
|
|
||||||
|
expect(response.status).toBe(422);
|
||||||
|
expect(response.body.details).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
field: 'roomId',
|
||||||
|
message: expect.stringContaining('cannot be empty after sanitization')
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject request with non-string roomId', async () => {
|
||||||
|
const response = await startRecording(123 as unknown as string, moderatorCookie);
|
||||||
|
|
||||||
|
expect(response.status).toBe(422);
|
||||||
|
expect(response.body.details).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
field: 'roomId',
|
||||||
|
message: expect.stringContaining('Expected string')
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject request with very long roomId', async () => {
|
||||||
|
const longRoomId = 'a'.repeat(101);
|
||||||
|
const response = await startRecording(longRoomId, moderatorCookie);
|
||||||
|
|
||||||
|
expect(response.status).toBe(422);
|
||||||
|
expect(response.body.details).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
field: 'roomId',
|
||||||
|
message: expect.stringContaining('cannot exceed 100 characters')
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle room that does not exist', async () => {
|
||||||
|
const response = await startRecording('non-existing-room-id', moderatorCookie);
|
||||||
|
const error = errorRoomNotFound('non-existing-room-id');
|
||||||
|
expect(response.status).toBe(404);
|
||||||
|
expect(response.body).toEqual({
|
||||||
|
name: error.name,
|
||||||
|
message: error.message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 409 when recording is already in progress', async () => {
|
||||||
|
await joinFakeParticipant(room.roomId, 'fakeParticipantId');
|
||||||
|
const firstResponse = await startRecording(room.roomId, moderatorCookie);
|
||||||
|
const recordingId = firstResponse.body.recordingId;
|
||||||
|
expectValidStartRecordingResponse(firstResponse, room.roomId);
|
||||||
|
|
||||||
|
const secondResponse = await startRecording(room!.roomId, moderatorCookie);
|
||||||
|
expect(secondResponse.status).toBe(409);
|
||||||
|
expect(secondResponse.body.message).toContain('already');
|
||||||
|
const stopResponse = await stopRecording(recordingId, moderatorCookie);
|
||||||
|
expectValidStopRecordingResponse(stopResponse, recordingId, room.roomId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 503 when recording start times out', async () => {
|
||||||
|
setInternalConfig({
|
||||||
|
RECORDING_STARTED_TIMEOUT: '1s'
|
||||||
|
});
|
||||||
|
await joinFakeParticipant(room.roomId, 'fakeParticipantId');
|
||||||
|
const response = await startRecording(room.roomId, moderatorCookie);
|
||||||
|
expect(response.status).toBe(503);
|
||||||
|
expect(response.body.message).toContain('timed out while starting');
|
||||||
|
setInternalConfig({
|
||||||
|
RECORDING_STARTED_TIMEOUT: '30s'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
39
backend/tests/utils/assertion-helpers.ts
Normal file
39
backend/tests/utils/assertion-helpers.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { expect } from '@jest/globals';
|
||||||
|
import INTERNAL_CONFIG from '../../src/config/internal-config';
|
||||||
|
const RECORDINGS_PATH = `${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/recordings`;
|
||||||
|
|
||||||
|
export const expectValidRecordingLocationHeader = (response: any) => {
|
||||||
|
// const locationRegex = new RegExp(
|
||||||
|
// `^http://127\\.0\\.0\\.1:\\d+/+${RECORDINGS_PATH.replace(/\//g, '\\/')}/${recordingId}$`
|
||||||
|
// );
|
||||||
|
// expect(response.headers.location).toMatch(locationRegex);
|
||||||
|
expect(response.headers.location).toBeDefined();
|
||||||
|
expect(response.headers.location).toContain('127.0.0.1');
|
||||||
|
expect(response.headers.location).toContain(RECORDINGS_PATH);
|
||||||
|
expect(response.headers.location).toContain(response.body.recordingId);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const expectValidStartRecordingResponse = (response: any, roomId: string) => {
|
||||||
|
expect(response.status).toBe(201);
|
||||||
|
expect(response.body).toHaveProperty('recordingId');
|
||||||
|
const recordingId = response.body.recordingId;
|
||||||
|
expect(recordingId).toContain(roomId);
|
||||||
|
expect(response.body).toHaveProperty('roomId', roomId);
|
||||||
|
expect(response.body).toHaveProperty('startDate');
|
||||||
|
expect(response.body).toHaveProperty('status', 'ACTIVE');
|
||||||
|
expect(response.body).toHaveProperty('filename');
|
||||||
|
expect(response.body).not.toHaveProperty('duration');
|
||||||
|
expect(response.body).not.toHaveProperty('endDate');
|
||||||
|
expect(response.body).not.toHaveProperty('size');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const expectValidStopRecordingResponse = (response: any, recordingId: string, roomId: string) => {
|
||||||
|
expect(response.status).toBe(202);
|
||||||
|
expect(response.body).toBeDefined();
|
||||||
|
expect(response.body).toHaveProperty('recordingId', recordingId);
|
||||||
|
expect(response.body).toHaveProperty('status', 'ENDING');
|
||||||
|
expect(response.body).toHaveProperty('roomId', roomId);
|
||||||
|
expect(response.body).toHaveProperty('filename');
|
||||||
|
expect(response.body).toHaveProperty('startDate');
|
||||||
|
expect(response.body).toHaveProperty('duration', expect.any(Number));
|
||||||
|
};
|
||||||
@ -20,6 +20,8 @@ import INTERNAL_CONFIG from '../../src/config/internal-config.js';
|
|||||||
import { ChildProcess, execSync, spawn } from 'child_process';
|
import { ChildProcess, execSync, spawn } from 'child_process';
|
||||||
import { container } from '../../src/config/dependency-injector.config.js';
|
import { container } from '../../src/config/dependency-injector.config.js';
|
||||||
import { RoomService } from '../../src/services/room.service.js';
|
import { RoomService } from '../../src/services/room.service.js';
|
||||||
|
import { MeetRoomHelper } from '../../src/helpers/room.helper.js';
|
||||||
|
import { RecordingService } from '../../src/services/recording.service.js';
|
||||||
|
|
||||||
const CREDENTIALS = {
|
const CREDENTIALS = {
|
||||||
user: {
|
user: {
|
||||||
@ -288,6 +290,15 @@ export const runRoomGarbageCollector = async () => {
|
|||||||
await sleep(1000);
|
await sleep(1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const runReleaseActiveRecordingLock = async (roomId: string) => {
|
||||||
|
if (!app) {
|
||||||
|
throw new Error('App instance is not defined');
|
||||||
|
}
|
||||||
|
|
||||||
|
const recordingService = container.get(RecordingService);
|
||||||
|
await recordingService.releaseRoomRecordingActiveLock(roomId);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes all rooms
|
* Deletes all rooms
|
||||||
*/
|
*/
|
||||||
@ -362,7 +373,7 @@ export const generateParticipantToken = async (
|
|||||||
* @param roomId The ID of the room to join
|
* @param roomId The ID of the room to join
|
||||||
* @param participantName The name for the fake participant
|
* @param participantName The name for the fake participant
|
||||||
*/
|
*/
|
||||||
export const joinFakeParticipant = (roomId: string, participantName: string) => {
|
export const joinFakeParticipant = async (roomId: string, participantName: string) => {
|
||||||
// Step 1: Manually create the LiveKit room
|
// Step 1: Manually create the LiveKit room
|
||||||
// In normal operation, the room is created when a real participant requests a token,
|
// In normal operation, the room is created when a real participant requests a token,
|
||||||
// but for testing we need to create it ourselves since we're bypassing the token flow.
|
// but for testing we need to create it ourselves since we're bypassing the token flow.
|
||||||
@ -396,15 +407,115 @@ export const joinFakeParticipant = (roomId: string, participantName: string) =>
|
|||||||
|
|
||||||
// Store the process to be able to terminate it later
|
// Store the process to be able to terminate it later
|
||||||
fakeParticipantsProcesses.set(participantName, process);
|
fakeParticipantsProcesses.set(participantName, process);
|
||||||
|
await sleep(1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const disconnectFakeParticipants = () => {
|
export const disconnectFakeParticipants = async () => {
|
||||||
fakeParticipantsProcesses.forEach((process, participantName) => {
|
fakeParticipantsProcesses.forEach((process, participantName) => {
|
||||||
process.kill();
|
process.kill();
|
||||||
console.log(`Stopped process for participant ${participantName}`);
|
console.log(`Stopped process for participant ${participantName}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
fakeParticipantsProcesses.clear();
|
fakeParticipantsProcesses.clear();
|
||||||
|
await sleep(1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const startRecording = async (roomId: string, moderatorCookie = '') => {
|
||||||
|
if (!app) {
|
||||||
|
throw new Error('App instance is not defined');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await request(app)
|
||||||
|
.post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/recordings`)
|
||||||
|
.set('Cookie', moderatorCookie)
|
||||||
|
.send({
|
||||||
|
roomId
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stopRecording = async (recordingId: string, moderatorCookie = '') => {
|
||||||
|
if (!app) {
|
||||||
|
throw new Error('App instance is not defined');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/recordings/${recordingId}/stop`)
|
||||||
|
.set('Cookie', moderatorCookie)
|
||||||
|
.send();
|
||||||
|
await sleep(2500);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stopAllRecordings = async (moderatorCookie: string) => {
|
||||||
|
if (!app) {
|
||||||
|
throw new Error('App instance is not defined');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await getAllRecordings({ fields: 'recordingId' });
|
||||||
|
const recordingIds: string[] = response.body.recordings.map(
|
||||||
|
(recording: { recordingId: string }) => recording.recordingId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (recordingIds.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Stopping ${recordingIds.length} recordings...`);
|
||||||
|
const tasks = recordingIds.map((recordingId: string) =>
|
||||||
|
request(app)
|
||||||
|
.post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/recordings/${recordingId}/stop`)
|
||||||
|
.set('Cookie', moderatorCookie)
|
||||||
|
.send()
|
||||||
|
);
|
||||||
|
await Promise.all(tasks);
|
||||||
|
await sleep(1000); // Wait for 1 second
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAllRecordings = async (query: Record<string, any> = {}) => {
|
||||||
|
if (!app) {
|
||||||
|
throw new Error('App instance is not defined');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await request(app)
|
||||||
|
.get(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/recordings`)
|
||||||
|
.set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_API_KEY)
|
||||||
|
.query(query);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteAllRecordings = async () => {
|
||||||
|
if (!app) {
|
||||||
|
throw new Error('App instance is not defined');
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextPageToken: string | undefined;
|
||||||
|
|
||||||
|
do {
|
||||||
|
const response: any = await getAllRecordings({
|
||||||
|
fields: 'recordingId',
|
||||||
|
maxItems: 100,
|
||||||
|
nextPageToken
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
|
||||||
|
nextPageToken = response.body.pagination?.nextPageToken ?? undefined;
|
||||||
|
const recordingIds = response.body.recordings.map(
|
||||||
|
(recording: { recordingId: string }) => recording.recordingId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (recordingIds.length === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Deleting ${recordingIds.length} recordings...`);
|
||||||
|
console.log('Recording IDs:', recordingIds);
|
||||||
|
await request(app)
|
||||||
|
.delete(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/recordings`)
|
||||||
|
.query({ recordingIds: recordingIds.join(',')})
|
||||||
|
.set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_API_KEY);
|
||||||
|
|
||||||
|
await sleep(1000); // Wait for 1 second
|
||||||
|
} while (nextPageToken);
|
||||||
};
|
};
|
||||||
|
|
||||||
// PRIVATE METHODS
|
// PRIVATE METHODS
|
||||||
|
|||||||
61
backend/tests/utils/test-scenarios.ts
Normal file
61
backend/tests/utils/test-scenarios.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { MeetRoomHelper } from '../../src/helpers';
|
||||||
|
import { createRoom, loginUserAsRole, generateParticipantToken, joinFakeParticipant } from './helpers';
|
||||||
|
|
||||||
|
import { UserRole } from '../../src/typings/ce';
|
||||||
|
|
||||||
|
export interface RoomData {
|
||||||
|
room: any;
|
||||||
|
moderatorCookie: string;
|
||||||
|
moderatorSecret: string;
|
||||||
|
recordingId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TestContext {
|
||||||
|
rooms: RoomData[];
|
||||||
|
getRoomByIndex(index: number): RoomData | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configura un escenario de prueba con dos salas para pruebas de grabación concurrente
|
||||||
|
*/
|
||||||
|
export async function setupMultiRoomTestContext(numRooms: number, withParticipants: boolean): Promise<TestContext> {
|
||||||
|
const adminCookie = await loginUserAsRole(UserRole.ADMIN);
|
||||||
|
const rooms: RoomData[] = [];
|
||||||
|
|
||||||
|
// Create additional rooms
|
||||||
|
for (let i = 0; i < numRooms; i++) {
|
||||||
|
const room = await createRoom({
|
||||||
|
roomIdPrefix: `test-recording-room-${i + 1}`
|
||||||
|
});
|
||||||
|
const { moderatorSecret } = MeetRoomHelper.extractSecretsFromRoom(room);
|
||||||
|
const moderatorCookie = await generateParticipantToken(
|
||||||
|
adminCookie,
|
||||||
|
room.roomId,
|
||||||
|
`Moderator-${i + 1}`,
|
||||||
|
moderatorSecret
|
||||||
|
);
|
||||||
|
|
||||||
|
if (withParticipants) {
|
||||||
|
const participantId = `TEST_P-${i + 1}`;
|
||||||
|
|
||||||
|
await joinFakeParticipant(room.roomId, participantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
rooms.push({
|
||||||
|
room,
|
||||||
|
moderatorCookie,
|
||||||
|
moderatorSecret
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
rooms,
|
||||||
|
getRoomByIndex: (index: number) => {
|
||||||
|
if (index < 0 || index >= rooms.length) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rooms[index];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user