/* eslint-disable no-else-return */ /* eslint-disable @typescript-eslint/no-explicit-any */ /** * jfaker - A flexible data generator for Cypress tests, supporting both faker.js generated data and custom injection payloads. * * This module provides a unified interface to generate either faker data or custom injection payloads based on the method called and global settings. * It uses a dynamic Proxy to handle method calls and determine whether to generate faker data or injection data. * * IMPORTANT: * When using the generated strings from jfaker in Cypress commands like `.type()`, make sure to: * use `parseSpecialCharSequences: false`, e.g.: `.type(text, {parseSpecialCharSequences: false})` * to prevent Cypress from interpreting special characters in the generated strings (e.g., {, }, [, ], etc.) as commands, * which is especially important for injection payloads that may contain such characters. */ import {faker} from '@faker-js/faker'; // Import injection data from corresponding files in injections-ts directory import {xssData} from '../injections/xss-data'; import {sqlData} from '../injections/sql-data'; import {charsData} from '../injections/chars-data'; import {htmlentitiesData} from '../injections/htmlentities-data'; import {numbersData} from '../injections/numbers-data'; const injectionData: Record = { xss: xssData, sql: sqlData, chars: charsData, htmlentities: htmlentitiesData, numbers: numbersData }; // Environment variable key for storing injection type in Cypress env // Can be set either using corresponding FakeData method or as an env var from CI/CD pipeline const ENV_INJECTIONS_TYPE = 'JAHIA_CYPRESS_INJECTION_TYPE'; // Default range for random length of injection payloads; used when length is undefined // Random items within the range will be picked and joined into a single string const injectionsDefaultLength = {min: 2, max: 5}; /** * Store FakeData type in Cypress env for persistence across specs * @param {string} type FakeData type: 'faker' | 'xss' | 'sql' | 'chars' | 'htmlentities' | 'numbers' * @returns void */ function setDataType(type: string): void { Cypress.env(ENV_INJECTIONS_TYPE, type); } /** * Retrieve FakeData type from Cypress env (defaults to 'faker' if not set) * @returns {string} Type of FakeData to use */ function getDataType(): string | undefined { return Cypress.env(ENV_INJECTIONS_TYPE) || 'faker'; } /** * Generate injection data based on the specified type and length * @param {string} type Injection type to generate (xss, sql, chars, htmlentities, numbers) * @param {number} length Length of the generated injection (optional) * @returns {string} Generated injection string */ function generateInjection(type: string, length?: number): string { let result: string[] = []; // Type is specified and exists in injectionData, use it to generate data const data = injectionData[type]; if (!data || data.length === 0) { throw new Error(`[jFaker EXCEPTION] No injection data found for type: ${type}.`); } if (length === -1) { // If length is -1, use all available items from the data array result = data; } else if (length && length > 0) { // If length is specified and greater than 0, pick random items until the combined length meets the requirement while (result.join('').length < length) { const randomIndex = Math.floor(Math.random() * data.length); result.push(data[randomIndex]); } // Trim the combined string to the specified length const combined = result.join(''); result = [combined.substring(0, length)]; } else { // If length is not specified, pick a random number of items within the default range const itemsCount = Math.floor(Math.random() * injectionsDefaultLength.max) + injectionsDefaultLength.min; for (let i = 0; i < itemsCount; i++) { const randomIndex = Math.floor(Math.random() * data.length); result.push(data[randomIndex]); } } return result.join(''); } /** * Generate faker data based on the specified entity and options * @param {string} entity Faker entity to generate (e.g., "person.firstName") * @param {Record | string | number | boolean | undefined} options Options to pass to the faker method (optional) * @returns {string} Generated faker string */ function generateFake(entity: string, options?: Record | string | number | boolean | undefined): string { let generator: () => string; const result: string[] = []; const parts: string[] = entity.split('.'); let fakerMethod: any = faker; for (const part of parts) { if (fakerMethod && typeof fakerMethod[part] !== 'undefined') { fakerMethod = fakerMethod[part]; } else { throw new Error(`[jFaker EXCEPTION] Invalid faker method: ${entity}`); } } if (typeof fakerMethod === 'function') { generator = (options && Object.keys(options).length > 0) ? () => fakerMethod(options) : () => fakerMethod(); } else { throw new Error(`[jFaker EXCEPTION] ${entity} is not a function`); } result.push(generator()); return result.join(''); } /** * Escape string to prevent issues when used in contexts like HTML or JavaScript * @param str */ function escape(str: string): string { return JSON.stringify(str).slice(1, -1); } /** * Simple DeepApi class to create a dynamic nested Proxy for generating fake data using faker or injection data based on method calls */ class DeepApi { constructor(private handler: (path: string, args: any[]) => any) { return this.createProxy([]); } private createProxy(path: string[]): any { return new Proxy( // The target is a function, so the proxy itself is callable function () {}, { // Handle property access - go deeper get: (target, prop) => { return this.createProxy([...path, String(prop)]); }, // Handle function call - execute handler apply: (target, thisArg, args) => { return this.handler(path.join('.'), args); } } ); } } /** * Interface to Fake Data generator (using DeepApi proxy to handle dynamic method calls) * @param {Record} options Options for data generation (length for injections, faker options, safe flag), \ * e.g.: `{length: 100}` for injections to specify desired length of the generated string, \ * or `{provider: 'example.com'}` for faker to pass options to the faker method. \ * For faker data generation, an additional option `safe` can be set to `true` \ * to force faker generation regardless of global type settings \ * (useful for specific cases where faker data is needed even when global type is set to injection). * @remarks * Available injection methods: * - `.xss()` - Generate XSS injection payloads * - `.sql()` - Generate SQL injection payloads * - `.chars()` - Generate random special characters * - `.htmlentities()` - Generate HTML entities * - `.numbers()` - Generate random numbers entities and edge cases * - or any faker.js method can also be called (e.g., `person.firstName()`, `internet.email()`) * * @returns {string} Generated data string based on the method called and options provided * * @see https://fakerjs.dev/api/ for available faker methods and options * @example * ```typescript * * // Generate faker data with entity. * const name = jfaker.person.firstName(); * * // Entity will always be generated using faker (safe: true) * const name = jfaker.person.firstName({safe: true}); * * // Generate faker data with options. * const email = jfaker.internet.email({provider: 'example.com'}); * * // Generate injection payloads (random between min and max items joined into a single string).. * // Entity will always be generated using 'xss'. * const xssName = jfaker.xss(); * * // Generate injection payloads with specific length. * // Entity will always be generated using 'xss'. * const xssName = jfaker.xss({length: 100}); * * // Use all SQL injections. * // Entity will always be generated using 'sql'. * const allSql = jfaker.sql({length: -1}); * ``` */ const jfaker = new DeepApi((path, args) => { // Handle non-faker methods first (escape, setFakeDataType, getFakeDataType) switch (path) { case 'escape': return escape(args[0]); case 'setDataType': return setDataType(args[0]); case 'getDataType': return getDataType(); case Object.prototype.hasOwnProperty.call(injectionData, path) ? path : 'default': // If the path corresponds to a valid injection type, generate data using that type, // or fallback to default case which treats the path as a faker entity. // Keep (safe === true) logic in "default" case for a readability. return generateInjection(path, args[0]?.length); default: { // For faker data generation, check if the generation should be persistent based on global settings and options passed. const safe = args[0]?.safe === true; // Delete the 'safe' property from options to avoid passing it to faker methods delete args[0]?.safe; // Here path represents a faker entity. // Check desired data type and 'safe' option to determine whether to generate faker data or injection data. // If global type is set to injection (to override faker data) but safe is explicitly set to true in options - // generate faker data for this specific call regardless of global settings. if (getDataType() === 'faker' || safe) { return generateFake(path, args[0]); } else { return generateInjection(getDataType(), args[0]?.length); } } } }) as any; export {jfaker};