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
This commit is contained in:
Carlos Santos 2025-10-17 12:05:20 +02:00
parent 4e65ca2d04
commit 498e837c70
3 changed files with 207 additions and 16 deletions

View File

@ -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$': '<rootDir>/../typings/src/index.ts'
'^@openvidu-meet/typings$': '<rootDir>/../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;

View File

@ -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}'"
},

View File

@ -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);
});
});
});