hay-caf
Version:
Haystacks Cloud Automation Framework: A demonstration testing automation framework application for the purposes of demonstrating the usage and integration of D-CAF, Hest, Hexiom, HayRunner, HayMonkey, and HayD-CAG in a single testing automation framework
301 lines (270 loc) • 15.5 kB
JavaScript
/**
* @file testRules.js
* @module testRules
* @description Contains all client defined business rules for the hay-CAF application.
* @requires module:application.constants
* @requires module:application.message.constants
* @requires module:application.system.constants
* @requires {@link https://www.npmjs.com/package/@haystacks/async|@haystacks/async}
* @requires {@link https://www.npmjs.com/package/@haystacks/constants|@haystacks/constants}
* @requires {@link https://www.npmjs.com/package/path|path}
* @author Seth Hollingsead
* @date 2023/03/30
* @copyright Copyright © 2023-… by Seth Hollingsead. All rights reserved
*/
// Internal imports
import * as apc from '../../constants/application.constants.js';
import * as app_msg from '../../constants/application.message.constants.js';
import * as app_sys from '../../constants/application.system.constants.js';
import * as app_cfg from '../../constants/application.configuration.constants.js';
// External imports
import haystacks from '@haystacks/async';
import hayConst from '@haystacks/constants';
import path from 'path';
import process from 'process';
import { fork } from 'child_process';
import socketsServer from '../../childProcess/socketsServer.js';
const { bas, biz, cfg, gen, msg, num, sys, wrd } = hayConst;
const baseFileName = path.basename(import.meta.url, path.extname(import.meta.url));
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const spawnProcess = `${__dirname}/../../childProcess/spawnProcess.js`;
// application.hay-CAF.businessRules.clientRules.testRules.
const namespacePrefix = wrd.capplication + bas.cDot + apc.cApplicationName + bas.cDot + wrd.cbusiness + wrd.cRules + bas.cDot + wrd.cclient + wrd.cRules + bas.cDot + baseFileName + bas.cDot;
/**
* @function buildArrayOfTestNames
* @description Takes a test workflow file name, parses the file name to get the test name, and adds the test name to an array of test names and returns the modified array.
* @param {string} inputData The file name and path of the test workflow data file.
* @param {array<string>} inputMetaData The array that the test name should be added to once it has been parsed, and if it fulfills the qualification as a valid test workflow file name.
* @return {array<string>} A modified string array that contains the test name if it is a valid test file name.
* @author Seth Hollingsead
* @date 2023/11/09
*/
async function buildArrayOfTestNames(inputData, inputMetaData) {
let functionName = buildArrayOfTestNames.name;
await haystacks.consoleLog(namespacePrefix, functionName, msg.cBEGIN_Function);
await haystacks.consoleLog(namespacePrefix, functionName, msg.cinputDataIs + JSON.stringify(inputData));
await haystacks.consoleLog(namespacePrefix, functionName, msg.cinputMetaDataIs + inputMetaData);
let returnData = inputMetaData;
let testFileName = await haystacks.executeBusinessRules([inputData, ''], [biz.cgetFileNameFromPath, biz.cremoveFileExtensionFromFileName],);
let executionEngine = await haystacks.getConfigurationSetting(wrd.csystem, app_cfg.cexecutionDriverEngine);
// executionEngine is:
await haystacks.consoleLog(namespacePrefix, functionName, app_msg.cexecutionEngineIs + executionEngine);
// testFileName is:
await haystacks.consoleLog(namespacePrefix, functionName, app_msg.ctestFileNameIs + testFileName);
// Now the test file name should have a prefix "Test_", lets make sure we only parse files with this prefix, otherwise they are not properly formatted tests,
// and the testing framework would have trouble executing them anyway!
if (testFileName.includes(wrd.cTest + bas.cUnderscore) === true) {
if (executionEngine === app_sys.ctestcafe) {
let testFileNameArray = testFileName.split(bas.cUnderscore); // Split the filename into an array so we can remove the prefix "Test_".
// testFileNameArray is:
await haystacks.consoleLog(namespacePrefix, functionName, app_msg.ctestFileNameArrayIs + JSON.stringify(testFileNameArray));
testFileNameArray.shift(); // Remove the "Test" prefix, this means we now just have the test name, not the file name.
returnData.push(testFileNameArray[0]); // Add the test name to the array of tests to execute.
} else {
returnData.push(testFileName);
}
} // End-if (testFileName.includes(wrd.cTest + bas.cUnderscore) === true)
await haystacks.consoleLog(namespacePrefix,functionName,msg.creturnDataIs + JSON.stringify(returnData));
await haystacks.consoleLog(namespacePrefix, functionName, msg.cEND_Function);
return returnData;
}
/**
* @function executeTestCommand
* @description Takes a command string and executes it on a CLI Windows CMD, BASH or PowerShell interface as a child process.
* @param {string} inputData The command string that should be executed in the child process.
* @param {'cmd'|'bash'|'powershell'} inputMetaData The command type to be used to spawn a child process
* @return {boolean} True or False to indicate if the test passed successfully or not.
* @author Seth Hollingsead
* @date 2023/11/14
*/
async function executeTestCommand(inputData, inputMetaData) {
let functionName = executeTestCommand.name;
await haystacks.consoleLog(namespacePrefix, functionName, msg.cBEGIN_Function);
await haystacks.consoleLog(namespacePrefix, functionName, msg.cinputDataIs + JSON.stringify(inputData));
await haystacks.consoleLog(namespacePrefix, functionName, msg.cinputMetaDataIs + inputMetaData);
let returnData = false;
const platform = process.platform.toLowerCase();
const supportedPlatforms = [gen.cdarwin, gen.cwin32, gen.clinux];
// Ensures the user is using a supported platform / OS.
if (supportedPlatforms.includes(platform)) {
if (inputData && inputMetaData) {
inputMetaData = String(inputMetaData).toLowerCase().trim() || '';
// Validates the selected command type
if (inputMetaData === '' || !app_sys.cvalidCommandTypes.split(bas.cComa).includes(inputMetaData)) {
// ERROR: You must specify a test type to execute. Command type is:
console.log(app_msg.cErrorExecuteTestCommandMessage01 + inputMetaData);
// Valid command types are:
console.log(app_msg.cvalidCommandTypesAre + app_sys.cvalidCommandTypes.split(bas.cComa).join(bas.cComa + bas.cSpace));
} else {
// Spawns child process using appropriate shell and executes input command
// TODO: Return true or false based on the test passing or failing.
returnData = await spawnCmdProcess(inputData, inputMetaData);
}
} else {
// ERROR: You must specify a test command to execute. Command is:
console.log(app_msg.cErrorExecuteTestCommandMessage01 + inputMetaData);
}
} else {
// ERROR: You're system,
// is not yet supported!
console.log(app_msg.cErrorYourSystemColon + platform + app_msg.cErrorIsNotYetSupported);
}
await haystacks.consoleLog(namespacePrefix, functionName, msg.creturnDataIs + JSON.stringify(returnData));
await haystacks.consoleLog(namespacePrefix, functionName, msg.cEND_Function);
return returnData;
}
/**
* @function spawnCmdProcess
* @description Detects the OS and creates a temporary shell script which executes the given command string in a child process's terminal
* @param {string} inputData The command string that should be executed in the child process.
* @param {string} inputMetaData This is not used in this business rule.
* @return {string} The string that executed command result
* @author Karl-Edward Jean-Mehu
* @date 2023/12/03
*/
async function spawnCmdProcess(inputData, inputMetaData) {
const functionName = spawnCmdProcess.name;
await haystacks.consoleLog(namespacePrefix, functionName, msg.cBEGIN_Function);
await haystacks.consoleLog(namespacePrefix, functionName, msg.cinputDataIs + inputData);
await haystacks.consoleLog(namespacePrefix, functionName, msg.cinputMetaDataIs + inputMetaData);
let result = false;
try {
// Start communication protocol Server
const socketServer = socketsServer();
await socketServer.connect();
// Obtain root path for haystacks
const _rootPath = await haystacks.getConfigurationSetting(wrd.csystem, app_cfg.crootTestFolderPath);
// Obtain the absolute path for CAFfeinated
const normalizedPath = path.normalize(_rootPath);
const directories = normalizedPath.split(path.sep);
const targetIndex = directories.indexOf(app_sys.cCAFfeinated) + 1;
const CAFfeinatedPath = directories.slice(0, targetIndex).join(bas.cForwardSlash);
// CAFfeinatedPath is:
await haystacks.consoleLog(namespacePrefix, functionName, app_msg.cCAFfeinatedPathIs + CAFfeinatedPath);
// Serialize shell options
const serializedOptions = JSON.stringify({
shell: inputMetaData,
CAFfeinatedPath,
debug: {
keepTmpFile: false
}
});
// Spawns a fork (independent) process
const childProcess = fork(spawnProcess, [
inputData,
serializedOptions,
{ stdio: [wrd.cpipe, wrd.cpipe, wrd.cpipe, gen.cipc] },
]);
// Handler for incoming data from child process
childProcess.on(wrd.cdata, async (chunk) => {
const eventName = bas.cDot + wrd.cdata;
await haystacks.consoleLog(namespacePrefix, functionName + eventName, msg.cBEGIN_Event);
// data is:
await haystacks.consoleLog(namespacePrefix, functionName + eventName, msg.cdataIs + chunk.toString());
const message = chunk.toString();
// message from child:
console.log(msg.cmessageFromChild + message);
// message is:
await haystacks.consoleLog(namespacePrefix, functionName + eventName, msg.cmessageIs + message);
await haystacks.consoleLog(namespacePrefix, functionName + eventName, msg.cEND_Event);
});
// Handler for incoming messages from child process
childProcess.on(wrd.cmessage, async (messageData) => {
const eventName = bas.cDot + wrd.cmessage;
await haystacks.consoleLog(namespacePrefix, functionName + eventName, msg.cBEGIN_Event);
// messageData is:
await haystacks.consoleLog(namespacePrefix, functionName + eventName, app_msg.cmessageDataIs + JSON.stringify(messageData));
if (messageData[wrd.cName]) {
// testScriptFileName is:
await haystacks.consoleLog(namespacePrefix, functionName + eventName, app_msg.ctestScriptFileNameIs + messageData[wrd.cName]);
await haystacks.setConfigurationSetting(wrd.csystem, app_cfg.ctestScriptFileName, messageData[wrd.cName]);
let tempTestScriptFileName = await haystacks.getConfigurationSetting(wrd.csystem, app_cfg.ctestScriptFileName);
// configuration testScriptFileName is:
await haystacks.consoleLog(namespacePrefix, functionName + eventName, app_msg.cconfigurationTestScriptFileNameIs + tempTestScriptFileName);
}
await haystacks.consoleLog(namespacePrefix, functionName + eventName, msg.cEND_Event);
});
// Error handler on child process
childProcess.on(wrd.cerror, async (error) => {
const eventName = bas.cDot + wrd.cerror;
await haystacks.consoleLog(namespacePrefix, functionName + eventName, msg.cBEGIN_Event);
// error is:
await haystacks.consoleLog(namespacePrefix, functionName + eventName, msg.cerrorIs + JSON.stringify(error));
console.log(msg.cERROR_Colon + error);
await haystacks.consoleLog(namespacePrefix, functionName + eventName, msg.cEND_Event);
await haystacks.consoleLog(namespacePrefix, functionName, msg.cEND_Function + bas.cSpace + num.c1);
return false;
});
// Child process exited
childProcess.on(wrd.cexit, async (code, signal) => {
const eventName = bas.cDot + wrd.cexit;
await haystacks.consoleLog(namespacePrefix, functionName + eventName, msg.cBEGIN_Event);
// exited with code:
await haystacks.consoleLog(namespacePrefix, functionName + eventName, msg.cexitedWithCode + JSON.stringify(code));
// signal is:
await haystacks.consoleLog(namespacePrefix, functionName + eventName, msg.csignalIs + JSON.stringify(signal));
// Display only unsuccessful exit codes
if (code !== 0) {
// Exited with code:
// , and signal:
console.log(msg.cexitedWithCode + code + msg.candSignal + signal);
await haystacks.consoleLog(namespacePrefix, functionName, msg.cEND_Function + bas.cSpace + num.c2);
}
return;
});
// Cleanup / delete tmp script file
const Cleanup = async () => {
const childFunction = bas.cDot + wrd.cClean + wrd.cup;
await haystacks.consoleLog(namespacePrefix, functionName + childFunction, msg.cBEGIN_Function);
let testScriptFileName = '';
let fileDeleted = false;
// Cleanup any script files from the last command run.
testScriptFileName = await haystacks.getConfigurationSetting(wrd.csystem, app_cfg.ctestScriptFileName);
// main.testScriptFileName is:
// console.log(app_msg.cmainTestScriptFileNameIs + testScriptFileName);
await haystacks.consoleLog(namespacePrefix, functionName, app_msg.cmainTestScriptFileNameIs + testScriptFileName);
if (testScriptFileName !== '') {
let applicationRootPath = await haystacks.getConfigurationSetting(wrd.csystem, cfg.cclientRootPath);
// main.applicationRootPath is:
await haystacks.consoleLog(namespacePrefix, functionName, app_msg.cmainApplicationRootPathIs + applicationRootPath);
// TODO Make sure to apply additional logic for file name and path to make sure it's fully qualifed
fileDeleted = await haystacks.executeBusinessRules([testScriptFileName, ''], [biz.cdeleteFile]);
if (fileDeleted === true) {
await haystacks.setConfigurationSetting(wrd.csystem, app_cfg.ctestScriptFileName, '');
}
}
await haystacks.consoleLog(namespacePrefix, functionName + childFunction, msg.cEND_Function);
}
// Obtain the child process time (allotted) limit
const childProcessLimitTime = Number(await haystacks.getConfigurationSetting(wrd.csystem, app_cfg.cchildProcessLimitTime));
result = await new Promise((resolve, reject) => {
const childFunction = bas.cDot + wrd.cresult;
haystacks.consoleLog(namespacePrefix, functionName + childFunction, msg.cBEGIN_Function);
// Obtain results from test
socketServer.getTestResult(childProcessLimitTime).then(testResult => {
haystacks.consoleLog(namespacePrefix, functionName + childFunction, namespacePrefix + functionName + childFunction + sys.cSpaceIsColonSpace + testResult);
Cleanup();
// Kill the child process.
childProcess.kill();
resolve(testResult);
})
.catch(error => {
haystacks.consoleLog(namespacePrefix, functionName + childFunction, namespacePrefix + functionName + childFunction + bas.cDot + wrd.cerror + sys.cSpaceIsColonSpace + error);
Cleanup();
// Kill the child process.
childProcess.kill();
console.log(error);
reject(false);
});
haystacks.consoleLog(namespacePrefix, functionName + childFunction, msg.cEND_Function);
});
await socketServer.terminate();
} catch (e) {
console.log(e);
}
await haystacks.consoleLog(namespacePrefix, functionName, msg.cEND_Function + bas.cSpace + num.c3);
return new Promise(resolve => { resolve(result)});
}
export { executeTestCommand, buildArrayOfTestNames, spawnCmdProcess };