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 d974ac46..bd55d428 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 @@ -1,4 +1,5 @@ import { + MeetAppearanceConfig, MeetChatConfig, MeetE2EEConfig, MeetRecordingAccess, @@ -10,6 +11,7 @@ import { MeetRoomFilters, MeetRoomOptions, MeetRoomStatus, + MeetRoomTheme, MeetRoomThemeMode, MeetVirtualBackgroundConfig, ParticipantRole, @@ -100,7 +102,7 @@ const hexColorSchema = z 'Must be a valid hex color code (with or without alpha)' ); -const RoomThemeSchema = z.object({ +const RoomThemeSchema: z.ZodType = z.object({ name: z .string() .min(1, 'Theme name cannot be empty') @@ -115,7 +117,7 @@ const RoomThemeSchema = z.object({ surfaceColor: hexColorSchema.optional() }); -export const AppearanceConfigSchema = z.object({ +export const AppearanceConfigSchema: z.ZodType = z.object({ themes: z.array(RoomThemeSchema).length(1, 'There must be exactly one theme defined') }); diff --git a/meet-ce/backend/src/models/error.model.ts b/meet-ce/backend/src/models/error.model.ts index 986599e5..d8b72bee 100644 --- a/meet-ce/backend/src/models/error.model.ts +++ b/meet-ce/backend/src/models/error.model.ts @@ -55,11 +55,11 @@ export const errorLivekitNotAvailable = (): OpenViduMeetError => { return new OpenViduMeetError('LiveKit Error', 'LiveKit is not available', 503); }; -export const errorS3NotAvailable = (error: any): OpenViduMeetError => { +export const errorS3NotAvailable = (error: unknown): OpenViduMeetError => { return new OpenViduMeetError('S3 Error', `S3 is not available ${error}`, 503); }; -export const errorAzureNotAvailable = (error: any): OpenViduMeetError => { +export const errorAzureNotAvailable = (error: unknown): OpenViduMeetError => { return new OpenViduMeetError('ABS Error', `Azure Blob Storage is not available ${error}`, 503); }; diff --git a/meet-ce/backend/tests/helpers/request-helpers.ts b/meet-ce/backend/tests/helpers/request-helpers.ts index 8a7c610f..10b18518 100644 --- a/meet-ce/backend/tests/helpers/request-helpers.ts +++ b/meet-ce/backend/tests/helpers/request-helpers.ts @@ -1,8 +1,8 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { expect } from '@jest/globals'; import { AuthMode, AuthTransportMode, + MeetAppearanceConfig, MeetRecordingAccess, MeetRecordingInfo, MeetRecordingStatus, @@ -11,7 +11,10 @@ import { MeetRoomDeletionPolicyWithMeeting, MeetRoomDeletionPolicyWithRecordings, MeetRoomOptions, + MeetTokenMetadata, + ParticipantOptions, ParticipantRole, + SecurityConfig, WebhookConfig } from '@openvidu-meet/typings'; import { ChildProcess, spawn } from 'child_process'; @@ -112,7 +115,7 @@ export const getRoomsAppearanceConfig = async () => { return response; }; -export const updateRoomsAppearanceConfig = async (config: any) => { +export const updateRoomsAppearanceConfig = async (config: { appearance: MeetAppearanceConfig }) => { checkAppIsRunning(); const accessToken = await loginUser(); @@ -162,7 +165,7 @@ export const getSecurityConfig = async () => { return response; }; -export const updateSecurityConfig = async (config: any) => { +export const updateSecurityConfig = async (config: SecurityConfig) => { checkAppIsRunning(); const accessToken = await loginUser(); @@ -282,7 +285,7 @@ export const createRoom = async (options: MeetRoomOptions = {}): Promise = {}) => { +export const getRooms = async (query: Record = {}) => { checkAppIsRunning(); return await request(app) @@ -359,7 +362,7 @@ export const updateRoomStatus = async (roomId: string, status: string) => { .send({ status }); }; -export const deleteRoom = async (roomId: string, query: Record = {}) => { +export const deleteRoom = async (roomId: string, query: Record = {}) => { checkAppIsRunning(); const result = await request(app) @@ -370,7 +373,7 @@ export const deleteRoom = async (roomId: string, query: Record = {} return result; }; -export const bulkDeleteRooms = async (roomIds: any[], withMeeting?: string, withRecordings?: string) => { +export const bulkDeleteRooms = async (roomIds: string[], withMeeting?: string, withRecordings?: string) => { checkAppIsRunning(); const result = await request(app) @@ -445,7 +448,10 @@ export const getRoomRoleBySecret = async (roomId: string, secret: string) => { return response; }; -export const generateParticipantTokenRequest = async (participantOptions: any, previousToken?: string) => { +export const generateParticipantTokenRequest = async ( + participantOptions: ParticipantOptions, + previousToken?: string +) => { checkAppIsRunning(); // Disable authentication to generate the token @@ -489,7 +495,7 @@ export const generateParticipantToken = async ( return `Bearer ${response.body.token}`; }; -export const refreshParticipantToken = async (participantOptions: any, previousToken: string) => { +export const refreshParticipantToken = async (participantOptions: ParticipantOptions, previousToken: string) => { checkAppIsRunning(); // Disable authentication to generate the token @@ -534,7 +540,11 @@ export const joinFakeParticipant = async (roomId: string, participantIdentity: s * @param participantIdentity The identity of the participant * @param metadata The metadata to update */ -export const updateParticipantMetadata = async (roomId: string, participantIdentity: string, metadata: any) => { +export const updateParticipantMetadata = async ( + roomId: string, + participantIdentity: string, + metadata: MeetTokenMetadata +) => { await ensureLivekitCliInstalled(); spawn('lk', [ 'room', @@ -748,7 +758,7 @@ export const deleteRecording = async (recordingId: string) => { .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY); }; -export const bulkDeleteRecordings = async (recordingIds: any[], recordingToken?: string): Promise => { +export const bulkDeleteRecordings = async (recordingIds: string[], recordingToken?: string): Promise => { checkAppIsRunning(); const req = request(app) @@ -822,7 +832,7 @@ export const stopAllRecordings = async (moderatorToken: string) => { await sleep('1s'); }; -export const getAllRecordings = async (query: Record = {}) => { +export const getAllRecordings = async (query: Record = {}) => { checkAppIsRunning(); return await request(app) @@ -845,7 +855,7 @@ export const deleteAllRecordings = async () => { let nextPageToken: string | undefined; do { - const response: any = await getAllRecordings({ + const response = await getAllRecordings({ fields: 'recordingId', maxItems: 100, nextPageToken diff --git a/meet-ce/backend/tests/integration/api/global-config/appearance.test.ts b/meet-ce/backend/tests/integration/api/global-config/appearance.test.ts index f80640e6..ed493996 100644 --- a/meet-ce/backend/tests/integration/api/global-config/appearance.test.ts +++ b/meet-ce/backend/tests/integration/api/global-config/appearance.test.ts @@ -218,7 +218,7 @@ describe('Rooms Appearance Config API Tests', () => { } ] } - }); + } as unknown as { appearance: MeetAppearanceConfig }); expectValidationError(response, 'appearance.themes.0.enabled', 'Expected boolean, received string'); }); @@ -233,7 +233,7 @@ describe('Rooms Appearance Config API Tests', () => { } ] } - }); + } as unknown as { appearance: MeetAppearanceConfig }); expectValidationError( response, @@ -253,7 +253,7 @@ describe('Rooms Appearance Config API Tests', () => { } ] } - }); + } as unknown as { appearance: MeetAppearanceConfig }); expectValidationError(response, 'appearance.themes.0.backgroundColor', 'Must be a valid hex color code'); response = await updateRoomsAppearanceConfig({ @@ -266,7 +266,7 @@ describe('Rooms Appearance Config API Tests', () => { } ] } - }); + } as unknown as { appearance: MeetAppearanceConfig }); expectValidationError(response, 'appearance.themes.0.primaryColor', 'Must be a valid hex color code'); response = await updateRoomsAppearanceConfig({ @@ -279,7 +279,7 @@ describe('Rooms Appearance Config API Tests', () => { } ] } - }); + } as unknown as { appearance: MeetAppearanceConfig }); expectValidationError(response, 'appearance.themes.0.secondaryColor', 'Must be a valid hex color code'); response = await updateRoomsAppearanceConfig({ @@ -292,7 +292,7 @@ describe('Rooms Appearance Config API Tests', () => { } ] } - }); + } as unknown as { appearance: MeetAppearanceConfig }); expectValidationError(response, 'appearance.themes.0.surfaceColor', 'Must be a valid hex color code'); }); @@ -326,7 +326,7 @@ describe('Rooms Appearance Config API Tests', () => { } ] } - }); + } as unknown as { appearance: MeetAppearanceConfig }); expectValidationError(response, 'appearance.themes.0.name', 'Required'); response = await updateRoomsAppearanceConfig({ @@ -338,7 +338,7 @@ describe('Rooms Appearance Config API Tests', () => { } ] } - }); + } as unknown as { appearance: MeetAppearanceConfig }); expectValidationError(response, 'appearance.themes.0.enabled', 'Required'); response = await updateRoomsAppearanceConfig({ @@ -350,14 +350,14 @@ describe('Rooms Appearance Config API Tests', () => { } ] } - }); + } as unknown as { appearance: MeetAppearanceConfig }); expectValidationError(response, 'appearance.themes.0.baseTheme', 'Required'); }); it('should reject when appearance is not an object', async () => { const response = await updateRoomsAppearanceConfig({ appearance: 'invalid' - }); + } as unknown as { appearance: MeetAppearanceConfig }); expectValidationError(response, 'appearance', 'Expected object, received string'); }); @@ -367,7 +367,7 @@ describe('Rooms Appearance Config API Tests', () => { appearance: { themes: 'invalid' } - }); + } as unknown as { appearance: MeetAppearanceConfig }); expectValidationError(response, 'appearance.themes', 'Expected array, received string'); }); diff --git a/meet-ce/backend/tests/integration/api/global-config/security.test.ts b/meet-ce/backend/tests/integration/api/global-config/security.test.ts index 6979bc7c..0f31900d 100644 --- a/meet-ce/backend/tests/integration/api/global-config/security.test.ts +++ b/meet-ce/backend/tests/integration/api/global-config/security.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeAll, describe, expect, it } from '@jest/globals'; -import { AuthMode, AuthTransportMode, AuthType } from '@openvidu-meet/typings'; +import { AuthMode, AuthTransportMode, AuthType, SecurityConfig } from '@openvidu-meet/typings'; import { expectValidationError } from '../../../helpers/assertion-helpers.js'; import { getSecurityConfig, @@ -48,7 +48,7 @@ describe('Security Config API Tests', () => { }, authModeToAccessRoom: 'invalid' } - }); + } as unknown as SecurityConfig); expectValidationError( response, @@ -65,7 +65,7 @@ describe('Security Config API Tests', () => { }, authModeToAccessRoom: AuthMode.ALL_USERS } - }); + } as unknown as SecurityConfig); expectValidationError( response, @@ -83,7 +83,7 @@ describe('Security Config API Tests', () => { authModeToAccessRoom: AuthMode.ALL_USERS, authTransportMode: 'invalid' } - }); + } as unknown as SecurityConfig); expectValidationError( response, @@ -98,7 +98,7 @@ describe('Security Config API Tests', () => { authMode: AuthMode.NONE, authTransportMode: AuthTransportMode.HEADER } - }); + } as unknown as SecurityConfig); expectValidationError(response, 'authentication.authMethod', 'Required'); response = await updateSecurityConfig({ @@ -108,7 +108,7 @@ describe('Security Config API Tests', () => { }, authModeToAccessRoom: AuthMode.NONE } - }); + } as unknown as SecurityConfig); expectValidationError(response, 'authentication.authTransportMode', 'Required'); response = await updateSecurityConfig({ @@ -118,14 +118,14 @@ describe('Security Config API Tests', () => { }, authTransportMode: AuthTransportMode.HEADER } - }); + } as unknown as SecurityConfig); expectValidationError(response, 'authentication.authModeToAccessRoom', 'Required'); }); it('should reject when authentication is not an object', async () => { const response = await updateSecurityConfig({ authentication: 'invalid' - }); + } as unknown as SecurityConfig); expectValidationError(response, 'authentication', 'Expected object, received string'); }); diff --git a/meet-ce/backend/tests/integration/api/participants/generate-token.test.ts b/meet-ce/backend/tests/integration/api/participants/generate-token.test.ts index 1844ebd7..b05bf6a0 100644 --- a/meet-ce/backend/tests/integration/api/participants/generate-token.test.ts +++ b/meet-ce/backend/tests/integration/api/participants/generate-token.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from '@jest/globals'; +import { AuthTransportMode, ParticipantOptions, ParticipantRole } from '@openvidu-meet/typings'; import { INTERNAL_CONFIG } from '../../../../src/config/internal-config.js'; -import { AuthTransportMode, ParticipantRole } from '@openvidu-meet/typings'; import { expectValidationError, expectValidParticipantTokenResponse } from '../../../helpers/assertion-helpers.js'; import { changeAuthTransportMode, @@ -204,7 +204,7 @@ describe('Participant API Tests', () => { const response = await generateParticipantTokenRequest({ secret: roomData.moderatorSecret, participantName - }); + } as unknown as ParticipantOptions); expectValidationError(response, 'roomId', 'Required'); }); @@ -212,7 +212,7 @@ describe('Participant API Tests', () => { const response = await generateParticipantTokenRequest({ roomId: roomData.room.roomId, participantName - }); + } as unknown as ParticipantOptions); expectValidationError(response, 'secret', 'Required'); }); diff --git a/meet-ce/backend/tests/integration/api/participants/refresh-token.test.ts b/meet-ce/backend/tests/integration/api/participants/refresh-token.test.ts index 17063fc5..4a44e917 100644 --- a/meet-ce/backend/tests/integration/api/participants/refresh-token.test.ts +++ b/meet-ce/backend/tests/integration/api/participants/refresh-token.test.ts @@ -1,6 +1,6 @@ import { afterAll, beforeAll, describe, expect, it } from '@jest/globals'; +import { AuthTransportMode, ParticipantOptions, ParticipantRole } from '@openvidu-meet/typings'; import { INTERNAL_CONFIG } from '../../../../src/config/internal-config.js'; -import { AuthTransportMode, ParticipantRole } from '@openvidu-meet/typings'; import { expectValidationError, expectValidParticipantTokenResponse } from '../../../helpers/assertion-helpers.js'; import { changeAuthTransportMode, @@ -191,7 +191,7 @@ describe('Participant API Tests', () => { secret: roomData.moderatorSecret, participantName, participantIdentity: participantName - }, + } as unknown as ParticipantOptions, roomData.moderatorToken ); expectValidationError(response, 'roomId', 'Required'); @@ -203,7 +203,7 @@ describe('Participant API Tests', () => { roomId: roomData.room.roomId, participantName, participantIdentity: participantName - }, + } as unknown as ParticipantOptions, roomData.moderatorToken ); expectValidationError(response, 'secret', 'Required'); diff --git a/meet-ce/backend/tests/integration/api/recordings/bulk-delete-recording.test.ts b/meet-ce/backend/tests/integration/api/recordings/bulk-delete-recording.test.ts index a5a22104..5d0f959e 100644 --- a/meet-ce/backend/tests/integration/api/recordings/bulk-delete-recording.test.ts +++ b/meet-ce/backend/tests/integration/api/recordings/bulk-delete-recording.test.ts @@ -29,7 +29,7 @@ describe('Recording API Tests', () => { describe('Bulk Delete Recording Tests', () => { it('should return 400 when mixed valid and non-existent IDs are provided', async () => { const testContext = await setupMultiRecordingsTestContext(3, 3, 3); - const recordingIds = testContext.rooms.map((room) => room.recordingId); + const recordingIds = testContext.rooms.map((room) => room.recordingId!); const nonExistentIds = ['nonExistent--EG_000--1234', 'nonExistent--EG_111--5678']; const mixedIds = [...recordingIds, ...nonExistentIds]; @@ -52,10 +52,10 @@ describe('Recording API Tests', () => { const testContext = await setupMultiRecordingsTestContext(3, 3, 2); const activeRecordingRoom = testContext.getLastRoom(); const recordingIds = testContext.rooms - .map((room) => room.recordingId) - .filter((id) => id !== activeRecordingRoom!.recordingId); + .map((room) => room.recordingId!) + .filter((id) => id !== activeRecordingRoom!.recordingId!); - const activeRecordingId = activeRecordingRoom?.recordingId; + const activeRecordingId = activeRecordingRoom!.recordingId!; let deleteResponse = await bulkDeleteRecordings([...recordingIds, activeRecordingId]); expect(deleteResponse.status).toBe(400); @@ -83,7 +83,7 @@ describe('Recording API Tests', () => { it('should not delete any recordings and return 400', async () => { const testContext = await setupMultiRecordingsTestContext(2, 2, 0); - const recordingIds = testContext.rooms.map((room) => room.recordingId); + const recordingIds = testContext.rooms.map((room) => room.recordingId!); const deleteResponse = await bulkDeleteRecordings(recordingIds); expect(deleteResponse.status).toBe(400); expect(deleteResponse.body).toEqual({ @@ -106,7 +106,7 @@ describe('Recording API Tests', () => { it('should delete all recordings and return 200 when all operations succeed', async () => { const response = await setupMultiRecordingsTestContext(5, 5, 5); - const recordingIds = response.rooms.map((room) => room.recordingId); + const recordingIds = response.rooms.map((room) => room.recordingId!); const deleteResponse = await bulkDeleteRecordings(recordingIds); expect(deleteResponse.status).toBe(200); @@ -116,14 +116,14 @@ describe('Recording API Tests', () => { // Create a room and start a recording const roomData = await setupSingleRoomWithRecording(true); const roomId = roomData.room.roomId; - const recordingId = roomData.recordingId; + const recordingId = roomData.recordingId!; // Generate a recording token for the room const recordingToken = await generateRecordingToken(roomId, roomData.moderatorSecret); // Create another room and start a recording const otherRoomData = await setupSingleRoomWithRecording(true); - const otherRecordingId = otherRoomData.recordingId; + const otherRecordingId = otherRoomData.recordingId!; // Intenta eliminar ambas grabaciones usando el token de la primera sala const deleteResponse = await bulkDeleteRecordings([recordingId, otherRecordingId], recordingToken); @@ -145,7 +145,7 @@ describe('Recording API Tests', () => { it('should handle single recording deletion correctly', async () => { const testContext = await setupMultiRecordingsTestContext(1, 1, 1); - const recordingId = testContext.rooms[0].recordingId; + const recordingId = testContext.rooms[0].recordingId!; const deleteResponse = await bulkDeleteRecordings([recordingId]); expect(deleteResponse.status).toBe(200); @@ -153,7 +153,7 @@ describe('Recording API Tests', () => { it('should handle duplicate recording IDs by treating them as a single delete', async () => { const testContext = await setupMultiRecordingsTestContext(1, 1, 1); - const recordingId = testContext.getRoomByIndex(0)!.recordingId; + const recordingId = testContext.getRoomByIndex(0)!.recordingId!; const deleteResponse = await bulkDeleteRecordings([recordingId, recordingId]); expect(deleteResponse.status).toBe(200); 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 e5945fd2..7889eb94 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 @@ -1,9 +1,9 @@ import { afterAll, beforeAll, describe, expect, it } from '@jest/globals'; +import { MeetRecordingAccess, MeetRoom } from '@openvidu-meet/typings'; import { Express } from 'express'; import request from 'supertest'; import { INTERNAL_CONFIG } from '../../../../src/config/internal-config.js'; import { MEET_INITIAL_API_KEY } from '../../../../src/environment.js'; -import { MeetRecordingAccess, MeetRoom } from '@openvidu-meet/typings'; import { expectValidRoom } from '../../../helpers/assertion-helpers.js'; import { createRoom, @@ -22,7 +22,7 @@ describe('E2EE Room Configuration Tests', () => { let app: Express; beforeAll(async () => { - app = startTestServer(); + app = await startTestServer(); }); afterAll(async () => { 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 95844557..c016f9ba 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 @@ -1,7 +1,7 @@ import { afterEach, beforeAll, describe, expect, it, jest } from '@jest/globals'; +import { MeetRecordingAccess, MeetRoomConfig, MeetSignalType } from '@openvidu-meet/typings'; import { container } from '../../../../src/config/index.js'; import { FrontendEventService } from '../../../../src/services/index.js'; -import { MeetSignalType, MeetRecordingAccess } from '@openvidu-meet/typings'; import { createRoom, deleteAllRooms, @@ -114,6 +114,23 @@ describe('Room API Tests', () => { expect(getResponse.status).toBe(200); expect(getResponse.body.config).toEqual(partialConfig); }); + + it('should return 404 when updating non-existent room', async () => { + const nonExistentRoomId = 'non-existent-room'; + + const config = { + recording: { + enabled: false, + allowAccessTo: MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER + }, + chat: { enabled: false }, + virtualBackground: { enabled: false } + }; + const response = await updateRoomConfig(nonExistentRoomId, config); + + expect(response.status).toBe(404); + expect(response.body.message).toContain(`'${nonExistentRoomId}' does not exist`); + }); }); describe('Update Room Config Validation failures', () => { @@ -130,7 +147,7 @@ describe('Room API Tests', () => { // Missing chat config virtualBackground: { enabled: false } }; - const response = await updateRoomConfig(roomId, invalidConfig); + const response = await updateRoomConfig(roomId, invalidConfig as unknown as MeetRoomConfig); expect(response.status).toBe(422); expect(response.body.error).toContain('Unprocessable Entity'); @@ -151,7 +168,7 @@ describe('Room API Tests', () => { chat: { enabled: false }, virtualBackground: { enabled: false } }; - const response = await updateRoomConfig(createdRoom.roomId, invalidConfig); + const response = await updateRoomConfig(createdRoom.roomId, invalidConfig as unknown as MeetRoomConfig); expect(response.status).toBe(422); expect(response.body.error).toContain('Unprocessable Entity'); @@ -164,7 +181,7 @@ describe('Room API Tests', () => { }); const emptyConfig = {}; - const response = await updateRoomConfig(createdRoom.roomId, emptyConfig); + const response = await updateRoomConfig(createdRoom.roomId, emptyConfig as unknown as MeetRoomConfig); expect(response.status).toBe(422); expect(response.body.error).toContain('Unprocessable Entity'); @@ -188,22 +205,5 @@ describe('Room API Tests', () => { expect(response.body.error).toContain('Unprocessable Entity'); expect(JSON.stringify(response.body.details)).toContain('recording.allowAccessTo'); }); - - it('should return 404 when updating non-existent room', async () => { - const nonExistentRoomId = 'non-existent-room'; - - const config = { - recording: { - enabled: false, - allowAccessTo: MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER - }, - chat: { enabled: false }, - virtualBackground: { enabled: false } - }; - const response = await updateRoomConfig(nonExistentRoomId, config); - - expect(response.status).toBe(404); - expect(response.body.message).toContain(`'${nonExistentRoomId}' does not exist`); - }); }); });