From 2c1657a58d6ed24d2c0adae82f1272de21bfcd1c Mon Sep 17 00:00:00 2001 From: Carlos Santos <4a.santos@gmail.com> Date: Thu, 24 Apr 2025 14:05:06 +0200 Subject: [PATCH] test: Add integration tests for GET Recording API and validation --- .../api/recordings/get-recording.test.ts | 88 +++++++++++++++++++ backend/tests/utils/assertion-helpers.ts | 37 +++++++- backend/tests/utils/helpers.ts | 10 +++ 3 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 backend/tests/integration/api/recordings/get-recording.test.ts diff --git a/backend/tests/integration/api/recordings/get-recording.test.ts b/backend/tests/integration/api/recordings/get-recording.test.ts new file mode 100644 index 0000000..7d79509 --- /dev/null +++ b/backend/tests/integration/api/recordings/get-recording.test.ts @@ -0,0 +1,88 @@ +import { describe, it, expect, beforeAll, afterAll } from '@jest/globals'; +import { + deleteAllRecordings, + deleteAllRooms, + disconnectFakeParticipants, + getRecording, + startTestServer, + stopAllRecordings +} from '../../../utils/helpers.js'; + +import { errorRecordingNotFound } from '../../../../src/models/error.model.js'; +import { expectValidationError, expectValidGetRecordingResponse } from '../../../utils/assertion-helpers.js'; +import { setupMultiRecordingsTestContext, TestContext } from '../../../utils/test-scenarios.js'; +import { MeetRoom } from '../../../../src/typings/ce/room.js'; +import { MeetRecordingStatus } from '../../../../src/typings/ce/recording.model.js'; + +describe('Recording API Tests', () => { + let context: TestContext | null = null; + let room: MeetRoom, moderatorCookie: string, recordingId: string; + + beforeAll(async () => { + startTestServer(); + // Create a room and join a participant + context = await setupMultiRecordingsTestContext(1, 1, 1, '0s'); + ({ room, moderatorCookie, recordingId = '' } = context.getRoomByIndex(0)!); + }); + + afterAll(async () => { + await stopAllRecordings(moderatorCookie); + await disconnectFakeParticipants(); + await deleteAllRooms(); + await deleteAllRecordings(); + context = null; + }); + + describe('Get Recording Tests', () => { + it('should return 200 when recording exists', async () => { + const response = await getRecording(recordingId); + + console.log(response.body); + expectValidGetRecordingResponse(response, recordingId, room.roomId, MeetRecordingStatus.COMPLETE, 1); + }); + + it('should return 404 when recording does not exist', async () => { + const response = await getRecording('nonexistent--EG_222--4s444'); + expect(response.status).toBe(404); + expect(response.body.message).toBe(errorRecordingNotFound('nonexistent--EG_222--4s444').message); + }); + }); + + describe('Get Recording Validation', () => { + it('should fail when recordingId has incorrect format', async () => { + const response = await getRecording('incorrect-format'); + expectValidationError(response, 'recordingId', 'does not follow the expected format'); + }); + + it('should fail when recordingId has less than 3 parts', async () => { + const response = await getRecording('part1--part2'); + expectValidationError(response, 'recordingId', 'does not follow the expected format'); + }); + + it('should fail when recordingId first part is empty', async () => { + const response = await getRecording('--EG_12345--uid'); + expectValidationError(response, 'recordingId', 'does not follow the expected format'); + }); + + it('should fail when recordingId second part does not start with EG_', async () => { + const response = await getRecording(`${room.roomId}--INVALID--uid`); + expectValidationError(response, 'recordingId', 'does not follow the expected format'); + }); + + it('should fail when recordingId second part is too short', async () => { + const response = await getRecording(`${room.roomId}--EG_--uid`); + expectValidationError(response, 'recordingId', 'does not follow the expected format'); + }); + + it('should fail when recordingId third part is empty', async () => { + const response = await getRecording(`${room.roomId}--EG_12345--`); + expectValidationError(response, 'recordingId', 'does not follow the expected format'); + }); + + it('should sanitize recordingId before validation', async () => { + const response = await getRecording(` ${recordingId} `); + expect(response.status).toBe(200); + expect(response.body).toHaveProperty('recordingId', recordingId); + }); + }); +}); diff --git a/backend/tests/utils/assertion-helpers.ts b/backend/tests/utils/assertion-helpers.ts index 1e5db38..b2da51b 100644 --- a/backend/tests/utils/assertion-helpers.ts +++ b/backend/tests/utils/assertion-helpers.ts @@ -1,6 +1,6 @@ import { expect } from '@jest/globals'; import INTERNAL_CONFIG from '../../src/config/internal-config'; -import { MeetRoom, MeetRoomPreferences } from '../../src/typings/ce'; +import { MeetRecordingStatus, MeetRoom, MeetRoomPreferences } from '../../src/typings/ce'; const RECORDINGS_PATH = `${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/recordings`; const expectErrorResponse = ( @@ -180,3 +180,38 @@ export const expectValidStopRecordingResponse = (response: any, recordingId: str expect(response.body).toHaveProperty('startDate'); expect(response.body).toHaveProperty('duration', expect.any(Number)); }; + +export const expectValidGetRecordingResponse = ( + response: any, + recordingId: string, + roomId: string, + status: MeetRecordingStatus, + maxSecDuration: number +) => { + expect(response.status).toBe(200); + expect(response.body).toBeDefined(); + const body = response.body; + + expect(body).toMatchObject({ recordingId, roomId, status }); + + expect(body).toEqual( + expect.objectContaining({ + duration: expect.any(Number), + startDate: expect.any(Number), + endDate: expect.any(Number), + size: expect.any(Number), + filename: expect.any(String), + details: expect.any(String) + }) + ); + + expect(body.duration).toBeGreaterThanOrEqual(0); + expect(body.duration).toBeLessThanOrEqual(maxSecDuration); + + expect(body.endDate).toBeGreaterThanOrEqual(body.startDate); + + const computedSec = (body.endDate - body.startDate) / 1000; + const diffSec = Math.abs(body.duration - computedSec); + // Estimate 5 seconds of tolerace because of time to start/stop recording + expect(diffSec).toBeLessThanOrEqual(5); +}; diff --git a/backend/tests/utils/helpers.ts b/backend/tests/utils/helpers.ts index e6c18a9..2119f15 100644 --- a/backend/tests/utils/helpers.ts +++ b/backend/tests/utils/helpers.ts @@ -356,6 +356,16 @@ export const stopRecording = async (recordingId: string, moderatorCookie = '') = return response; }; +export const getRecording = async (recordingId: string) => { + if (!app) { + throw new Error('App instance is not defined'); + } + + return await request(app) + .get(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/recordings/${recordingId}`) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_API_KEY); +} + export const stopAllRecordings = async (moderatorCookie: string) => { if (!app) { throw new Error('App instance is not defined');