diff --git a/backend/tests/helpers/request-helpers.ts b/backend/tests/helpers/request-helpers.ts index 61d46e6..89415f7 100644 --- a/backend/tests/helpers/request-helpers.ts +++ b/backend/tests/helpers/request-helpers.ts @@ -77,23 +77,23 @@ export const getApiKeys = async () => { return response; }; -export const getAppearanceConfig = async () => { +export const getRoomsAppearanceConfig = async () => { checkAppIsRunning(); const adminCookie = await loginUser(); const response = await request(app) - .get(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config/appearance`) + .get(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config/rooms/appearance`) .set('Cookie', adminCookie) .send(); return response; }; -export const updateAppearanceConfig = async (config: any) => { +export const updateRoomsAppearanceConfig = async (config: any) => { checkAppIsRunning(); const adminCookie = await loginUser(); const response = await request(app) - .put(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config/appearance`) + .put(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config/rooms/appearance`) .set('Cookie', adminCookie) .send(config); return response; diff --git a/backend/tests/integration/api/global-config/appearance.test.ts b/backend/tests/integration/api/global-config/appearance.test.ts index f9dad97..efc7e74 100644 --- a/backend/tests/integration/api/global-config/appearance.test.ts +++ b/backend/tests/integration/api/global-config/appearance.test.ts @@ -1,22 +1,333 @@ -import { beforeAll, describe, expect, it } from '@jest/globals'; -import { getAppearanceConfig, startTestServer, updateAppearanceConfig } from '../../../helpers/request-helpers.js'; +import { afterEach, beforeAll, describe, expect, it } from '@jest/globals'; +import { container } from '../../../../src/config/dependency-injector.config.js'; +import { MeetStorageService } from '../../../../src/services/index.js'; +import { MeetRoomThemeMode } from '../../../../src/typings/ce/index.js'; +import { expectValidationError } from '../../../helpers/assertion-helpers.js'; +import { + getRoomsAppearanceConfig, + startTestServer, + updateRoomsAppearanceConfig +} from '../../../helpers/request-helpers.js'; -describe('Appearance API Tests', () => { - beforeAll(() => { +describe('Rooms Appearance Config API Tests', () => { + beforeAll(async () => { startTestServer(); }); - describe('Get Appearance Config', () => { - it('should return 402 status code as it is a PRO feature', async () => { - const response = await getAppearanceConfig(); - expect(response.status).toBe(402); + afterEach(async () => { + const storageService = container.get(MeetStorageService); + await storageService['initializeGlobalConfig'](); + }); + + describe('Update rooms appearance config', () => { + it('should update rooms appearance config with valid complete data', async () => { + const validConfig = { + appearance: { + themes: [ + { + name: 'Custom Theme', + baseTheme: MeetRoomThemeMode.DARK, + backgroundColor: '#121212', + primaryColor: '#bb86fc', + secondaryColor: '#03dac6', + surfaceColor: '#1f1f1f' + } + ] + } + }; + + let response = await updateRoomsAppearanceConfig(validConfig); + expect(response.status).toBe(200); + expect(response.body.message).toBe('Rooms appearance config updated successfully'); + + response = await getRoomsAppearanceConfig(); + expect(response.status).toBe(200); + expect(response.body).toEqual(validConfig); + }); + + it('should update rooms appearance config with minimal required data', async () => { + const validConfig = { + appearance: { + themes: [ + { + name: 'Minimal Theme', + baseTheme: MeetRoomThemeMode.LIGHT + } + ] + } + }; + + let response = await updateRoomsAppearanceConfig(validConfig); + expect(response.status).toBe(200); + expect(response.body.message).toBe('Rooms appearance config updated successfully'); + + response = await getRoomsAppearanceConfig(); + expect(response.status).toBe(200); + expect(response.body).toEqual(validConfig); + }); + + it('should replace existing config when updating', async () => { + const initialConfig = { + appearance: { + themes: [ + { + name: 'Initial Theme', + baseTheme: MeetRoomThemeMode.LIGHT, + primaryColor: '#1976d2' + } + ] + } + }; + + let response = await updateRoomsAppearanceConfig(initialConfig); + expect(response.status).toBe(200); + + response = await getRoomsAppearanceConfig(); + expect(response.status).toBe(200); + expect(response.body).toEqual(initialConfig); + + const newConfig = { + appearance: { + themes: [ + { + name: 'New Theme', + baseTheme: MeetRoomThemeMode.DARK, + primaryColor: '#bb86fc' + } + ] + } + }; + + response = await updateRoomsAppearanceConfig(newConfig); + expect(response.status).toBe(200); + + response = await getRoomsAppearanceConfig(); + expect(response.status).toBe(200); + expect(response.body).toEqual(newConfig); }); }); - describe('Update Appearance Config', () => { - it('should return 402 status code as it is a PRO feature', async () => { - const response = await updateAppearanceConfig({}); - expect(response.status).toBe(402); + describe('Update rooms appearance config validation', () => { + it('should reject when themes array is empty', async () => { + const response = await updateRoomsAppearanceConfig({ + appearance: { + themes: [] + } + }); + + expectValidationError(response, 'appearance.themes', 'There must be exactly one theme defined'); + }); + + it('should reject when themes array has more than one theme', async () => { + const response = await updateRoomsAppearanceConfig({ + appearance: { + themes: [ + { + name: 'Theme 1', + baseTheme: MeetRoomThemeMode.LIGHT + }, + { + name: 'Theme 2', + baseTheme: MeetRoomThemeMode.DARK + } + ] + } + }); + + expectValidationError(response, 'appearance.themes', 'There must be exactly one theme defined'); + }); + + it('should reject when theme name is empty', async () => { + const response = await updateRoomsAppearanceConfig({ + appearance: { + themes: [ + { + name: '', + baseTheme: MeetRoomThemeMode.LIGHT + } + ] + } + }); + + expectValidationError(response, 'appearance.themes.0.name', 'Theme name cannot be empty'); + }); + + it('should reject when theme name exceeds 50 characters', async () => { + const response = await updateRoomsAppearanceConfig({ + appearance: { + themes: [ + { + name: 'This is a very long theme name that exceeds fifty characters', + baseTheme: MeetRoomThemeMode.LIGHT + } + ] + } + }); + + expectValidationError(response, 'appearance.themes.0.name', 'Theme name cannot exceed 50 characters'); + }); + + it('should reject when baseTheme is not a valid enum value', async () => { + const response = await updateRoomsAppearanceConfig({ + appearance: { + themes: [ + { + name: 'Invalid Theme', + baseTheme: 'invalid' + } + ] + } + }); + + expectValidationError( + response, + 'appearance.themes.0.baseTheme', + "Invalid enum value. Expected 'light' | 'dark', received 'invalid'" + ); + }); + + it('should reject when hex colors are invalid', async () => { + let response = await updateRoomsAppearanceConfig({ + appearance: { + themes: [ + { + name: 'Invalid Color Theme', + baseTheme: MeetRoomThemeMode.LIGHT, + backgroundColor: 'not-a-color' + } + ] + } + }); + expectValidationError(response, 'appearance.themes.0.backgroundColor', 'Must be a valid hex color code'); + + response = await updateRoomsAppearanceConfig({ + appearance: { + themes: [ + { + name: 'Invalid Color Theme', + baseTheme: MeetRoomThemeMode.LIGHT, + primaryColor: '#gggggg' + } + ] + } + }); + expectValidationError(response, 'appearance.themes.0.primaryColor', 'Must be a valid hex color code'); + + response = await updateRoomsAppearanceConfig({ + appearance: { + themes: [ + { + name: 'Invalid Color Theme', + baseTheme: MeetRoomThemeMode.LIGHT, + secondaryColor: '#12345' + } + ] + } + }); + expectValidationError(response, 'appearance.themes.0.secondaryColor', 'Must be a valid hex color code'); + + response = await updateRoomsAppearanceConfig({ + appearance: { + themes: [ + { + name: 'Invalid Color Theme', + baseTheme: MeetRoomThemeMode.LIGHT, + surfaceColor: 'rgb(255,255,255)' + } + ] + } + }); + expectValidationError(response, 'appearance.themes.0.surfaceColor', 'Must be a valid hex color code'); + }); + + it('should accept valid 3-digit hex colors', async () => { + const response = await updateRoomsAppearanceConfig({ + appearance: { + themes: [ + { + name: 'Short Hex Theme', + baseTheme: MeetRoomThemeMode.LIGHT, + backgroundColor: '#fff', + primaryColor: '#000', + secondaryColor: '#f0f', + surfaceColor: '#abc' + } + ] + } + }); + + expect(response.status).toBe(200); + }); + + it('should reject when name or baseTheme are not provided', async () => { + let response = await updateRoomsAppearanceConfig({ + appearance: { + themes: [ + { + baseTheme: MeetRoomThemeMode.LIGHT + } + ] + } + }); + expectValidationError(response, 'appearance.themes.0.name', 'Required'); + + response = await updateRoomsAppearanceConfig({ + appearance: { + themes: [ + { + name: 'Missing Base Theme' + } + ] + } + }); + expectValidationError(response, 'appearance.themes.0.baseTheme', 'Required'); + }); + + it('should reject when appearance is not an object', async () => { + const response = await updateRoomsAppearanceConfig({ + appearance: 'invalid' + }); + + expectValidationError(response, 'appearance', 'Expected object, received string'); + }); + + it('should reject when themes is not an array', async () => { + const response = await updateRoomsAppearanceConfig({ + appearance: { + themes: 'invalid' + } + }); + + expectValidationError(response, 'appearance.themes', 'Expected array, received string'); + }); + }); + + describe('Get rooms appearance config', () => { + it('should return 404 when no appearance config is set', async () => { + const response = await getRoomsAppearanceConfig(); + + expect(response.status).toBe(404); + expect(response.body.message).toBe('Rooms appearance config not defined'); + }); + + it('should return rooms appearance config when one is set', async () => { + const configToSet = { + appearance: { + themes: [ + { + name: 'Test Theme', + baseTheme: MeetRoomThemeMode.DARK, + primaryColor: '#bb86fc' + } + ] + } + }; + let response = await updateRoomsAppearanceConfig(configToSet); + expect(response.status).toBe(200); + + response = await getRoomsAppearanceConfig(); + expect(response.status).toBe(200); + expect(response.body).toEqual(configToSet); }); }); }); diff --git a/backend/tests/integration/api/security/preferences-security.test.ts b/backend/tests/integration/api/security/global-config-security.test.ts similarity index 68% rename from backend/tests/integration/api/security/preferences-security.test.ts rename to backend/tests/integration/api/security/global-config-security.test.ts index 26b9beb..cd80f71 100644 --- a/backend/tests/integration/api/security/preferences-security.test.ts +++ b/backend/tests/integration/api/security/global-config-security.test.ts @@ -1,13 +1,20 @@ -import { beforeAll, describe, expect, it } from '@jest/globals'; +import { afterAll, beforeAll, describe, expect, it } from '@jest/globals'; import { Express } from 'express'; import request from 'supertest'; +import { container } from '../../../../src/config/dependency-injector.config.js'; import INTERNAL_CONFIG from '../../../../src/config/internal-config.js'; import { MEET_INITIAL_API_KEY } from '../../../../src/environment.js'; -import { AuthMode, AuthType } from '../../../../src/typings/ce/index.js'; -import { loginUser, startTestServer } from '../../../helpers/request-helpers.js'; +import { MeetStorageService } from '../../../../src/services/index.js'; +import { AuthMode, AuthType, MeetRoomThemeMode } from '../../../../src/typings/ce/index.js'; +import { loginUser, startTestServer, updateRoomsAppearanceConfig } from '../../../helpers/request-helpers.js'; const CONFIG_PATH = `${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config`; +const restoreGlobalConfig = async () => { + const storageService = container.get(MeetStorageService); + await storageService['initializeGlobalConfig'](); +}; + describe('Global Config API Security Tests', () => { let app: Express; let adminCookie: string; @@ -37,6 +44,8 @@ describe('Global Config API Security Tests', () => { .set('Cookie', adminCookie) .send(webhookConfig); expect(response.status).toBe(200); + + await restoreGlobalConfig(); }); it('should fail when user is not authenticated', async () => { @@ -88,6 +97,8 @@ describe('Global Config API Security Tests', () => { .set('Cookie', adminCookie) .send(securityConfig); expect(response.status).toBe(200); + + await restoreGlobalConfig(); }); it('should fail when user is not authenticated', async () => { @@ -103,41 +114,76 @@ describe('Global Config API Security Tests', () => { }); }); - describe('Update Appearance Config Tests', () => { + describe('Update Rooms Appearance Config Tests', () => { + const appearanceConfig = { + appearance: { + themes: [ + { + name: 'Default Theme', + baseTheme: MeetRoomThemeMode.DARK + } + ] + } + }; + it('should fail when request includes API key', async () => { const response = await request(app) - .put(`${CONFIG_PATH}/appearance`) + .put(`${CONFIG_PATH}/rooms/appearance`) .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY) - .send({}); + .send(appearanceConfig); expect(response.status).toBe(401); }); it('should succeed when user is authenticated as admin', async () => { - const response = await request(app).put(`${CONFIG_PATH}/appearance`).set('Cookie', adminCookie).send({}); - expect(response.status).toBe(402); // Assuming 402 is the expected status code for this case + const response = await request(app) + .put(`${CONFIG_PATH}/rooms/appearance`) + .set('Cookie', adminCookie) + .send(appearanceConfig); + expect(response.status).toBe(200); + + await restoreGlobalConfig(); }); it('should fail when user is not authenticated', async () => { - const response = await request(app).put(`${CONFIG_PATH}/appearance`).send({}); + const response = await request(app).put(`${CONFIG_PATH}/rooms/appearance`).send(appearanceConfig); expect(response.status).toBe(401); }); }); - describe('Get Appearance Config Tests', () => { + describe('Get Rooms Appearance Config Tests', () => { + const appearanceConfig = { + appearance: { + themes: [ + { + name: 'Default Theme', + baseTheme: MeetRoomThemeMode.DARK + } + ] + } + }; + + beforeAll(async () => { + await updateRoomsAppearanceConfig(appearanceConfig); + }); + + afterAll(async () => { + await restoreGlobalConfig(); + }); + it('should fail when request includes API key', async () => { const response = await request(app) - .get(`${CONFIG_PATH}/appearance`) + .get(`${CONFIG_PATH}/rooms/appearance`) .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_INITIAL_API_KEY); expect(response.status).toBe(401); }); it('should succeed when user is authenticated as admin', async () => { - const response = await request(app).get(`${CONFIG_PATH}/appearance`).set('Cookie', adminCookie); - expect(response.status).toBe(402); // Assuming 402 is the expected status code for this case + const response = await request(app).get(`${CONFIG_PATH}/rooms/appearance`).set('Cookie', adminCookie); + expect(response.status).toBe(200); }); it('should fail when user is not authenticated', async () => { - const response = await request(app).get(`${CONFIG_PATH}/appearance`); + const response = await request(app).get(`${CONFIG_PATH}/rooms/appearance`); expect(response.status).toBe(401); }); });