From 498e837c7058592de7503e97717b9d021d3a2b75 Mon Sep 17 00:00:00 2001 From: Carlos Santos <4a.santos@gmail.com> Date: Fri, 17 Oct 2025 12:05:20 +0200 Subject: [PATCH] backend: Updated configuration for backend unit testing Sets up Jest with ts-jest for unit and integration tests. Includes configurations for: - ESM support - Module resolution - Path aliasing - Adds unit tests to validate path utilities --- meet-ce/backend/jest.config.mjs | 31 +-- meet-ce/backend/package.json | 2 +- meet-ce/backend/tests/unit/path.utils.spec.ts | 190 ++++++++++++++++++ 3 files changed, 207 insertions(+), 16 deletions(-) create mode 100644 meet-ce/backend/tests/unit/path.utils.spec.ts diff --git a/meet-ce/backend/jest.config.mjs b/meet-ce/backend/jest.config.mjs index 6fd2e7e..44f9d66 100644 --- a/meet-ce/backend/jest.config.mjs +++ b/meet-ce/backend/jest.config.mjs @@ -3,28 +3,29 @@ import { createDefaultEsmPreset } from 'ts-jest'; /** @type {import('ts-jest').JestConfigWithTsJest} */ const jestConfig = { displayName: 'backend', - ...createDefaultEsmPreset({ - tsconfig: 'tsconfig.json' - }), + ...createDefaultEsmPreset(), testTimeout: 60000, resolver: 'ts-jest-resolver', testMatch: ['**/?(*.)+(spec|test).[tj]s?(x)'], moduleFileExtensions: ['js', 'ts', 'json', 'node'], testEnvironment: 'node', - globals: { - 'ts-jest': { - tsconfig: 'tsconfig.json' - } - }, + extensionsToTreatAsEsm: ['.ts'], moduleNameMapper: { - '^@openvidu-meet/typings$': '/../typings/src/index.ts' + '^@openvidu-meet/typings$': '/../typings/src/index.ts', + '^(\\.{1,2}/.*)\\.js$': '$1' // Permite importar .js que resuelven a .ts }, - // transform: { - // '^.+\\.tsx?$': ['ts-jest', { - // // Opcionalmente, especifica el archivo tsconfig si es necesario - // tsconfig: 'tsconfig.json', - // }], - // }, + transform: { + '^.+\\.tsx?$': ['ts-jest', { + tsconfig: { + module: 'esnext', + moduleResolution: 'node16', + esModuleInterop: true, + allowSyntheticDefaultImports: true, + isolatedModules: true + }, + useESM: true + }] + } }; export default jestConfig; diff --git a/meet-ce/backend/package.json b/meet-ce/backend/package.json index 0f49ea2..e8d7f6a 100644 --- a/meet-ce/backend/package.json +++ b/meet-ce/backend/package.json @@ -43,7 +43,7 @@ "test:integration-participants": "node --experimental-vm-modules ../../node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/participants\" --ci --reporters=default --reporters=jest-junit", "test:integration-meetings": "node --experimental-vm-modules ../../node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/meetings\" --ci --reporters=default --reporters=jest-junit", "test:integration-users": "node --experimental-vm-modules ../../node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/users\" --ci --reporters=default --reporters=jest-junit", - "test:unit": "pnpm exec jest --runInBand --forceExit --testPathPattern \"tests/unit\" --ci --reporters=default --reporters=jest-junit", + "test:unit": "node --experimental-vm-modules ../../node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/unit\" --ci --reporters=default --reporters=jest-junit", "lint:fix": "eslint src --fix", "format:code": "prettier --ignore-path .gitignore --write '**/*.{ts,js,json,md}'" }, diff --git a/meet-ce/backend/tests/unit/path.utils.spec.ts b/meet-ce/backend/tests/unit/path.utils.spec.ts new file mode 100644 index 0000000..7785662 --- /dev/null +++ b/meet-ce/backend/tests/unit/path.utils.spec.ts @@ -0,0 +1,190 @@ +import { describe, it, expect, beforeAll } from '@jest/globals'; +import path from 'path'; +import fs from 'fs'; +import * as pathUtils from '../../src/utils/path.utils.js'; + +/** + * Tests for path.utils - Ensure robust path resolution + * across different execution scenarios. + * + * These tests verify the module resolves project paths correctly + * regardless of the directory from which the process is executed. + * + * NOTE: Because the module initializes on import, these tests assert + * the module behavior after it has been loaded and cannot mock initialization. + */ +describe('path.utils - Robust project path resolution', () => { + beforeAll(() => { + // Silence logs during tests + process.env.NODE_ENV = 'test'; + }); + + describe('Scenario 1: Exported paths verification', () => { + it('should export all required paths', () => { + // Verify all exported paths exist as properties + expect(pathUtils.publicDirectoryPath).toBeDefined(); + expect(pathUtils.frontendDirectoryPath).toBeDefined(); + expect(pathUtils.webcomponentBundlePath).toBeDefined(); + expect(pathUtils.frontendHtmlPath).toBeDefined(); + expect(pathUtils.publicApiHtmlFilePath).toBeDefined(); + expect(pathUtils.internalApiHtmlFilePath).toBeDefined(); + + // Verify the paths are non-empty strings + expect(typeof pathUtils.publicDirectoryPath).toBe('string'); + expect(pathUtils.publicDirectoryPath.length).toBeGreaterThan(0); + + expect(typeof pathUtils.frontendDirectoryPath).toBe('string'); + expect(pathUtils.frontendDirectoryPath.length).toBeGreaterThan(0); + + expect(typeof pathUtils.webcomponentBundlePath).toBe('string'); + expect(pathUtils.webcomponentBundlePath.length).toBeGreaterThan(0); + }); + + it('should build coherent paths (frontend inside public)', () => { + // frontendDirectoryPath should be a subdirectory of publicDirectoryPath + expect(pathUtils.frontendDirectoryPath.startsWith(pathUtils.publicDirectoryPath)).toBe(true); + + // Ensure it contains 'frontend' + expect(pathUtils.frontendDirectoryPath).toContain('frontend'); + }); + + it('should build webcomponentBundlePath inside public', () => { + // webcomponentBundlePath must be inside publicDirectoryPath + expect(pathUtils.webcomponentBundlePath.startsWith(pathUtils.publicDirectoryPath)).toBe(true); + + // Should end with the correct bundle name + expect(pathUtils.webcomponentBundlePath).toContain('openvidu-meet.bundle.min.js'); + }); + + it('should build coherent HTML paths', () => { + // frontendHtmlPath should be inside frontendDirectoryPath + expect(pathUtils.frontendHtmlPath.startsWith(pathUtils.frontendDirectoryPath)).toBe(true); + expect(pathUtils.frontendHtmlPath).toContain('index.html'); + + // OpenAPI paths should reference 'openapi' + expect(pathUtils.publicApiHtmlFilePath).toContain('openapi'); + expect(pathUtils.publicApiHtmlFilePath).toContain('public.html'); + + expect(pathUtils.internalApiHtmlFilePath).toContain('openapi'); + expect(pathUtils.internalApiHtmlFilePath).toContain('internal.html'); + }); + }); + + describe('Scenario 2: Directory structure validation', () => { + it('should contain "backend" and "public" in publicDirectoryPath', () => { + // Path should contain both segments + expect(pathUtils.publicDirectoryPath).toContain('backend'); + expect(pathUtils.publicDirectoryPath).toContain('public'); + + // Verify backend appears before public + const segments = pathUtils.publicDirectoryPath.split(path.sep); + const backendIndex = segments.findIndex((s: string) => s === 'backend'); + const publicIndex = segments.findIndex((s: string) => s === 'public'); + + expect(backendIndex).toBeGreaterThanOrEqual(0); + expect(publicIndex).toBeGreaterThan(backendIndex); + }); + + it('should work for both meet-ce and meet-pro', () => { + // Path may be identifiable as CE or PRO + const isCE = pathUtils.publicDirectoryPath.includes('meet-ce'); + const isPRO = pathUtils.publicDirectoryPath.includes('meet-pro'); + + // The structure backend/public must exist + expect(pathUtils.publicDirectoryPath).toMatch(/backend[\/\\]public$/); + }); + }); + + describe('Scenario 3: Path integrity validation', () => { + it('should use correct path separators for the OS', () => { + // Should not contain mixed separators + const hasForwardSlash = pathUtils.publicDirectoryPath.includes('/'); + const hasBackslash = pathUtils.publicDirectoryPath.includes('\\'); + + // Must have at least one type of separator + expect(hasForwardSlash || hasBackslash).toBe(true); + + // Verify normalized consistency + const normalized = path.normalize(pathUtils.publicDirectoryPath); + expect(pathUtils.publicDirectoryPath).toBe(normalized); + }); + + it('should produce absolute paths', () => { + // All exported paths must be absolute + expect(path.isAbsolute(pathUtils.publicDirectoryPath)).toBe(true); + expect(path.isAbsolute(pathUtils.frontendDirectoryPath)).toBe(true); + expect(path.isAbsolute(pathUtils.webcomponentBundlePath)).toBe(true); + expect(path.isAbsolute(pathUtils.frontendHtmlPath)).toBe(true); + expect(path.isAbsolute(pathUtils.publicApiHtmlFilePath)).toBe(true); + expect(path.isAbsolute(pathUtils.internalApiHtmlFilePath)).toBe(true); + }); + + it('should resolve normalized paths (no .. or . segments)', () => { + // Paths must not contain relative segments + expect(pathUtils.publicDirectoryPath).not.toContain('..'); + expect(pathUtils.publicDirectoryPath).not.toMatch(/\/\.\//); + + expect(pathUtils.frontendDirectoryPath).not.toContain('..'); + expect(pathUtils.webcomponentBundlePath).not.toContain('..'); + }); + }); + + describe('Scenario 4: Existence of critical directories', () => { + it('should point to a public/ directory whose parent exists', () => { + // public directory might not exist at test time, + // but the path should be valid + const publicDir = pathUtils.publicDirectoryPath; + + // Parent directory must exist + const parentDir = path.dirname(publicDir); + expect(fs.existsSync(parentDir)).toBe(true); + }); + + it('should have an accessible backend/ directory up the tree', () => { + // Walk up from publicDirectoryPath to find backend/ + let currentPath = pathUtils.publicDirectoryPath; + let foundBackend = false; + + // Up to 10 levels + for (let i = 0; i < 10; i++) { + if (path.basename(currentPath) === 'backend') { + foundBackend = true; + // Ensure this backend dir contains src/ + const srcPath = path.join(currentPath, 'src'); + expect(fs.existsSync(srcPath)).toBe(true); + break; + } + const parent = path.dirname(currentPath); + if (parent === currentPath) break; // Reached root + currentPath = parent; + } + + expect(foundBackend).toBe(true); + }); + }); + + describe('Scenario 5: Robustness and consistency', () => { + it('should keep the same resolution across multiple accesses', () => { + // Paths must be consistent + const path1 = pathUtils.publicDirectoryPath; + const path2 = pathUtils.publicDirectoryPath; + + expect(path1).toBe(path2); + expect(path1).toStrictEqual(path2); + }); + + it('should use path.join correctly (structure verification)', () => { + // frontendDirectoryPath should equal public + frontend + const expectedFrontend = path.join(pathUtils.publicDirectoryPath, 'frontend'); + expect(pathUtils.frontendDirectoryPath).toBe(expectedFrontend); + + // Verify webcomponent + const expectedWebcomponent = path.join( + pathUtils.publicDirectoryPath, + 'webcomponent', + 'openvidu-meet.bundle.min.js' + ); + expect(pathUtils.webcomponentBundlePath).toBe(expectedWebcomponent); + }); + }); +});