@mojaloop/ml-testing-toolkit-client-lib 1.6.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [1.7.0](https://github.com/mojaloop/ml-testing-toolkit-client-lib/compare/v1.6.1...v1.7.0) (2025-03-13)
6
+
7
+
8
+ ### Features
9
+
10
+ * enhance release report structure, implement brief summary option for Slack ([#19](https://github.com/mojaloop/ml-testing-toolkit-client-lib/issues/19)) ([8a6dd8b](https://github.com/mojaloop/ml-testing-toolkit-client-lib/commit/8a6dd8b99182f2931fe3b1cbd14aec7a63b2e316))
11
+
5
12
  ### [1.6.1](https://github.com/mojaloop/ml-testing-toolkit-client-lib/compare/v1.6.0...v1.6.1) (2025-02-25)
6
13
 
7
14
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mojaloop/ml-testing-toolkit-client-lib",
3
3
  "description": "Testing Toolkit Client Library",
4
- "version": "1.6.1",
4
+ "version": "1.7.0",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Vijaya Kumar Guthi, ModusBox Inc. ",
7
7
  "contributors": [
@@ -63,13 +63,13 @@
63
63
  "snapshot": "npx standard-version --no-verify --skip.changelog --prerelease snapshot --releaseCommitMessageFormat 'chore(snapshot): {{currentTag}}'"
64
64
  },
65
65
  "dependencies": {
66
- "@mojaloop/central-services-logger": "11.5.5",
66
+ "@mojaloop/central-services-logger": "11.6.2",
67
67
  "@mojaloop/ml-testing-toolkit-shared-lib": "14.0.4",
68
- "@mojaloop/sdk-standard-components": "19.7.0",
69
- "@slack/webhook": "7.0.4",
68
+ "@mojaloop/sdk-standard-components": "19.10.3",
69
+ "@slack/webhook": "7.0.5",
70
70
  "atob": "2.1.2",
71
71
  "aws-sdk": "2.1692.0",
72
- "axios": "1.7.9",
72
+ "axios": "1.8.3",
73
73
  "cli-table3": "0.6.5",
74
74
  "commander": "13.1.0",
75
75
  "dotenv": "16.4.7",
package/src/client.js CHANGED
@@ -48,6 +48,7 @@ program
48
48
  .option('--report-target <reportTarget>', 'default: "file://<file_name_genrated_by_backend>" --- supported targets: "file://path_to_file", "s3://<bucket_name>[/<file_path>]"')
49
49
  .option('--slack-webhook-url <slackWebhookUrl>', 'default: "Disabled" --- supported formats: "https://....."')
50
50
  .option('--extra-summary-information <Comma separated values in the format key:value>', 'default: none --- example: "Testcase Name:something,Environment:Dev1"')
51
+ .option('--brief-summary-prefix <Prefix to use for a brief summary in Slack>', 'default: none --- example: "environment name, test name"')
51
52
  .on('--help', () => { // Extra information on help message
52
53
  console.log('')
53
54
  console.log(' *** If the option report-target is set to use AWS S3 service, the variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION) should be passed in environment')
@@ -29,10 +29,21 @@
29
29
  const axios = require('axios').default
30
30
  const config = require('rc')('release_cd', {})
31
31
 
32
- module.exports = async function (name, result) {
32
+ module.exports = async function (name, { runtimeInformation, test_cases: testCases = [] }) {
33
33
  if (!config.reportUrl) return
34
34
  const data = {
35
- [`tests.${name}`]: result
35
+ [`tests.${name}`]: {
36
+ ...runtimeInformation,
37
+ assertions: Object.fromEntries(testCases.map(
38
+ testCase => testCase?.requests?.map(
39
+ request => request?.request?.tests?.assertions
40
+ .filter(assertion => assertion?.assertionId ?? assertion.id)
41
+ .map(
42
+ assertion => [`${testCase.testCaseId ?? testCase.name}.${request.request.requestId ?? request.request.id}.${assertion?.assertionId ?? assertion.id}`, assertion?.resultStatus?.status]
43
+ )
44
+ )
45
+ ).flat(2))
46
+ }
36
47
  }
37
48
  console.log(`Sending report to ${config.reportUrl}`, data)
38
49
  await axios({
@@ -31,24 +31,38 @@ const objectStore = require('../objectStore')
31
31
 
32
32
  const config = objectStore.get('config')
33
33
 
34
- const generateSlackBlocks = (progress) => {
34
+ const millisecondsToTime = (milliseconds) => {
35
+ const seconds = Math.floor(milliseconds / 1000)
36
+ const minutes = Math.floor(seconds / 60)
37
+ const hours = Math.floor(minutes / 60)
38
+ return `${String(hours).padStart(2, '0')}:${String(minutes % 60).padStart(2, '0')}:${String(seconds % 60).padStart(2, '0')}`
39
+ }
40
+
41
+ const generateSlackBlocks = (progress, reportURL) => {
35
42
  const slackBlocks = []
36
43
  let totalAssertionsCount = 0
37
44
  let totalPassedAssertionsCount = 0
38
45
  let totalRequestsCount = 0
46
+ const failedTestCases = []
39
47
  progress.test_cases.forEach(testCase => {
40
48
  // console.log(fStr.yellow(testCase.name))
41
49
  totalRequestsCount += testCase.requests.length
42
- // let testCaseAssertionsCount = 0
43
- // let testCasePassedAssertionsCount = 0
50
+ let testCaseAssertionsCount = 0
51
+ let testCasePassedAssertionsCount = 0
44
52
  testCase.requests.forEach(req => {
45
53
  const passedAssertionsCount = req.request.tests && req.request.tests.passedAssertionsCount ? req.request.tests.passedAssertionsCount : 0
46
54
  const assertionsCount = req.request.tests && req.request.tests.assertions && req.request.tests.assertions.length ? req.request.tests.assertions.length : 0
47
55
  totalAssertionsCount += assertionsCount
48
56
  totalPassedAssertionsCount += passedAssertionsCount
49
- // testCaseAssertionsCount += assertionsCount
50
- // testCasePassedAssertionsCount += passedAssertionsCount
57
+ testCaseAssertionsCount += assertionsCount
58
+ testCasePassedAssertionsCount += passedAssertionsCount
51
59
  })
60
+ if (testCaseAssertionsCount !== testCasePassedAssertionsCount) {
61
+ failedTestCases.push({
62
+ name: testCase.name,
63
+ failedAssertions: testCaseAssertionsCount - testCasePassedAssertionsCount
64
+ })
65
+ }
52
66
  // const passed = testCasePassedAssertionsCount === testCaseAssertionsCount
53
67
  // // TODO: make sure this list should not be more than 40 because we can add only max 50 blocks in a slack message
54
68
  // if(!passed) {
@@ -61,6 +75,40 @@ const generateSlackBlocks = (progress) => {
61
75
  // })
62
76
  // }
63
77
  })
78
+
79
+ // totalAssertionsCount = totalPassedAssertionsCount
80
+ // failedTestCases.length = 0
81
+
82
+ if (config.briefSummaryPrefix) {
83
+ const top5FailedTestCases = failedTestCases.sort((a, b) => b.failedAssertions - a.failedAssertions).slice(0, 5)
84
+ return [{
85
+ type: 'context',
86
+ elements: [{
87
+ type: 'mrkdwn',
88
+ text: [
89
+ `${totalAssertionsCount === totalPassedAssertionsCount ? '🟢' : '🔴'}`,
90
+ reportURL ? `<${reportURL}|${config.briefSummaryPrefix}>` : `${config.briefSummaryPrefix}`,
91
+ `failed: \`${totalAssertionsCount - totalPassedAssertionsCount}/${totalAssertionsCount}`,
92
+ `(${(100 * ((totalAssertionsCount - totalPassedAssertionsCount) / totalAssertionsCount)).toFixed(2)}%)\`,`,
93
+ `requests: \`${totalRequestsCount}\`,`,
94
+ `tests: \`${progress.test_cases.length}\`,`,
95
+ `duration: \`${millisecondsToTime(progress.runtimeInformation.runDurationMs)}\``,
96
+ top5FailedTestCases.length > 0 && '\nTop 5 failed test cases:\n',
97
+ top5FailedTestCases.length > 0 && top5FailedTestCases.map(tc => `• ${tc.name}: \`${tc.failedAssertions}\``).join('\n')
98
+ ].filter(Boolean).join(' ')
99
+ }]
100
+ }]
101
+ }
102
+
103
+ slackBlocks.push({
104
+ type: 'header',
105
+ text: {
106
+ type: 'plain_text',
107
+ text: 'Testing Toolkit Report',
108
+ emoji: true
109
+ }
110
+ })
111
+
64
112
  let summaryText = ''
65
113
 
66
114
  summaryText += '>Total assertions: *' + totalAssertionsCount + '*\n'
@@ -107,40 +155,32 @@ const generateSlackBlocks = (progress) => {
107
155
  },
108
156
  ...additionalParams
109
157
  })
158
+ if (reportURL) {
159
+ slackBlocks.push({
160
+ type: 'section',
161
+ text: {
162
+ type: 'mrkdwn',
163
+ text: '<' + reportURL + '|View Report>'
164
+ }
165
+ })
166
+ }
167
+ slackBlocks.push({
168
+ type: 'divider'
169
+ })
110
170
  return slackBlocks
111
171
  }
112
172
 
113
- const sendSlackNotification = async (progress, reportURL = null) => {
173
+ const sendSlackNotification = async (progress, reportURL = 'http://localhost/') => {
114
174
  if (config.slackWebhookUrl) {
115
175
  const url = config.slackWebhookUrl
116
176
  const webhook = new IncomingWebhook(url)
117
- let slackBlocks = []
118
- slackBlocks.push({
119
- type: 'header',
120
- text: {
121
- type: 'plain_text',
122
- text: 'Testing Toolkit Report',
123
- emoji: true
124
- }
125
- })
126
- slackBlocks = slackBlocks.concat(generateSlackBlocks(progress))
127
- if (reportURL) {
128
- slackBlocks.push({
129
- type: 'section',
130
- text: {
131
- type: 'mrkdwn',
132
- text: '<' + reportURL + '|View Report>'
133
- }
134
- })
135
- }
136
- slackBlocks.push({
137
- type: 'divider'
138
- })
177
+ const blocks = generateSlackBlocks(progress, reportURL)
178
+
139
179
  try {
140
180
  // console.log(JSON.stringify(slackBlocks, null, 2))
141
181
  await webhook.send({
142
182
  text: 'Test Report',
143
- blocks: slackBlocks
183
+ blocks
144
184
  })
145
185
  console.log('Slack notification sent.')
146
186
  } catch (err) {
@@ -200,7 +200,7 @@ const handleIncomingProgress = async (progress) => {
200
200
  }
201
201
  }
202
202
  try {
203
- await releaseCd(config.reportName, progress.totalResult.runtimeInformation)
203
+ await releaseCd(config.reportName, progress.totalResult)
204
204
  } catch (err) {
205
205
  console.error(err)
206
206
  }
package/src/router.js CHANGED
@@ -67,6 +67,7 @@ const cli = (commanderOptions) => {
67
67
  slackFailedImage: configFile.slackFailedImage,
68
68
  baseURL: commanderOptions.baseUrl || configFile.baseURL,
69
69
  extraSummaryInformation: commanderOptions.extraSummaryInformation || configFile.extraSummaryInformation,
70
+ briefSummaryPrefix: commanderOptions.briefSummaryPrefix || configFile.briefSummaryPrefix,
70
71
  labels: commanderOptions.labels || configFile.labels,
71
72
  batchSize: commanderOptions.batchSize || configFile.batchSize || parseInt(process.env.TESTCASES_BATCH_SIZE, 10)
72
73
  }
@@ -42,14 +42,41 @@ describe('Release CD', () => {
42
42
  describe('Post test results', () => {
43
43
  it('Posts test result to configured URL', async () => {
44
44
  const name = 'test'
45
- const result = {}
45
+ const result = {
46
+ test_cases: [
47
+ {
48
+ name: 'test-case',
49
+ requests: [
50
+ {
51
+ request: {
52
+ id: 'request-id',
53
+ tests: {
54
+ assertions: [
55
+ {
56
+ id: 'assertion-id',
57
+ resultStatus: {
58
+ status: 'SUCCESS'
59
+ }
60
+ }
61
+ ]
62
+ }
63
+ }
64
+ }
65
+ ]
66
+ }
67
+ ]
68
+ }
46
69
  config.reportUrl = 'http://example.com'
47
70
  await releaseCd(name, result)
48
71
  expect(axios.default).toHaveBeenCalledWith({
49
72
  method: 'post',
50
73
  url: 'http://example.com',
51
74
  data: {
52
- [`tests.${name}`]: result
75
+ [`tests.${name}`]: {
76
+ assertions: {
77
+ 'test-case.request-id.assertion-id': 'SUCCESS'
78
+ }
79
+ }
53
80
  }
54
81
  })
55
82
  })
@@ -102,6 +102,13 @@ describe('Cli client', () => {
102
102
  text: expect.any(String),
103
103
  blocks: expect.any(Array)
104
104
  }))
105
+ SpySlackSend.mockResolvedValueOnce(null)
106
+ config.briefSummaryPrefix = 'brief'
107
+ await expect(slackBroadCast.sendSlackNotification(sampleProgress)).resolves.toBe(undefined)
108
+ expect(SpySlackSend).toHaveBeenCalledWith(expect.objectContaining({
109
+ text: expect.any(String),
110
+ blocks: expect.any(Array)
111
+ }))
105
112
  })
106
113
  it('When failed case, it should call slack send function', async () => {
107
114
  config.slackWebhookUrl = 'http://some_url'