diff --git a/meet-ce/backend/openapi/components/parameters/x-expand-header.yaml b/meet-ce/backend/openapi/components/parameters/x-expand-header.yaml
new file mode 100644
index 00000000..b85ad096
--- /dev/null
+++ b/meet-ce/backend/openapi/components/parameters/x-expand-header.yaml
@@ -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
diff --git a/meet-ce/backend/openapi/components/parameters/x-fields-header.yaml b/meet-ce/backend/openapi/components/parameters/x-fields-header.yaml
new file mode 100644
index 00000000..50123b8c
--- /dev/null
+++ b/meet-ce/backend/openapi/components/parameters/x-fields-header.yaml
@@ -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
\ No newline at end of file
diff --git a/meet-ce/backend/openapi/paths/rooms.yaml b/meet-ce/backend/openapi/paths/rooms.yaml
index 70b63e78..77c67ffc 100644
--- a/meet-ce/backend/openapi/paths/rooms.yaml
+++ b/meet-ce/backend/openapi/paths/rooms.yaml
@@ -5,11 +5,20 @@
description: >
Creates a new OpenVidu Meet room.
The room will be available for participants to join using the generated URLs.
+
+ 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:
diff --git a/meet-ce/backend/src/controllers/room.controller.ts b/meet-ce/backend/src/controllers/room.controller.ts
index 2d3df7e6..153d112b 100644
--- a/meet-ce/backend/src/controllers/room.controller.ts
+++ b/meet-ce/backend/src/controllers/room.controller.ts
@@ -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}'`);
diff --git a/meet-ce/backend/src/helpers/room.helper.ts b/meet-ce/backend/src/helpers/room.helper.ts
index 093a3d8d..c83b2c32 100644
--- a/meet-ce/backend/src/helpers/room.helper.ts
+++ b/meet-ce/backend/src/helpers/room.helper.ts
@@ -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;
+
+ 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;
+ }
}
diff --git a/meet-ce/backend/src/middlewares/request-validators/room-validator.middleware.ts b/meet-ce/backend/src/middlewares/request-validators/room-validator.middleware.ts
index d2a4be55..9583558b 100644
--- a/meet-ce/backend/src/middlewares/request-validators/room-validator.middleware.ts
+++ b/meet-ce/backend/src/middlewares/request-validators/room-validator.middleware.ts
@@ -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();
};
diff --git a/meet-ce/backend/src/models/zod-schemas/room.schema.ts b/meet-ce/backend/src/models/zod-schemas/room.schema.ts
index 3e418bac..2c14e9d4 100644
--- a/meet-ce/backend/src/models/zod-schemas/room.schema.ts
+++ b/meet-ce/backend/src/models/zod-schemas/room.schema.ts
@@ -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)
diff --git a/meet-ce/backend/src/services/room.service.ts b/meet-ce/backend/src/services/room.service.ts
index 701ac84c..d5be46d8 100644
--- a/meet-ce/backend/src/services/room.service.ts
+++ b/meet-ce/backend/src/services/room.service.ts
@@ -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} A promise that resolves to the created OpenVidu room.
*
* @throws {Error} If the room creation fails.
*
*/
- async createMeetRoom(roomOptions: MeetRoomOptions): Promise {
+ async createMeetRoom(roomOptions: MeetRoomOptions, responseOpts?: GetMeetRoomOptions): Promise {
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 {
+ async getMeetRoom(roomId: string, opts?: GetMeetRoomOptions): Promise {
+ 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 {
- 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 {
- 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 {
// 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
diff --git a/meet-ce/backend/tests/helpers/request-helpers.ts b/meet-ce/backend/tests/helpers/request-helpers.ts
index 5c2ec9cf..76900b3d 100644
--- a/meet-ce/backend/tests/helpers/request-helpers.ts
+++ b/meet-ce/backend/tests/helpers/request-helpers.ts
@@ -387,7 +387,32 @@ export const deleteAllUsers = async () => {
// ROOM HELPERS
-export const createRoom = async (options: MeetRoomOptions = {}, accessToken?: string): Promise => {
+/**
+ * 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 => {
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;
};
diff --git a/meet-ce/backend/tests/integration/api/rooms/create-room.test.ts b/meet-ce/backend/tests/integration/api/rooms/create-room.test.ts
index abe90e45..04d983e5 100644
--- a/meet-ce/backend/tests/integration/api/rooms/create-room.test.ts
+++ b/meet-ce/backend/tests/integration/api/rooms/create-room.test.ts
@@ -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,
diff --git a/meet-ce/backend/tests/integration/api/rooms/e2ee-room-config.test.ts b/meet-ce/backend/tests/integration/api/rooms/e2ee-room-config.test.ts
index 844cbcfd..43f2678a 100644
--- a/meet-ce/backend/tests/integration/api/rooms/e2ee-room-config.test.ts
+++ b/meet-ce/backend/tests/integration/api/rooms/e2ee-room-config.test.ts
@@ -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
diff --git a/meet-ce/backend/tests/integration/api/rooms/recording-layout-room-config.test.ts b/meet-ce/backend/tests/integration/api/rooms/recording-layout-room-config.test.ts
index a450c9a4..4721e93d 100644
--- a/meet-ce/backend/tests/integration/api/rooms/recording-layout-room-config.test.ts
+++ b/meet-ce/backend/tests/integration/api/rooms/recording-layout-room-config.test.ts
@@ -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: {
diff --git a/meet-ce/backend/tests/integration/api/rooms/update-room-config.test.ts b/meet-ce/backend/tests/integration/api/rooms/update-room-config.test.ts
index 99e9f808..a1e362bf 100644
--- a/meet-ce/backend/tests/integration/api/rooms/update-room-config.test.ts
+++ b/meet-ce/backend/tests/integration/api/rooms/update-room-config.test.ts
@@ -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 = {