Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 36 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@mojaloop/ml-testing-toolkit-client-lib",
"description": "Testing Toolkit Client Library",
"version": "1.12.0",
"version": "1.12.1-snapshot.13",
"license": "Apache-2.0",
"author": "Vijaya Kumar Guthi, ModusBox Inc. ",
"contributors": [
Expand Down Expand Up @@ -75,9 +75,9 @@
"@slack/webhook": "7.0.6",
"atob": "2.1.2",
"aws-sdk": "2.1692.0",
"axios": "1.12.2",
"axios": "1.13.2",
"cli-table3": "0.6.5",
"commander": "14.0.1",
"commander": "14.0.2",
"dotenv": "17.2.3",
"fs": "0.0.1-security",
"lodash": "4.17.21",
Expand All @@ -93,7 +93,7 @@
"audit-ci": "7.1.0",
"jest": "30.2.0",
"jest-junit": "16.0.0",
"npm-check-updates": "19.1.1",
"npm-check-updates": "19.1.2",
"nyc": "17.1.0",
"parse-strings-in-object": "1.6.0",
"pre-commit": "1.2.2",
Expand Down
14 changes: 14 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const { env } = require('node:process')

const TESTS_EXECUTION_TIMEOUT = parseInt(env.TESTS_EXECUTION_TIMEOUT, 10) || 1000 * 60 * 15

const EXIT_CODES = Object.freeze({
success: 0,
failure: 1,
timeout: 2
})

module.exports = {
EXIT_CODES,
TESTS_EXECUTION_TIMEOUT
}
29 changes: 26 additions & 3 deletions src/extras/slack-broadcast.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,17 @@ const millisecondsToTime = (milliseconds) => {
*/
const generateSlackBlocks = (progress, reportURL) => {
const slackBlocks = []
const failedTestCases = []
let totalAssertionsCount = 0
let totalPassedAssertionsCount = 0
let totalRequestsCount = 0
const failedTestCases = []

progress.test_cases.forEach(testCase => {
// console.log(fStr.yellow(testCase.name))
totalRequestsCount += testCase.requests.length
let testCaseAssertionsCount = 0
let testCasePassedAssertionsCount = 0

testCase.requests.forEach(req => {
const passedAssertionsCount = req.request.tests && req.request.tests.passedAssertionsCount ? req.request.tests.passedAssertionsCount : 0
const assertionsCount = req.request.tests && req.request.tests.assertions && req.request.tests.assertions.length ? req.request.tests.assertions.length : 0
Expand All @@ -61,6 +63,7 @@ const generateSlackBlocks = (progress, reportURL) => {
testCaseAssertionsCount += assertionsCount
testCasePassedAssertionsCount += passedAssertionsCount
})

if (testCaseAssertionsCount !== testCasePassedAssertionsCount) {
failedTestCases.push({
name: testCase.name,
Expand All @@ -83,14 +86,16 @@ const generateSlackBlocks = (progress, reportURL) => {
// totalAssertionsCount = totalPassedAssertionsCount
// failedTestCases.length = 0

const isPassed = (totalAssertionsCount === totalPassedAssertionsCount) && (totalPassedAssertionsCount > 0)

if (config.briefSummaryPrefix) {
const top5FailedTestCases = failedTestCases.sort((a, b) => b.failedAssertions - a.failedAssertions).slice(0, 5)
return [{
type: 'rich_text',
elements: [{
type: 'rich_text_section',
elements: [
{ type: 'text', text: `${totalAssertionsCount === totalPassedAssertionsCount ? '' : '⚠️'}${config.briefSummaryPrefix || ''} ` },
{ type: 'text', text: `${isPassed ? '✅' : (progress.isTimeout ? '' : '⚠️')}${config.briefSummaryPrefix || ''} ` },
reportURL ? { type: 'link', url: reportURL, text: config.reportName } : { type: 'text', text: config.reportName },
{ type: 'text', text: ' tests: ' },
{ type: 'text', text: String(progress.test_cases.length), style: { code: true } },
Expand Down Expand Up @@ -138,7 +143,7 @@ const generateSlackBlocks = (progress, reportURL) => {
summaryText += '>Runtime duration: *' + `${progress.runtimeInformation.runDurationMs} ms` + '*\n'

const additionalParams = {}
if (totalAssertionsCount === totalPassedAssertionsCount) {
if (isPassed) {
if (config.slackPassedImage) {
additionalParams.accessory = {
type: 'image',
Expand All @@ -155,6 +160,7 @@ const generateSlackBlocks = (progress, reportURL) => {
}
}
}

let extramSummaryText = ''
if (config.extraSummaryInformation) {
const extraSummaryInformationArr = config.extraSummaryInformation.split(',')
Expand All @@ -171,6 +177,7 @@ const generateSlackBlocks = (progress, reportURL) => {
},
...additionalParams
})

if (reportURL) {
slackBlocks.push({
type: 'section',
Expand All @@ -183,6 +190,7 @@ const generateSlackBlocks = (progress, reportURL) => {
slackBlocks.push({
type: 'divider'
})

return slackBlocks
}

Expand All @@ -208,6 +216,20 @@ const sendSlackNotification = async (progress, reportURL = 'http://localhost/')
}
}

/* istanbul ignore next */
const sendTimeoutSlackNotification = async (progress, reportURL = 'http://localhost/') => {
const text = 'Timeout Tests Report'
const blocks = generateSlackBlocks(progress, reportURL)

if (config.slackWebhookUrl) {
await sendWebhook(config.slackWebhookUrl, text, blocks)
}

if (config.slackWebhookUrlForFailed) {
await sendWebhook(config.slackWebhookUrlForFailed, text, blocks)
}
}

const sendWebhook = async (url, text, blocks) => {
const webhook = new IncomingWebhook(url)
try {
Expand All @@ -232,6 +254,7 @@ const needToNotifyFailed = (webhookUrl, progress) => {

module.exports = {
sendSlackNotification,
sendTimeoutSlackNotification,
sendWebhook,
needToNotifyFailed
}
33 changes: 33 additions & 0 deletions src/modes/outbound.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const slackBroadcast = require('../extras/slack-broadcast')
const releaseCd = require('../extras/release-cd')
const TemplateGenerator = require('../utils/templateGenerator')
const { TraceHeaderUtils } = require('@mojaloop/ml-testing-toolkit-shared-lib')
const { TESTS_EXECUTION_TIMEOUT } = require('../constants')

const totalProgress = {
totalTestCases: 0,
Expand Down Expand Up @@ -188,6 +189,7 @@ const sendTemplate = async (sessionId) => {
* @property {string} status
* @property {Object} totalResult
* @property {Object} saveReportStatus
* @property {Boolean} [isTimeout]
* @property {unknown} [otherFields] - see ml-testing-toolkit repo.
*/

Expand All @@ -212,6 +214,7 @@ const sendTemplate = async (sessionId) => {
*/
const handleIncomingProgress = async (progress) => {
const config = objectStore.get('config')

if (progress.status === 'FINISHED') {
let passed
try {
Expand Down Expand Up @@ -256,8 +259,38 @@ const handleIncomingProgress = async (progress) => {
}
}

// istanbul ignore next
const handleTimeout = async () => {
try {
console.log('Tests execution timed out....')
const config = objectStore.get('config')
const now = Date.now()

const timeoutReport = {
name: config.reportName || determineTemplateName(config.inputFiles.split(',')),
runtimeInformation: {
testReportId: `timeout-${now}`,
startedTime: new Date(now - TESTS_EXECUTION_TIMEOUT).toUTCString(),
completedTime: new Date(now).toUTCString(),
runDurationMs: TESTS_EXECUTION_TIMEOUT,
totalAssertions: totalProgress.totalAssertions,
totalPassedAssertions: totalProgress.passedAssertions
},
test_cases: [], // think if we need to reconstruct passed test cases
status: 'TERMINATED',
isTimeout: true
}
console.log(fStr.yellow(`⚠️ Summary (timeout): ${totalProgress.passedAssertions}/${totalProgress.totalAssertions} assertions passed`))

await slackBroadcast.sendTimeoutSlackNotification(timeoutReport)
} catch (err) {
console.log(fStr.red(`Error on handling tests timeout: ${err?.message}`))
}
}

module.exports = {
sendTemplate,
handleIncomingProgress,
handleTimeout,
determineTemplateName
}
17 changes: 9 additions & 8 deletions src/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@
* Georgi Logodazhki <georgi.logodazhki@modusbox.com> (Original Author)
--------------
******/
const fs = require('fs')

const fs = require('node:fs')
const _ = require('lodash')
const objectStore = require('./objectStore')
const { TraceHeaderUtils } = require('@mojaloop/ml-testing-toolkit-shared-lib')

const TESTS_EXECUTION_TIMEOUT = 1000 * 60 * 15 // 15min timout
const { EXIT_CODES, TESTS_EXECUTION_TIMEOUT } = require('./constants')
const objectStore = require('./objectStore')

const cli = (commanderOptions) => {
const configFile = {
Expand Down Expand Up @@ -87,10 +87,11 @@ const cli = (commanderOptions) => {
// Generate a session ID
const sessionId = TraceHeaderUtils.generateSessionId()
require('./utils/listeners').outbound(sessionId)
require('./modes/outbound').sendTemplate(sessionId)
setTimeout(() => {
console.log('Tests execution timed out....')
process.exit(1)
const { sendTemplate, handleTimeout } = require('./modes/outbound')
sendTemplate(sessionId)
setTimeout(async () => {
await handleTimeout()
process.exit(EXIT_CODES.timeout)
}, TESTS_EXECUTION_TIMEOUT)
} else {
console.log('error: required option \'-e, --environment-file <environmentFile>\' not specified')
Expand Down
2 changes: 2 additions & 0 deletions src/utils/report.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const report = async (data, reportType) => {
const config = objectStore.get('config')
let reportData
let reportFilename

switch (config.reportFormat) {
case 'none':
return returnInfo
Expand Down Expand Up @@ -86,6 +87,7 @@ const report = async (data, reportType) => {
console.log('reportFormat is not supported')
return
}

if (config.reportTarget) {
const reportTargetRe = /(.*):\/\/(.*)/g
const reportTargetArr = reportTargetRe.exec(config.reportTarget)
Expand Down
5 changes: 3 additions & 2 deletions test/unit/router.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

const spyExit = jest.spyOn(process, 'exit').mockImplementation(() => {})
const { cli } = require('../../src/router')
const { EXIT_CODES, TESTS_EXECUTION_TIMEOUT } = require('../../src/constants');

jest.mock('../../src/utils/listeners')
jest.mock('../../src/modes/outbound')
Expand Down Expand Up @@ -82,8 +83,8 @@ describe('Cli client', () => {
expect(() => {
cli(config)
}).not.toThrow()
jest.advanceTimersByTime(1000 * 60 * 15)
expect(spyExit).toHaveBeenCalledWith(1)
await jest.advanceTimersByTime(TESTS_EXECUTION_TIMEOUT)
expect(spyExit).toHaveBeenCalledWith(EXIT_CODES.timeout)
})
it('when mode is outbound and inputFile was not provided should not throw an error', async () => {
const config = {
Expand Down