Enables response control via headers

Adds functionality to control the room creation and retrieval responses
using the `X-Fields` and `X-Expand` headers.

- `X-Fields` allows clients to specify which fields to include in the
  response, optimizing bandwidth usage.
- `X-Expand` allows clients to request the full data of expandable
  properties, such as `config`, avoiding subsequent GET requests.

This change introduces new request validators, service methods, and
helper functions to handle the header logic and process the room objects accordingly.
This commit is contained in:
CSantosM 2026-02-06 16:39:39 +01:00
parent ecef2844a0
commit d4a87f8a45
13 changed files with 367 additions and 107 deletions

View File

@ -0,0 +1,19 @@
name: X-Expand
in: header
description: >
Specifies which complex properties to include fully expanded in the response.
By default, certain large or nested properties (like `config`) are replaced with expandable stubs
to optimize payload size and reduce network bandwidth.
Use this header to include the full data of these properties in the creation response,
avoiding the need for a subsequent GET request.
Provide a comma-separated list of property names to expand.
required: false
schema:
type: string
examples:
config:
value: 'config'
summary: Include full room configuration in response

View File

@ -0,0 +1,15 @@
name: X-Fields
in: header
description: >
Specifies which fields to include in the response for the room resource.
Provide a comma-separated list of field names to filter the response payload.
This header allows you to optimize API responses by requesting only the data you need,
reducing bandwidth usage and improving performance.
required: false
schema:
type: string
examples:
basic:
value: 'roomId,roomName,accessUrl'
summary: Only return basic room information

View File

@ -5,11 +5,20 @@
description: >
Creates a new OpenVidu Meet room.
The room will be available for participants to join using the generated URLs.
<br/>
You can control the response format using custom headers:
- `X-Expand`: Include expanded properties (e.g., `config`) instead of stubs
- `X-Fields`: Filter which fields to include in the response for efficiency
tags:
- OpenVidu Meet - Rooms
security:
- apiKeyHeader: []
- accessTokenHeader: []
parameters:
- $ref: '../components/parameters/x-fields-header.yaml'
- $ref: '../components/parameters/x-expand-header.yaml'
requestBody:
$ref: '../components/requestBodies/create-room-request.yaml'
responses:

View File

@ -2,26 +2,42 @@ import {
MeetRoomDeletionPolicyWithMeeting,
MeetRoomDeletionPolicyWithRecordings,
MeetRoomDeletionSuccessCode,
MeetRoomFilters,
MeetRoomOptions
} from '@openvidu-meet/typings';
import { Request, Response } from 'express';
import { container } from '../config/dependency-injector.config.js';
import { INTERNAL_CONFIG } from '../config/internal-config.js';
import { MeetRoomHelper } from '../helpers/room.helper.js';
import { handleError } from '../models/error.model.js';
import { MeetRoomExpandableProperties, MeetRoomField, MeetRoomFilters } from '../models/room-request.js';
import { LoggerService } from '../services/logger.service.js';
import { RoomService } from '../services/room.service.js';
import { getBaseUrl } from '../utils/url.utils.js';
interface RequestWithValidatedHeaders extends Request {
validatedHeaders?: {
'x-fields'?: MeetRoomField[];
'x-expand'?: MeetRoomExpandableProperties[];
};
}
export const createRoom = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const roomService = container.get(RoomService);
const options: MeetRoomOptions = req.body;
const { validatedHeaders } = req as RequestWithValidatedHeaders;
const fields = validatedHeaders?.['x-fields'];
const expand = validatedHeaders?.['x-expand'];
try {
logger.verbose(`Creating room with options '${JSON.stringify(options)}'`);
const room = await roomService.createMeetRoom(options);
// Pass response options to service for consistent handling
const room = await roomService.createMeetRoom(options, {
fields,
collapse: MeetRoomHelper.toCollapseProperties(expand)
});
res.set('Location', `${getBaseUrl()}${INTERNAL_CONFIG.API_BASE_PATH_V1}/rooms/${room.roomId}`);
return res.status(201).json(room);
} catch (error) {
@ -49,14 +65,22 @@ export const getRoom = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const { roomId } = req.params;
const fields = req.query.fields as string | undefined;
const expand = req.query.expand as string | undefined;
// Zod already validated and transformed to typed arrays
const { fields, expand } = req.query as {
fields?: MeetRoomField[];
expand?: MeetRoomExpandableProperties[];
};
try {
logger.verbose(`Getting room '${roomId}' with expand: ${expand || 'none'}`);
logger.verbose(`Getting room '${roomId}' with expand: ${expand?.join(',') || 'none'}`);
const roomService = container.get(RoomService);
const room = await roomService.getMeetRoom(roomId, fields, expand, true);
const collapse = MeetRoomHelper.toCollapseProperties(expand);
const room = await roomService.getMeetRoom(roomId, {
fields,
collapse,
checkPermissions: true
});
return res.status(200).json(room);
} catch (error) {
@ -134,7 +158,7 @@ export const getRoomConfig = async (req: Request, res: Response) => {
logger.verbose(`Getting room config for room '${roomId}'`);
try {
const { config } = await roomService.getMeetRoom(roomId);
const { config } = await roomService.getMeetRoom(roomId, { fields: ['config'] });
return res.status(200).json(config);
} catch (error) {
handleError(res, error, `getting room config for room '${roomId}'`);

View File

@ -4,7 +4,8 @@ import { MEET_ENV } from '../environment.js';
import {
MEET_ROOM_EXPANDABLE_FIELDS,
MeetRoomCollapsibleProperties,
MeetRoomExpandableProperties
MeetRoomExpandableProperties,
MeetRoomField
} from '../models/room-request.js';
export class MeetRoomHelper {
@ -186,4 +187,36 @@ export class MeetRoomHelper {
return collapsedRoom;
}
/**
* Filters a MeetRoom object to include only the specified fields.
* By default, returns the full room object.
* Only filters when fields are explicitly specified.
*
* @param room - The room object to filter
* @param fields - Optional list of fields to include (e.g., ['roomId', 'roomName'])
* @returns A MeetRoom object containing only the specified fields, or the full room if no fields are specified
* @example
* ```
* // Filter to only roomId and roomName:
* const filtered = MeetRoomHelper.applyFieldsFilter(room, ['roomId', 'roomName']);
* // Result: { roomId: '123', roomName: 'My Room' }
* ```
*/
static applyFieldsFilter(room: MeetRoom, fields?: MeetRoomField[]): MeetRoom {
// If no fields specified, return the full room
if (!room || !fields || fields.length === 0) {
return room;
}
const filteredRoom = {} as Record<string, unknown>;
for (const key of Object.keys(room)) {
if (fields.includes(key as MeetRoomField)) {
filteredRoom[key] = room[key as keyof MeetRoom];
}
}
return filteredRoom as unknown as MeetRoom;
}
}

View File

@ -2,6 +2,7 @@ import { NextFunction, Request, Response } from 'express';
import { rejectUnprocessableRequest } from '../../models/error.model.js';
import {
BulkDeleteRoomsReqSchema,
CreateRoomHeadersSchema,
DeleteRoomReqSchema,
GetRoomQuerySchema,
nonEmptySanitizedRoomId,
@ -14,13 +15,22 @@ import {
} from '../../models/zod-schemas/room.schema.js';
export const validateCreateRoomReq = (req: Request, res: Response, next: NextFunction) => {
const { success, error, data } = RoomOptionsSchema.safeParse(req.body);
const bodyResult = RoomOptionsSchema.safeParse(req.body);
if (!success) {
return rejectUnprocessableRequest(res, error);
if (!bodyResult.success) {
return rejectUnprocessableRequest(res, bodyResult.error);
}
req.body = data;
// Validate X-Fields and X-Expand headers
const headersResult = CreateRoomHeadersSchema.safeParse(req.headers);
if (!headersResult.success) {
return rejectUnprocessableRequest(res, headersResult.error);
}
req.body = bodyResult.data;
// Store validated headers in a custom property for controller access
(req as any).validatedHeaders = headersResult.data;
next();
};

View File

@ -450,6 +450,11 @@ export const GetRoomQuerySchema = z.object({
expand: expandSchema
});
export const CreateRoomHeadersSchema = z.object({
'x-fields': fieldsSchema,
'x-expand': expandSchema
});
export const DeleteRoomReqSchema = z.object({
withMeeting: RoomDeletionPolicyWithMeetingSchema.optional().default(MeetRoomDeletionPolicyWithMeeting.FAIL),
withRecordings: RoomDeletionPolicyWithRecordingsSchema.optional().default(MeetRoomDeletionPolicyWithRecordings.FAIL)

View File

@ -8,7 +8,6 @@ import {
MeetRoomDeletionPolicyWithMeeting,
MeetRoomDeletionPolicyWithRecordings,
MeetRoomDeletionSuccessCode,
MeetRoomFilters,
MeetRoomMemberPermissions,
MeetRoomOptions,
MeetRoomRoles,
@ -35,6 +34,7 @@ import {
internalError,
OpenViduMeetError
} from '../models/error.model.js';
import { GetMeetRoomOptions, MeetRoomFilters } from '../models/room-request.js';
import { RoomMemberRepository } from '../repositories/room-member.repository.js';
import { RoomRepository } from '../repositories/room.repository.js';
import { FrontendEventService } from './frontend-event.service.js';
@ -71,13 +71,15 @@ export class RoomService {
* Creates an OpenVidu Meet room with the specified options.
*
* @param {MeetRoomOptions} roomOptions - The options for creating the OpenVidu room.
* @param {GetMeetRoomOptions} responseOpts - Options for controlling the response format (fields, collapse)
* @returns {Promise<MeetRoom>} A promise that resolves to the created OpenVidu room.
*
* @throws {Error} If the room creation fails.
*
*/
async createMeetRoom(roomOptions: MeetRoomOptions): Promise<MeetRoom> {
async createMeetRoom(roomOptions: MeetRoomOptions, responseOpts?: GetMeetRoomOptions): Promise<MeetRoom> {
const { roomName, autoDeletionDate, autoDeletionPolicy, config, roles, anonymous } = roomOptions;
const { collapse, fields } = responseOpts || {};
// Generate a unique room ID based on the room name
const roomIdPrefix = MeetRoomHelper.createRoomIdPrefixFromRoomName(roomName!) || 'room';
@ -159,10 +161,12 @@ export class RoomService {
rolesUpdatedAt: now,
meetingEndAction: MeetingEndAction.NONE
};
const room = await this.roomRepository.create(meetRoom);
let room = await this.roomRepository.create(meetRoom);
// Avoid include full config in payload
return MeetRoomHelper.processRoomExpandProperties(room, '');
room = MeetRoomHelper.applyCollapseProperties(room, collapse);
room = MeetRoomHelper.applyFieldsFilter(room, fields);
return room;
}
/**
@ -222,7 +226,9 @@ export class RoomService {
}
await this.roomRepository.update(room);
// Send signal to frontend
// Send signal to frontend.
// Note: Rooms updates are not allowed during active meetings, so we don't need to send an immediate update signal to participants,
// as they will receive the updated config when they join the meeting or when the meeting is restarted.
// await this.frontendEventService.sendRoomConfigUpdatedSignal(roomId, room);
return room;
}
@ -345,8 +351,8 @@ export class RoomService {
const response = await this.roomRepository.find(queryOptions);
// Process rooms with expand logic
response.rooms = response.rooms.map((room) => MeetRoomHelper.processRoomExpandProperties(room, filters.expand));
const collapse = MeetRoomHelper.toCollapseProperties(filters.expand);
response.rooms = response.rooms.map((room) => MeetRoomHelper.applyCollapseProperties(room, collapse));
return response;
}
@ -405,15 +411,17 @@ export class RoomService {
}
/**
* Retrieves an OpenVidu room by its name.
* Retrieves a specific meeting room by its unique identifier.
*
* @param roomId - The name of the room to retrieve.
* @param fields - Optional fields to retrieve from the room.
* @param expand - Optional comma-separated list of properties to expand.
* @param checkPermissions - Whether to check permissions and remove sensitive properties. Defaults to false.
* @returns A promise that resolves to an {@link MeetRoom} object (with expandable properties as stubs when not expanded).
* @param opts - Optional parameters for retrieving the room:
* - fields: Array of fields to retrieve from the room
* - collapse: {@link MeetRoomCollapsible} list of properties to collapse into {@link ExpandableStub}
* - checkPermissions: Whether to check permissions and remove sensitive properties
* @returns A promise that resolves to an {@link MeetRoom} object
*/
async getMeetRoom(roomId: string, fields?: string, expand?: string, checkPermissions = false): Promise<MeetRoom> {
async getMeetRoom(roomId: string, opts?: GetMeetRoomOptions): Promise<MeetRoom> {
const { collapse, checkPermissions, fields } = opts || {};
const room = await this.roomRepository.findByRoomId(roomId, fields);
if (!room) {
@ -430,8 +438,7 @@ export class RoomService {
}
}
// Process expand
return MeetRoomHelper.processRoomExpandProperties(room, expand);
return MeetRoomHelper.applyCollapseProperties(room, collapse);
}
/**
@ -458,7 +465,7 @@ export class RoomService {
);
// Check if there's an active meeting in the room and/or if it has recordings associated
const room = await this.getMeetRoom(roomId);
const room = await this.getMeetRoom(roomId, { fields: ['status'] });
const hasActiveMeeting = room.status === MeetRoomStatus.ACTIVE_MEETING;
const hasRecordings = await this.recordingService.hasRoomRecordings(roomId);
@ -479,7 +486,7 @@ export class RoomService {
hasRecordings,
withMeeting,
withRecordings,
updatedRoom
MeetRoomHelper.applyCollapseProperties(updatedRoom!, ['config'])
);
} catch (error) {
this.logger.error(`Error deleting room '${roomId}': ${error}`);
@ -851,7 +858,7 @@ export class RoomService {
* @throws Error if room not found
*/
async isRoomOwner(roomId: string, userId: string): Promise<boolean> {
const room = await this.getMeetRoom(roomId);
const room = await this.getMeetRoom(roomId, { fields: ['owner'] });
return room.owner === userId;
}
@ -864,7 +871,7 @@ export class RoomService {
* @throws Error if room not found
*/
async isValidRoomSecret(roomId: string, secret: string): Promise<boolean> {
const room = await this.getMeetRoom(roomId);
const room = await this.getMeetRoom(roomId, { fields: ['anonymous'] });
const { moderatorSecret, speakerSecret } = MeetRoomHelper.extractSecretsFromRoom(room);
return secret === moderatorSecret || secret === speakerSecret;
}
@ -928,7 +935,7 @@ export class RoomService {
*/
async canUserAccessRoom(roomId: string, user: MeetUser): Promise<boolean> {
// Verify room exists first (throws 404 if not found)
const room = await this.getMeetRoom(roomId);
const room = await this.getMeetRoom(roomId, { fields: ['owner'] });
if (user.role === MeetUserRole.ADMIN) {
// Admins can access all rooms

View File

@ -387,7 +387,32 @@ export const deleteAllUsers = async () => {
// ROOM HELPERS
export const createRoom = async (options: MeetRoomOptions = {}, accessToken?: string): Promise<MeetRoom> => {
/**
* Creates a room with the specified options and optional headers for response customization.
*
* @param options - Room creation options (roomName, config, etc.)
* @param accessToken - Optional access token for authentication (uses API key if not provided)
* @param headers - Optional headers object supporting:
* - xFields: Comma-separated list of fields to include (e.g., 'roomId,roomName')
* - xExpand: Comma-separated list of properties to expand (e.g., 'config')
* @returns A Promise that resolves to the created MeetRoom
* @example
* ```
* // Create room with default collapsed config
* const room = await createRoom({ roomName: 'Test' });
*
* // Create room with specific fields only
* const room = await createRoom({ roomName: 'Test' }, undefined, { xFields: 'roomId,roomName' });
*
* // Create room with expanded config
* const room = await createRoom({ roomName: 'Test' }, undefined, { xExpand: 'config' });
* ```
*/
export const createRoom = async (
options: MeetRoomOptions = {},
accessToken?: string,
headers?: { xFields?: string; xExpand?: string }
): Promise<MeetRoom> => {
checkAppIsRunning();
const req = request(app)
@ -401,6 +426,15 @@ export const createRoom = async (options: MeetRoomOptions = {}, accessToken?: st
req.set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_ENV.INITIAL_API_KEY);
}
// Add optional headers for response customization
if (headers?.xFields) {
req.set('x-fields', headers.xFields);
}
if (headers?.xExpand) {
req.set('x-expand', headers.xExpand);
}
const response = await req;
return response.body;
};

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { afterAll, beforeAll, describe, expect, it } from '@jest/globals';
import {
MeetRecordingAudioCodec,
@ -168,6 +169,56 @@ describe('Room API Tests', () => {
validAutoDeletionDate
);
});
it('should create a room with collapsed config by default', async () => {
const room = await createRoom({
roomName: 'Collapsed Config Room'
});
expect(room.config).toBeDefined();
expect((room.config as any)._expandable).toBe(true);
expect((room.config as any)._href).toBe(
`${INTERNAL_CONFIG.API_BASE_PATH_V1}/rooms/${room.roomId}?expand=config`
);
});
it('should expand config when x-Expand header is provided', async () => {
const room = await createRoom(
{
roomName: 'Collapsed Config Room'
},
undefined,
{ xExpand: 'config' }
);
expect(room.config).toBeDefined();
expect((room.config as any)._expandable).toBeUndefined();
expect((room.config as any)._href).toBeUndefined();
expect(room.config.recording.layout).toBe(DEFAULT_RECORDING_LAYOUT);
});
it('should filter fields when x-Field header is provided', async () => {
const room = await createRoom(undefined, undefined, { xFields: 'roomName' });
expect(room.roomName).toBeDefined();
expect(room.roomId).toBeUndefined();
expect(room.config).toBeUndefined();
});
it('should filter fields and expand config when both xFields and xExpand are provided', async () => {
const room = await createRoom(undefined, undefined, { xFields: 'config', xExpand: 'config' });
expect(room.roomName).toBeUndefined();
expect(room.config).toBeDefined();
expect((room.config as any)._expandable).toBeUndefined();
});
it('should not includes config if filter fields are provided without config', async () => {
const room = await createRoom(undefined, undefined, { xFields: 'roomName', xExpand: 'config' });
expect(room.roomName).toBeDefined();
expect(room.config).toBeUndefined();
});
});
describe('Room Name Sanitization Tests', () => {
@ -457,11 +508,32 @@ describe('Room API Tests', () => {
};
expectValidRoom(room, 'Full Advanced Encoding Room', 'full_advanced_encoding_room', 'expandable');
const response = await getRoom(room.roomId, undefined, 'config');
expectValidRoom(response.body, 'Full Advanced Encoding Room', 'full_advanced_encoding_room', expectedConfig);
expectValidRoom(
response.body,
'Full Advanced Encoding Room',
'full_advanced_encoding_room',
expectedConfig
);
});
});
describe('Room Creation Validation failures', () => {
it('should fail when x-Expand header has invalid value', async () => {
const payload = {
roomName: 'Test Room with Invalid Expand Header'
};
const response = await request(app)
.post(ROOMS_PATH)
.set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_ENV.INITIAL_API_KEY)
.set('x-Expand', 'invalidField')
.send(payload)
.expect(422);
expect(response.body.error).toContain('Unprocessable Entity');
expect(JSON.stringify(response.body.details)).toContain('Invalid expand properties.');
});
it('should fail when autoDeletionDate is negative', async () => {
const payload = {
autoDeletionDate: -5000,

View File

@ -4,7 +4,6 @@ import { Express } from 'express';
import request from 'supertest';
import { INTERNAL_CONFIG } from '../../../../src/config/internal-config.js';
import { MEET_ENV } from '../../../../src/environment.js';
import { expectValidRoom } from '../../../helpers/assertion-helpers.js';
import {
createRoom,
deleteAllRecordings,
@ -33,11 +32,19 @@ describe('E2EE Room Configuration Tests', () => {
describe('E2EE Default Configuration', () => {
it('Should create a room with E2EE disabled by default', async () => {
const room = await createRoom({
roomName: 'Test E2EE Default'
});
const room = await createRoom(
{
roomName: 'Test E2EE Default'
},
undefined,
{ xExpand: 'config' }
);
expectValidRoom(room, 'Test E2EE Default');
// Validate room structure (skip config validation in expectValidRoom since we're checking it below)
expect(room).toBeDefined();
expect(room.roomName).toBe('Test E2EE Default');
expect(room.roomId).toBeDefined();
expect(room.config).toBeDefined();
expect(room.config.e2ee).toBeDefined();
expect(room.config.e2ee.enabled).toBe(false);
});
@ -58,7 +65,7 @@ describe('E2EE Room Configuration Tests', () => {
}
};
const room = await createRoom(payload);
const room = await createRoom(payload, undefined, { xFields: 'roomName,config', xExpand: 'config' });
expect(room.roomName).toBe('Test E2EE Enabled');
expect(room.config.e2ee.enabled).toBe(true);
@ -84,18 +91,22 @@ describe('E2EE Room Configuration Tests', () => {
it('Should disable recording when updating room config to enable E2EE', async () => {
// Create room with recording enabled and E2EE disabled
const room = await createRoom({
roomName: 'Test E2EE Update',
config: {
recording: {
enabled: true
},
chat: { enabled: true },
virtualBackground: { enabled: true },
e2ee: { enabled: false },
captions: { enabled: true }
}
});
const room = await createRoom(
{
roomName: 'Test E2EE Update',
config: {
recording: {
enabled: true
},
chat: { enabled: true },
virtualBackground: { enabled: true },
e2ee: { enabled: false },
captions: { enabled: true }
}
},
undefined,
{ xExpand: 'config' }
);
expect(room.config.recording.enabled).toBe(true);
expect(room.config.e2ee?.enabled).toBe(false);
@ -167,9 +178,13 @@ describe('E2EE Room Configuration Tests', () => {
describe('E2EE Update Configuration Tests', () => {
it('Should successfully update room config with E2EE disabled to enabled', async () => {
const room = await createRoom({
roomName: 'Test E2EE Update Enabled'
});
const room = await createRoom(
{
roomName: 'Test E2EE Update Enabled'
},
undefined,
{ xExpand: 'config' }
);
expect(room.config.e2ee.enabled).toBe(false);
@ -198,35 +213,44 @@ describe('E2EE Room Configuration Tests', () => {
it('Should return E2EE configuration when listing rooms', async () => {
await deleteAllRooms();
const room1 = await createRoom({
roomName: 'E2EE Enabled Room',
config: {
recording: {
enabled: false
},
chat: { enabled: true },
virtualBackground: { enabled: true },
e2ee: { enabled: true },
captions: { enabled: true }
}
});
const room1 = await createRoom(
{
roomName: 'E2EE Enabled Room',
config: {
recording: {
enabled: false
},
chat: { enabled: true },
virtualBackground: { enabled: true },
e2ee: { enabled: true },
captions: { enabled: true }
}
},
undefined,
{ xFields: 'roomId' }
);
const room2 = await createRoom({
roomName: 'E2EE Disabled Room',
config: {
recording: {
enabled: true
},
chat: { enabled: true },
virtualBackground: { enabled: true },
e2ee: { enabled: false },
captions: { enabled: true }
}
});
const room2 = await createRoom(
{
roomName: 'E2EE Disabled Room',
config: {
recording: {
enabled: true
},
chat: { enabled: true },
virtualBackground: { enabled: true },
e2ee: { enabled: false },
captions: { enabled: true }
}
},
undefined,
{ xFields: 'roomId' }
);
const response = await request(app)
.get(ROOMS_PATH)
.set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_ENV.INITIAL_API_KEY)
.query({ expand: 'config' })
.expect(200);
// Filter out any rooms from other test suites

View File

@ -26,7 +26,7 @@ describe('Room API Tests', () => {
}
};
const room = await createRoom(payload);
const room = await createRoom(payload, undefined, { xExpand: 'config' });
const expectedConfig = {
recording: {
@ -53,7 +53,7 @@ describe('Room API Tests', () => {
}
};
const room = await createRoom(payload);
const room = await createRoom(payload, undefined, { xExpand: 'config' });
const expectedConfig = {
recording: {
@ -80,7 +80,7 @@ describe('Room API Tests', () => {
}
};
const room = await createRoom(payload);
const room = await createRoom(payload, undefined, { xExpand: 'config' });
const expectedConfig = {
recording: {

View File

@ -60,7 +60,7 @@ describe('Room API Tests', () => {
expect(updateResponse.body).toHaveProperty('message');
// Verify with a get request
const getResponse = await getRoom(createdRoom.roomId);
const getResponse = await getRoom(createdRoom.roomId, 'config', 'config');
expect(getResponse.status).toBe(200);
expect(getResponse.body.config).toEqual({
...updatedConfig,
@ -97,7 +97,7 @@ describe('Room API Tests', () => {
expect(updateResponse.body).toHaveProperty('message');
// Verify with a get request
const getResponse = await getRoom(createdRoom.roomId);
const getResponse = await getRoom(createdRoom.roomId, undefined, 'config');
expect(getResponse.status).toBe(200);
const expectedConfig: MeetRoomConfig = {
@ -179,7 +179,7 @@ describe('Room API Tests', () => {
expect(updateResponse.status).toBe(200);
// Verify with a get request
const getResponse = await getRoom(createdRoom.roomId);
const getResponse = await getRoom(createdRoom.roomId, 'config', 'config');
expect(getResponse.status).toBe(200);
expect(getResponse.body.config.recording.encoding).toBe(MeetRecordingEncodingPreset.H264_1080P_60);
});
@ -221,12 +221,12 @@ describe('Room API Tests', () => {
expect(updateResponse.status).toBe(200);
// Verify with a get request
const getResponse = await getRoom(createdRoom.roomId);
const getResponse = await getRoom(createdRoom.roomId, undefined, 'config');
expect(getResponse.status).toBe(200);
expect(getResponse.body.config.recording.encoding).toMatchObject(updatedConfig.recording.encoding);
});
it('should update room encoding from advanced options to preset', async () => {
it('should update room encoding config from advanced options to preset', async () => {
const recordingEncoding = {
video: {
width: 1280,
@ -243,15 +243,19 @@ describe('Room API Tests', () => {
frequency: 44100
}
};
const createdRoom = await createRoom({
roomName: 'advanced-to-preset',
config: {
recording: {
enabled: true,
encoding: recordingEncoding
const createdRoom = await createRoom(
{
roomName: 'advanced-to-preset',
config: {
recording: {
enabled: true,
encoding: recordingEncoding
}
}
}
});
},
undefined,
{ xExpand: 'config' }
);
expect(createdRoom.config.recording.encoding).toMatchObject(recordingEncoding);
// Update to preset encoding
@ -266,22 +270,26 @@ describe('Room API Tests', () => {
expect(updateResponse.status).toBe(200);
// Verify with a get request
const getResponse = await getRoom(createdRoom.roomId);
const getResponse = await getRoom(createdRoom.roomId, undefined, 'config');
expect(getResponse.status).toBe(200);
expect(getResponse.body.config.recording.encoding).toBe(MeetRecordingEncodingPreset.PORTRAIT_H264_1080P_60);
});
it('should update only encoding while keeping other recording config', async () => {
const createdRoom = await createRoom({
roomName: 'partial-encoding-update',
config: {
recording: {
enabled: true,
layout: MeetRecordingLayout.SPEAKER,
encoding: MeetRecordingEncodingPreset.H264_720P_30
const createdRoom = await createRoom(
{
roomName: 'partial-encoding-update',
config: {
recording: {
enabled: true,
layout: MeetRecordingLayout.SPEAKER,
encoding: MeetRecordingEncodingPreset.H264_720P_30
}
}
}
});
},
undefined,
{ xExpand: 'config' }
);
expect(createdRoom.config.recording.layout).toBe(MeetRecordingLayout.SPEAKER);
expect(createdRoom.config.recording.encoding).toBe(MeetRecordingEncodingPreset.H264_720P_30);
@ -298,7 +306,7 @@ describe('Room API Tests', () => {
expect(updateResponse.status).toBe(200);
// Verify with a get request
const getResponse = await getRoom(createdRoom.roomId);
const getResponse = await getRoom(createdRoom.roomId, 'config', 'config');
expect(getResponse.status).toBe(200);
const expectedConfig = {