This repository was archived by the owner on Nov 19, 2020. It is now read-only.
forked from jkamepalli/PulseRules_v9.1
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCsRules.json
More file actions
1 lines (1 loc) · 29.9 KB
/
CsRules.json
File metadata and controls
1 lines (1 loc) · 29.9 KB
1
{"constants":[{"id":"constant-1","name":"ManagerURL","hidden":false,"value":""},{"id":"constant-2","name":"SlackWebHook","hidden":false,"value":""},{"id":"constant-3","name":"SCENARIO_PROJECT_ID","hidden":false,"value":""},{"id":"constant-4","name":"QTEST_TOKEN","hidden":false,"value":""},{"id":"constant-5","name":"Scenario_URL","hidden":false,"value":""}],"actions":[{"id":"action-1","name":"LinkScenarioRequirements","description":null,"code":"const { Webhooks } = require('@qasymphony/pulse-sdk');\n\n// @pure\nconst standardHeaders = (qTestToken, scenarioPojectId) => ({\n 'Content-Type': 'application/json',\n 'Authorization': `bearer ${qTestToken}`,\n 'x-scenario-project-id': scenarioPojectId\n});\n\n// @pure\nconst options = (url, headers) => new Request(`${url}/api/features`, {\n method: 'GET',\n headers: headers\n});\n\n// @pure\nconst genericRequest = (url, headers, body) => new Request(url, {\n url: url,\n method: 'POST',\n headers: headers,\n body: JSON.stringify(body)\n});\n\n// @pure\nconst testCasesRequest = (url, headers, tcName) => {\n const body = {\n \"object_type\": \"test-cases\",\n \"fields\": [\n \"*\"\n ],\n \"query\": `Name = '${tcName}'`\n }\n return genericRequest(url, headers, body);\n};\n\n// @pure\nconst requirementsRequest = (url, headers, key) => {\n const body = {\n \"object_type\": \"requirements\",\n \"fields\": [\n \"*\"\n ],\n \"query\": `Name ~ '${key}'`\n }\n return genericRequest(url, headers, body);\n};\n\n// @pure\nconst linksRequest = (url, headers, reqid, tcid) => {\n const body = [\n tcid\n ]\n return genericRequest(url, headers, body);\n};\n\n// @pure\nconst findMatchingFeature = (testCase, scenarioFeatures) =>\n scenarioFeatures.find(\n scenarioFeature => scenarioFeature.name === testCase.featureName\n );\n\n// @pure\nconst testCasesExistingOnScenario = (testCase, scenarioFeatures) =>\n findMatchingFeature(testCase, scenarioFeatures) != null;\n\n// @pure\nconst searchResponseContainsNoItems = object => object.items.length === 0;\n\n// @pure\nconst isNotBackground = ({ keyword }) => keyword.toLowerCase() !== 'background';\n\n// @pure\nconst sleep = milliSeconds =>\n new Promise(\n resolve => setTimeout(resolve, milliSeconds)\n );\n\n// @impure: mutable object\nconst State = {\n constants: null,\n triggers: null,\n projectId: null,\n}\n\n// @impure: reliant on network and the mutable object State\nconst emitEvent = (name, payload) => {\n const t = State.triggers.find(t => t.name === name);\n return t && new Webhooks().invoke(t, payload);\n}\n\n// @impure: reliant on mutable object State\nconst getStandardHeaders = () => standardHeaders(State.constants.QTEST_TOKEN, State.constants.SCENARIO_PROJECT_ID);\n\n// @impure: reliant on mutable object State\nconst getOptions = () => {\n const headers = getStandardHeaders();\n return options(State.constants.Scenario_URL, headers);\n}\n\n// @impure: reliant on mutable object State\nconst getManagerSearchUrl = () => `${State.constants.ManagerURL}/api/v3/projects/${State.projectId}/search`;\n\n// @impure: reliant on mutable object State\nconst testCasesFromManager = tcName => {\n const url = getManagerSearchUrl();\n const headers = getStandardHeaders();\n return testCasesRequest(url, headers, tcName);\n}\n\n// @impure: reliant on mutable object State\nconst requirementsFromManager = key => {\n const url = getManagerSearchUrl();\n const headers = getStandardHeaders();\n return requirementsRequest(url, headers, key);\n}\n\n// @impure: reliant on mutable object State\nconst testCaseLinkOnRequirement = (reqId, testCaseId) => {\n const url = `${State.constants.ManagerURL}/api/v3/projects/${State.projectId}/requirements/${reqId}/link?type=test-cases`\n const headers = getStandardHeaders();\n return linksRequest(url, headers, reqId, testCaseId)\n}\n\n// @impure: reliant on network\nconst getRequirementId = async function({ issueKey }) {\n const response = await fetch(requirementsFromManager(issueKey));\n const requirements = await response.json();\n\n if (searchResponseContainsNoItems(requirements)) {\n return Promise.reject(\"[Info] No matching requirement found\");\n }\n return Promise.resolve(requirements.items[0].id);\n}\n\n// @impure: reliant on network\nconst getMatchingTestCases = async function(name) {\n const response = await fetch(testCasesFromManager(name));\n return response.json();\n}\n\n// @impure: reliant on network\nconst getTestCaseId = async function({ name }) {\n let testCases = await getMatchingTestCases(name);\n\n // Implement retry logic to handle the delay when a new test case is created\n for (let attempt = 1; attempt <= 10 && searchResponseContainsNoItems(testCases); attempt++) {\n console.log(`[Info] Retrying to get matching test cases, attempt (${attempt})...`);\n await sleep(5000);\n testCases = getMatchingTestCases(name);\n }\n\n if (searchResponseContainsNoItems(testCases)) {\n return Promise.reject(`[Info] No matching test case found: ${name}`);\n }\n return Promise.resolve(testCases.items[0].id);\n}\n\n// @impure: reliant on network and uses I/O\nconst useReqAndTcIdToCreateLink = async function(reqId, testCaseId, { name }, { issueKey }) {\n try {\n const response = await fetch(testCaseLinkOnRequirement(reqId, testCaseId));\n await emitEvent('SlackEvent', { Linking: `link added for TC: ${name} -> Requirement: ${issueKey}` });\n console.log(`[Info] A link is added TC: ${name} -> Req: ${issueKey}`);\n } catch (error) {\n await emitEvent('SlackEvent', { Error: `Problem creating test link to requirement: ${error}` });\n return Promise.reject(`[Error] Failed to create a link. ${error}`);\n }\n}\n\n// @impure: reliant on network and uses I/O\nconst createLink = async function(testCase, scenarioFeatures) {\n try {\n const matchingFeature = findMatchingFeature(testCase, scenarioFeatures);\n const reqId = await getRequirementId(matchingFeature);\n const testCaseId = await getTestCaseId(testCase);\n const response = await useReqAndTcIdToCreateLink(reqId, testCaseId, testCase, matchingFeature);\n } catch (error) {\n console.log(error);\n }\n}\n\n// @impure: reliant on network\nconst linkRequirements = async function(testCases, scenarioFeatures) {\n const matchingTestCases = testCases.filter(testcase =>\n testCasesExistingOnScenario(testcase, scenarioFeatures));\n\n return Promise.all(\n matchingTestCases.map(testCase => createLink(testCase, scenarioFeatures))\n )\n}\n\n// Entry point to the script\n// @impure: reliant on network and uses I/O\nexports.handler = async function({ event: body, constants, triggers }, context, callback) {\n console.log(\"[Info] Starting Link Requirements Action\");\n\n const { logs, projectId } = body;\n const testCases = logs.filter(isNotBackground);\n\n State.constants = constants;\n State.triggers = triggers;\n State.projectId = projectId;\n\n try {\n const options = getOptions(constants.Scenario_URL);\n const response = await fetch(options);\n const scenarioFeatures = await response.json();\n console.log(\"Got Features List:\", scenarioFeatures);\n await linkRequirements(testCases, scenarioFeatures);\n\n } catch (error) {\n console.log(\"[Error]\", error);\n throw error;\n }\n console.log(\"[Info] Finished linking requirements\");\n}"},{"id":"action-2","name":"SlackAction","description":null,"code":"const PulseSdk = require('@qasymphony/pulse-sdk');\n\n// @pure\nconst getPayload = body => {\n const text = JSON.stringify(body);\n return {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ \"text\": text })\n }\n}\n\n// Entry point to the script\n// @impure: reliant on network and uses I/O\nexports.handler = async function({ event: body, constants, triggers }, context, callback) {\n console.log('About to request slack webhook: ', constants.SlackWebHook);\n\n try {\n const response = await fetch(constants.SlackWebHook, getPayload(body));\n console.log(\"URL:\", response.url, \"Status:\", response.status, \"Status Text:\", response.statusText);\n } catch (error) {\n console.log(\"Caught Error:\", error);\n throw error;\n }\n}"},{"id":"action-3","name":"scenarioColors","description":null,"code":"const ScenarioSdk = require('@qasymphony/scenario-sdk');\n\n// @pure\nconst flattenArray = (acc, arrayValue) => acc.concat(arrayValue);\n\n// @pure\nconst testStepLogs = ({ test_step_logs }) => test_step_logs;\n\n// @pure\nconst steps = logs => logs\n .map(testStepLogs)\n .reduce(flattenArray);\n\n// Ensures the status matches a status supported by Scenario: One of PASSED (green), FAILED (red), or SKIPPED (yellow)\n// @pure\nconst processStatus = ({ status }) => (\"undefined\" === status) ? \"FAILED\" : status.toUpperCase();\n\n// @impure: mutable\nconst State = {\n sdk: null\n}\n\n// @impure: Data returned is dependent on where the ScenarioSDK is pointing\nconst getStepSdk = (qtestToken, scenarioProjectId) => new ScenarioSdk.Steps({ qtestToken, scenarioProjectId });\n\n// @impure: has side effects (updates ScenarioSdk)\nconst setConfigAndRetrieveSDK = (scenarioUrl, qtestToken, scenarioProjectId) => {\n ScenarioSdk.config({ scenarioUrl: scenarioUrl });\n return getStepSdk(qtestToken, scenarioProjectId);\n}\n\n// @impure: uses I/O \nconst loggingOutError = err => console.log('Error updating colors: ' + err);\n\n// @impure: reliant on state object\nconst addingStatusToStep = (sdkStep, status) => State.sdk.updateStep(\n sdkStep.id,\n Object.assign(sdkStep, { \"status\": status })\n);\n\n// @impure: reliant on network\nconst asyncronouslyUpdateTheStatusOfEachStep = (sdkSteps, status) => Promise.all(sdkSteps.map((sdkStep) => addingStatusToStep(sdkStep, status)));\n\n// Grabs all of the steps matching the step description using the Scenario API and then updates their status based on the test results\n// Note - Whilst the test results can feature a step multiple times, the API only appears to return a single instance, so the status will get set multiple times, and if there is \n// different a statuses the last one set will win E.g. in one instance the step passed and in another it failed you are at the mercy of the order in which the promises resolve as to\n// which gets set in Scenario.\n// There are several ways where we could actively prioritise setting one status over the other, but I'm not sure which would be more desirable. E.g. If we have a step that ran twice and in one instance it passes and in one it fails. \n// If we took the passed result we would be masking a failure, but if we took a failed result the output of the scenario where the step passed would be confusing. (It would appear as if it continued to run other steps after a step failed.)\n// This is a limitation of the Scenario SDK and should be raised with Tricentis.\n// @impure: reliant on state object\nconst updateStepResults = (name, status) => {\n State.sdk.getSteps(`\"${name}\"`)\n .then(sdkSteps => asyncronouslyUpdateTheStatusOfEachStep(sdkSteps, status))\n .catch(loggingOutError);\n}\n\n// @impure: calls updateStepResults which is reliant on network\nconst updateStep = (step, index) => updateStepResults(step.expected_result, processStatus(step));\n\n// @impure: sets state and performs network actions\nexports.handler = function({ event: body, constants, triggers }, context, callback) {\n const payload = body;\n\n State.sdk = setConfigAndRetrieveSDK(constants.Scenario_URL, constants.QTEST_TOKEN, constants.SCENARIO_PROJECT_ID);\n\n steps(payload.logs).forEach(updateStep);\n}"},{"id":"action-4","name":"UpdateQTestWithFormattedResults","description":null,"code":"const { Webhooks } = require('@qasymphony/pulse-sdk');\n\n// @pure\nconst State = {\n triggers: null,\n};\n\n// @pure\nconst isNotBackground = ({ keyword }) => keyword.toLowerCase() !== 'background';\n\n// @pure\nconst getHeaders = (token) => ({\n 'Content-Type': 'application/json',\n 'Authorization': `bearer ${token}`\n});\n\n// @pure\nconst getBody = (cycleId, testLogs) => JSON.stringify({\n test_cycle: cycleId,\n test_logs: testLogs\n});\n\n// @pure\nconst generateRequest = ({ ManagerURL, QTEST_TOKEN }, cycleId, projectId, testLogs) => {\n const url = `${ManagerURL}/api/v3/projects/${projectId}/auto-test-logs?type=automation`;\n\n const data = {\n method: \"POST\",\n headers: getHeaders(QTEST_TOKEN),\n body: getBody(cycleId, testLogs)\n };\n\n return new Request(url, data);\n};\n\n// @impure: reliant on network connectivity\nconst emitEvent = (name, payload) => {\n const t = State.triggers.find(t => t.name === name);\n return t && new Webhooks().invoke(t, payload);\n};\n\n// @impure: reliant on network connectivity\nasync function postTestLogsToTestManager(request) {\n const response = await fetch(request);\n const resbody = await response.json();\n\n emitEvent('SlackEvent', { AutomationLogUploaded: resbody });\n\n if (resbody.type === \"AUTOMATION_TEST_LOG\") {\n return Promise.resolve(\"Uploaded results successfully\");\n }\n\n emitEvent('SlackEvent', { Error: \"Wrong type\" });\n return Promise.reject(\"Unable to upload test results\");\n};\n\n// @impure: reliant on network connectivity and uses I/O\nasync function postLogsAndCallAdditionalPulseActions(request, payload) {\n try {\n const response = await postTestLogsToTestManager(request);\n console.log(response);\n await emitEvent('LinkScenarioRequirements', payload);\n await emitEvent('UpdateDescriptionPreconditionAndPrettify', payload);\n\n } catch (error) {\n const { url, status, statusText } = error;\n const errorMessage = (url != null) ?\n `url: ${url}, status: ${status}, status text: ${statusText}` :\n error;\n\n console.log('Caught Error:', errorMessage);\n emitEvent('SlackEvent', { CaughtError: errorMessage });\n }\n};\n\n// @impure: sets state, relies on network connectivity and uses I/O\nexports.handler = ({ event: body, constants, triggers }, context, callback) => {\n State.triggers = triggers;\n\n const { logs, \"test-cycle\": cycleId, projectId } = body;\n\n const testLogs = logs.filter(isNotBackground);\n\n const request = generateRequest(constants, cycleId, projectId, testLogs);\n\n postLogsAndCallAdditionalPulseActions(request, body);\n};"},{"id":"action-5","name":"UpdateDescriptionPreconditionAndPrettify","description":null,"code":"const PulseSdk = require('@qasymphony/pulse-sdk');\n\n// @pure\nconst isBackground = ({ keyword }) => keyword.toLowerCase() === 'background';\n\n// @pure\nconst getHeaders = (token) => ({\n 'Content-Type': 'application/json',\n 'Authorization': `bearer ${token}`\n});\n\n// @pure\nconst convertKeywordAndNameToParagraph = (keyword, name) =>\n `<p><span style='color: #808000;;'><strong>${keyword}:</strong></span> ${name}</p>`;\n\n// @pure\nconst convertStepToSpanWithBreak = ({ description, expected_result }) =>\n `<span style='color: #008080; padding-left: 10px'><strong>${description}</strong></span>${expected_result}<br>`;\n\n// @pure\nconst convertDescriptionToParagraph = description => `<p style='padding-left: 10px'><em>${description}</em></p>`;\n\n// @pure\nconst convertStepLogsToPrecondition = ({ keyword, name, description, test_step_logs }) => {\n const header = convertKeywordAndNameToParagraph(keyword, name);\n const summary = (description !== \"\") ? convertDescriptionToParagraph(description) : \"\";\n const paragraphs = test_step_logs.map(convertStepToSpanWithBreak);\n\n return `${header}${summary}${paragraphs.join(\"\")}`;\n};\n\n// @pure\nconst isDuplicateField = (item, index, array, property) => array.map(object => object[property])\n .indexOf(item[property]) !== index;\n\n// @pure\nconst removingDuplicatesCausedByScenarioOutlines = (item, index, arr) => {\n const isDuplicateName = isDuplicateField(item, index, arr, \"name\");\n const isDuplicateFeatureName = isDuplicateField(item, index, arr, \"featureName\");\n\n return !(isDuplicateName && isDuplicateFeatureName);\n};\n\n// @pure\nconst isNotABackgroundAndHasAMatchingFeatureName = ({ keyword, featureName }, nameToMatch) =>\n (featureName === nameToMatch) && (keyword.toLowerCase() !== 'background');\n\n// @pure\nconst getSearchUrl = (managerUrl, projectId) => `${managerUrl}/api/v3/projects/${projectId}/search`;\n\n// @pure\nconst getApproveTestCaseUrl = (managerUrl, projectId, id) => `${managerUrl}/api/v3/projects/${projectId}/test-cases/${id}/approve`\n\n// @pure\nconst getSearchBody = name => ({\n object_type: \"test-cases\",\n fields: [\"id\", \"test_steps\"],\n query: `name = '${name}'`\n});\n\n// @pure\nconst getUpdateTestCaseUrl = (managerUrl, projectId, id) => `${managerUrl}/api/v3/projects/${projectId}/test-cases/${id}`;\n\n// @pure\nconst formatDescriptionIfNotEmpty = description => description !== \"\" ? convertDescriptionToParagraph(description) : \"\";\n\n// @pure\nconst getUpdateTestCaseBody = (precondition, description) => ({\n precondition: precondition,\n description: formatDescriptionIfNotEmpty(description)\n});\n\n// @pure\nconst sleep = milliSeconds => new Promise(\n resolve => setTimeout(resolve, milliSeconds)\n);\n\n// @impure: mutable\nconst State = {\n projectId: null,\n managerUrl: null,\n qTestToken: null,\n logs: null\n};\n\n// @impure: reliant on network and mutable state\nconst performApiCall = async function(url, method, body) {\n const { qTestToken } = State;\n const payload = {\n method: method,\n headers: getHeaders(qTestToken),\n body: JSON.stringify(body)\n }\n const response = await fetch(url, payload);\n\n return response.json();\n};\n\n// @impure: reliant on network and mutable state\nconst performApiCallEmptyBody = async function(url, method) {\n const { qTestToken } = State;\n const payload = {\n method: method,\n headers: getHeaders(qTestToken)\n }\n\n const response = await fetch(url, payload);\n\n return response.json();\n};\n\n// @impure: reliant on network and mutable state and uses I/O\nconst getTestCaseId = async function({ name }) {\n const { managerUrl, projectId } = State;\n const url = getSearchUrl(managerUrl, projectId);\n const body = getSearchBody(name);\n\n let resbody = await performApiCall(url, \"POST\", body);\n\n // Implement retry logic to handle the delay when a new test case is created\n for (let attempt = 1; attempt <= 10 && resbody.items.length === 0; attempt++) {\n console.log(`Retrying to get matching test cases, attempt (${attempt})...`);\n await sleep(5000);\n resbody = await performApiCall(url, \"POST\", body);\n }\n\n if (resbody.items.length === 0) {\n return Promise.reject(`Failed to return a matching test case for '${name}'`);\n }\n\n return Promise.resolve(resbody.items[0].id);\n};\n\n// @impure: reliant on network and mutable state\nconst updateAllTestCases = async function(testCase) {\n const { precondition } = this;\n const { description } = testCase;\n const id = await getTestCaseId(testCase);\n\n return await updateTestCaseFields(id, precondition, description);\n};\n\n// @impure: reliant on network and mutable state\nconst approveAllTestCases = async function(testCase) {\n const { managerUrl, projectId } = State;\n const id = await getTestCaseId(testCase);\n const url = await getApproveTestCaseUrl(managerUrl, projectId, id);\n return await performApiCallEmptyBody(url, \"PUT\");\n};\n\n// @impure: reliant on network and mutable state\nconst updateTestCaseFields = async function(id, precondition, description) {\n const { managerUrl, projectId } = State;\n const url = getUpdateTestCaseUrl(managerUrl, projectId, id);\n const body = getUpdateTestCaseBody(precondition, description);\n\n return await performApiCall(url, \"PUT\", body);\n};\n\n// @impure: reliant on network and mutable state\nasync function generatePreconditionAndPostToManager(background) {\n const { logs } = State;\n const nameToMatch = background.featureName;\n const precondition = { precondition: convertStepLogsToPrecondition(background) };\n const matchingTestCases = logs.filter(\n log => isNotABackgroundAndHasAMatchingFeatureName(log, nameToMatch)\n );\n\n const updates = await Promise.all(\n matchingTestCases.map(updateAllTestCases, precondition)\n );\n\n const approvals = await Promise.all(\n matchingTestCases.map(approveAllTestCases)\n );\n\n return updates.concat(approvals);\n};\n\n// Entry point to the script\n// @impure: reliant on network and uses I/O\nexports.handler = async function({ event: body, constants, triggers }, context, callback) {\n const { logs, projectId } = body;\n const { ManagerURL, QTEST_TOKEN } = constants;\n const backgrounds = logs.filter(isBackground)\n .filter(removingDuplicatesCausedByScenarioOutlines);\n\n State.projectId = projectId;\n State.managerUrl = ManagerURL;\n State.qTestToken = QTEST_TOKEN;\n State.logs = logs;\n\n try {\n await Promise.all(\n backgrounds.map(generatePreconditionAndPostToManager)\n );\n console.log(\"Successfully updated all test cases\");\n } catch (error) {\n console.log(error);\n }\n\n console.log(\"End of job\");\n};"},{"id":"action-6","name":"FormatJavaCucumber","description":null,"code":"const { Webhooks } = require('@qasymphony/pulse-sdk');\n\n// Returns an array of step names\n// @pure\nconst getStepNames = testCase => testCase.steps.map(step => step.name);\n\n// Create a deep clone of object when wanting to avoid mutating original data, this method is only safe if the supplied object is 100% parsable in JSON\n// @pure\nconst createDeepCloneOfJsonObject = object => JSON.parse(JSON.stringify(object));\n\n// Creates a clone of the step before iterating through the attachments contained within it, adding a 'step_name' field\n// @pure\nconst attachmentsWithStepNameInjected = (step) => createDeepCloneOfJsonObject(step).embeddings.map(injectStepName, step);\n\n// Flattens a multidimensional array, into a simple array by storing each value in the accumulator\n// @pure\nconst flattenArray = (acc, arrayValue) => acc.concat(arrayValue);\n\n// @pure\nconst hasEmbeddings = object => object.hasOwnProperty(\"embeddings\");\n\n// @pure\nconst attachmentInformation = (attachment, index) => {\n return {\n name: `${attachment.step_name} Attachment ${index + 1}`,\n \"content_type\": attachment.mime_type,\n data: attachment.data\n }\n};\n\n// @pure\nconst getStepAttachments = testCase => testCase.steps.filter(hasEmbeddings)\n .map(attachmentsWithStepNameInjected)\n .reduce(flattenArray, [])\n .map(attachmentInformation);\n\n// @pure\nconst getHookAttachments = testCase => (testCase.hasOwnProperty(\"after\")) ? testCase.after.filter(hasEmbeddings)\n .map(attachmentsWithStepNameInjected)\n .reduce(flattenArray, [])\n .map(attachmentInformation) : [];\n\n// Grabs all attachments from the hooks and steps and combines them into a single flat array of attachment information\n// @pure\nconst getAllAttachments = testCase => getStepAttachments(testCase).concat(getHookAttachments(testCase));\n\n// Creates a clone of the feature before iterating through the test cases contained within it, adding the 'feature_name' and 'feature_uri' fields.\n// @pure\nconst testCasesWithFeatureNameAndUriInjected = feature => createDeepCloneOfJsonObject(feature).elements.map(injectFeatureNameAndUri, feature);\n\n// @pure\nconst moduleThatHasFeatureFileExtension = (module) => module.includes(\".FEATURE\");\n\n// @pure\nconst getLowerCaseFeatureNameNoExtension = name => name.toLowerCase()\n .replace(\".feature\", \"\");\n\n// @pure\nconst getUpperCaseSubModules = url => url.replace(/.+features\\//i, \"\")\n .toUpperCase()\n .split(\"/\");\n\n// @pure\nconst addParentFeaturesModuleAndReplaceLastModule = (moduleArray, replacementModule) => {\n const modules = [\"FEATURES\"].concat(moduleArray);\n modules.pop();\n modules.push(replacementModule);\n return modules;\n}\n\n// gets all of the folders after the 'features' directory\n// @pure\nconst getModules = URI => {\n const subModules = getUpperCaseSubModules(URI);\n const featureName = getLowerCaseFeatureNameNoExtension(\n subModules.find(moduleThatHasFeatureFileExtension)\n );\n return addParentFeaturesModuleAndReplaceLastModule(subModules, featureName);\n}\n\n// Injects the step name into the attachment object, relying on the context of 'this' to be set to a step object \n// e.g. an arrow function cannot be used\n// @impure: relies on the context of 'this'\nconst injectStepName = function(attachment) {\n attachment.step_name = this.name || \"Hook\";\n return attachment;\n}\n\n// Enum object to handle possible statuses within cucumber json output and in qTest Manager\n// @impure: mutable\nconst Status = {\n PASSED: \"passed\",\n FAILED: \"failed\",\n SKIPPED: \"skipped\",\n UNDEFINED: \"undefined\",\n PENDING: \"pending\",\n BLOCKED: \"blocked\",\n}\n\n// Calculates the overall testcase status based on the result of the passed in step, storing the result in the accumulator\n// @impure: reliant on mutable object\nconst testCaseStatus = (acc, step) => (Status.PASSED === acc) ? step.result.status : acc;\n\n// Gets the testcase status based on the result of each step\n// @impure: reliant on mutable object\nconst getTCStatus = testCase => testCase.steps.reduce(testCaseStatus, Status.PASSED);\n\n// Returns an actual result based on step.result.status\n// A step is skipped when a previous step, background step or before hook fails\n// A step is undefined when it exists in a feature but no definition is found\n// A step is pending when it exists in a feature file, has a defition, but explicitly throws a PendingException\n// @impure: reliant on mutable object\nconst getActualResult = step => {\n return {\n [Status.PASSED]: step.name,\n [Status.FAILED]: step.result.error_message,\n [Status.SKIPPED]: \"This step has been skipped due to a previous failure\",\n [Status.UNDEFINED]: \"This step does not match any step definitions\",\n [Status.PENDING]: \"This step is marked as pending\"\n } [step.result.status];\n}\n\n// Generates a step log object for injection into a test log\n// @impure: reliant on mutable object\nconst testStepLogs = testCase => testCase.steps.map((step, index) => {\n return {\n order: index,\n description: `${step.keyword}`,\n expected_result: step.name,\n actual_result: getActualResult(step),\n status: step.result.status\n };\n});\n\n// Injects the feature name and URI into the test case object, relying on the context of 'this' to be set to a feature object \n// e.g. an arrow function cannot be used\n// @impure: reliant on the context of 'this'\nconst injectFeatureNameAndUri = function(testCase) {\n testCase.feature_uri = this.uri;\n testCase.feature_name = this.name;\n return testCase;\n}\n\n// Create a new object to represent a test log and populate it's fields\n// @impure: reliant on mutable object\nconst testLogs = testCase => ({\n exe_start_date: new Date(), // TODO These could be passed in\n exe_end_date: new Date(),\n module_names: getModules(testCase.feature_uri),\n name: testCase.hasOwnProperty(\"name\") ? testCase.name : \"Unnamed\",\n automation_content: testCase.feature_uri + \"#\" + testCase.name,\n attachments: getAllAttachments(testCase),\n status: getTCStatus(testCase),\n test_step_logs: testStepLogs(testCase),\n keyword: testCase.keyword,\n featureName: testCase.feature_name,\n description: testCase.description,\n});\n\n// Loops through all of the features and test cases creating a test log for each\n// @impure: reliant on mutable object\nconst generateTestLogs = features => features.map(testCasesWithFeatureNameAndUriInjected)\n .reduce(flattenArray, [])\n .map(testLogs);\n\n// @impure: mutable\nconst State = {\n triggers: null\n}\n\n// @impure: reliant on state and network\nconst emitEvent = (name, payload) => {\n let t = State.triggers.find(t => t.name === name);\n return t && new Webhooks().invoke(t, payload);\n}\n\n// Entry point to the script, it takes the cucumber json input and reformat it into qTest Manager friendly\n// json before handing it off to the down stream rule\n// @impure: reliant on state and network connectivity\nexports.handler = ({ event: body, constants, triggers }, context, callback) => {\n\n State.triggers = triggers;\n\n const payload = body;\n\n const formattedResults = {\n \"projectId\": payload.projectId,\n \"test-cycle\": payload[\"test-cycle\"],\n \"logs\": generateTestLogs(payload.result)\n };\n\n // Pulse Version\n // Emit next fxn to upload results/parse\n emitEvent('UpdateQTestWithFormattedResultsEvent', formattedResults);\n}"}],"triggers":[{"id":"trigger-1","name":"SlackEvent"},{"id":"trigger-2","name":"UpdateQTestWithFormattedResultsEvent"},{"id":"trigger-3","name":"UploadJavaCucumberResults"},{"id":"trigger-4","name":"LinkScenarioRequirements"},{"id":"trigger-5","name":"UpdateDescriptionPreconditionAndPrettify"},{"id":"trigger-6","name":"scenarioColors"}],"rules":[{"id":"rule-7","name":"MessageSlack","active":true,"triggerId":"trigger-1","actionIds":["action-2"],"tags":[]},{"id":"rule-8","name":"UploadJavaCucumberResults","active":true,"triggerId":"trigger-3","actionIds":["action-6"],"tags":[]},{"id":"rule-9","name":"UploadJavaCucumberResultsWithScenario","active":true,"triggerId":"trigger-2","actionIds":["action-3","action-4"],"tags":[]},{"id":"rule-10","name":"UpdateDescriptionPreconditionAndPrettify","active":true,"triggerId":"trigger-5","actionIds":["action-5"],"tags":[]},{"id":"rule-11","name":"LinkTestCasesToRequirementsWithScenario","active":true,"triggerId":"trigger-4","actionIds":["action-1"],"tags":[]}]}