From f0092b9d041f6dae1c766923ee32adc43e2408fa Mon Sep 17 00:00:00 2001 From: Carlos Santos <4a.santos@gmail.com> Date: Fri, 11 Apr 2025 11:25:38 +0200 Subject: [PATCH] backend: Add integration tests for getRooms API with validation and pagination --- .../integration/api/rooms/get-rooms.test.ts | 166 ++++++++++++++++++ backend/tests/utils/helpers.ts | 58 +++++- 2 files changed, 221 insertions(+), 3 deletions(-) create mode 100644 backend/tests/integration/api/rooms/get-rooms.test.ts diff --git a/backend/tests/integration/api/rooms/get-rooms.test.ts b/backend/tests/integration/api/rooms/get-rooms.test.ts new file mode 100644 index 0000000..5daadf9 --- /dev/null +++ b/backend/tests/integration/api/rooms/get-rooms.test.ts @@ -0,0 +1,166 @@ +import request from 'supertest'; +import { describe, it, expect, beforeAll, afterAll, afterEach } from '@jest/globals'; +import { Express } from 'express'; +import { + createRoom, + deleteAllRooms, + assertEmptyRooms, + getRooms, + startTestServer, + stopTestServer, + assertRoomsResponse +} from '../../../utils/helpers.js'; +import { MEET_API_BASE_PATH_V1, MEET_API_KEY } from '../../../../src/environment.js'; + +const endpoint = '/rooms'; +describe('OpenVidu Meet Room API Tests', () => { + let app: Express; + const validAutoDeletionDate = Date.now() + 2 * 60 * 60 * 1000; // 2 hours ahead + + beforeAll(async () => { + app = await startTestServer(); + }); + + afterAll(async () => { + await stopTestServer(); + }); + + afterEach(async () => { + // Remove all rooms created + await deleteAllRooms(); + }); + + describe('List Rooms Tests', () => { + it('should return an empty list of rooms', async () => { + await assertEmptyRooms(app); + }); + + it('should return a list of rooms', async () => { + await assertEmptyRooms(app); + + await createRoom(app, { + roomIdPrefix: 'test-room' + }); + + const body = await getRooms(app); + const { rooms } = body; + + assertRoomsResponse(body, 1, 10, false, false); + expect(rooms[0].roomId).toBeDefined(); + expect(rooms[0].roomId).toContain('test-room'); + expect(rooms[0].creationDate).toBeDefined(); + expect(rooms[0].roomIdPrefix).toBeDefined(); + expect(rooms[0].autoDeletionDate).not.toBeDefined(); + expect(rooms[0].preferences).toBeDefined(); + expect(rooms[0].moderatorRoomUrl).toBeDefined(); + expect(rooms[0].publisherRoomUrl).toBeDefined(); + }); + + it('should return a list of rooms applying fields filter', async () => { + await assertEmptyRooms(app); + + await createRoom(app, { + roomIdPrefix: 'test-room', + autoDeletionDate: validAutoDeletionDate + }); + + const body = await getRooms(app, { fields: 'roomId,createdAt' }); + const { rooms } = body; + + assertRoomsResponse(body, 1, 10, false, false); + + expect(rooms[0].roomId).toBeDefined(); + expect(rooms[0].roomId).toContain('test-room'); + + expect(rooms[0].creationDate).not.toBeDefined(); + expect(rooms[0].roomIdPrefix).not.toBeDefined(); + //CreatedAt does not exist in the room + expect(rooms[0].createdAt).not.toBeDefined(); + expect(rooms[0].autoDeletionDate).not.toBeDefined(); + expect(rooms[0].preferences).not.toBeDefined(); + expect(rooms[0].moderatorRoomUrl).not.toBeDefined(); + expect(rooms[0].publisherRoomUrl).not.toBeDefined(); + }); + + it('should return a list of rooms with pagination', async () => { + await assertEmptyRooms(app); + const promises = [1, 2, 3, 4, 5, 6].map((i) => { + return createRoom(app, { + roomIdPrefix: `test-room-${i}`, + autoDeletionDate: validAutoDeletionDate + }); + }); + await Promise.all(promises); + + let body = await getRooms(app, { maxItems: 3 }); + const { pagination } = body; + + assertRoomsResponse(body, 3, 3, true, true); + + const nextPageToken = pagination.nextPageToken; + body = await getRooms(app, { maxItems: 3, nextPageToken }); + + assertRoomsResponse(body, 3, 3, false, false); + }); + + it('should capped maxItems to the maximum allowed', async () => { + const body = await getRooms(app, { maxItems: 101 }); + + assertRoomsResponse(body, 0, 100, false, false); + }); + + it('should coerce a floating number to an integer for maxItems', async () => { + const body = await getRooms(app, { maxItems: 12.78 }); + + assertRoomsResponse(body, 0, 12, false, false); + }); + }); + + describe('List Room Validation failures', () => { + it('should fail when maxItems is not a number', async () => { + const response = await request(app) + .get(`${MEET_API_BASE_PATH_V1}${endpoint}`) + .set('X-API-KEY', MEET_API_KEY) + .query({ maxItems: 'not-a-number' }) + .expect(422); + + expect(response.body.error).toContain('Unprocessable Entity'); + // Check that the error details mention an invalid number. + expect(JSON.stringify(response.body.details)).toContain('Expected number, received nan'); + }); + + it('should fail when maxItems is negative', async () => { + const response = await request(app) + .get(`${MEET_API_BASE_PATH_V1}${endpoint}`) + .set('X-API-KEY', MEET_API_KEY) + .query({ maxItems: -1 }) + .expect(422); + + console.log(response.body); + expect(response.body.error).toContain('Unprocessable Entity'); + expect(JSON.stringify(response.body.details)).toContain('positive number'); + }); + + it('should fail when maxItems is zero', async () => { + const response = await request(app) + .get(`${MEET_API_BASE_PATH_V1}${endpoint}`) + .set('X-API-KEY', MEET_API_KEY) + .query({ maxItems: 0 }) + .expect(422); + + expect(response.body.error).toContain('Unprocessable Entity'); + expect(JSON.stringify(response.body.details)).toContain('positive number'); + }); + + it('should fail when fields is not a string', async () => { + const response = await request(app) + .get(`${MEET_API_BASE_PATH_V1}${endpoint}`) + .set('X-API-KEY', MEET_API_KEY) + .query({ fields: { invalid: 'data' } }) + .expect(422); + + expect(response.body.error).toContain('Unprocessable Entity'); + expect(JSON.stringify(response.body.details)).toContain('Expected string'); + }); + }); +}); diff --git a/backend/tests/utils/helpers.ts b/backend/tests/utils/helpers.ts index 5e6aa73..5e4a6f1 100644 --- a/backend/tests/utils/helpers.ts +++ b/backend/tests/utils/helpers.ts @@ -13,7 +13,8 @@ import { MEET_ADMIN_USER, MEET_ADMIN_SECRET } from '../../src/environment.js'; -import { AuthMode, AuthType, MeetRoom, UserRole } from '../../src/typings/ce/index.js'; +import { AuthMode, AuthType, MeetRoom, UserRole, MeetRoomOptions } from '../../src/typings/ce/index.js'; +import { expect } from '@jest/globals'; export const API_KEY_HEADER = 'X-API-Key'; @@ -131,7 +132,7 @@ export const loginUserAsRole = async (role: UserRole): Promise => { /** * Creates a room with the given prefix */ -export const createRoom = async (roomIdPrefix = 'test'): Promise => { +export const createRoom = async (options: MeetRoomOptions): Promise => { if (!app) { throw new Error('App instance is not defined'); } @@ -139,11 +140,62 @@ export const createRoom = async (roomIdPrefix = 'test'): Promise => { const response = await request(app) .post(`${MEET_API_BASE_PATH_V1}/rooms`) .set(API_KEY_HEADER, MEET_API_KEY) - .send({ roomIdPrefix }) + .send(options) .expect(200); return response.body; }; +/** + * Performs a GET /rooms request with provided query parameters. + * Returns the parsed response. + */ +export const getRooms = async (app: Express, query: Record = {}) => { + const response = await request(app) + .get(`${MEET_API_BASE_PATH_V1}/rooms`) + .set(API_KEY_HEADER, MEET_API_KEY) + .query(query) + .expect(200); + return response.body; +}; + +/** + * Asserts that a rooms response matches the expected values for testing purposes. + * Validates the room array length and pagination properties. + * + * @param body - The API response body to validate + * @param expectedRoomLength - The expected number of rooms in the response + * @param expectedMaxItems - The expected maximum number of items in pagination + * @param expectedTruncated - The expected value for pagination.isTruncated flag + * @param expectedNextPageToken - The expected presence of pagination.nextPageToken + * (if true, expects nextPageToken to be defined; + * if false, expects nextPageToken to be undefined) + */ +export const assertRoomsResponse = ( + body: any, + expectedRoomLength: number, + expectedMaxItems: number, + expectedTruncated: boolean, + expectedNextPageToken: boolean +) => { + expect(body).toBeDefined(); + expect(body.rooms).toBeDefined(); + expect(Array.isArray(body.rooms)).toBe(true); + expect(body.rooms.length).toBe(expectedRoomLength); + expect(body.pagination).toBeDefined(); + expect(body.pagination.isTruncated).toBe(expectedTruncated); + + expectedNextPageToken + ? expect(body.pagination.nextPageToken).toBeDefined() + : expect(body.pagination.nextPageToken).toBeUndefined(); + expect(body.pagination.maxItems).toBe(expectedMaxItems); +}; + +export const assertEmptyRooms = async (app: Express) => { + const body = await getRooms(app); + + assertRoomsResponse(body, 0, 10, false, false); +}; + /** * Deletes all rooms */