Skip to content

Commit 8a6dd8b

Browse files
authored
feat: enhance release report structure, implement brief summary option for Slack (#19)
1 parent 02515e7 commit 8a6dd8b

File tree

9 files changed

+676
-209
lines changed

9 files changed

+676
-209
lines changed

package-lock.json

Lines changed: 551 additions & 171 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,13 @@
6363
"snapshot": "npx standard-version --no-verify --skip.changelog --prerelease snapshot --releaseCommitMessageFormat 'chore(snapshot): {{currentTag}}'"
6464
},
6565
"dependencies": {
66-
"@mojaloop/central-services-logger": "11.5.5",
66+
"@mojaloop/central-services-logger": "11.6.2",
6767
"@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",
7070
"atob": "2.1.2",
7171
"aws-sdk": "2.1692.0",
72-
"axios": "1.7.9",
72+
"axios": "1.8.3",
7373
"cli-table3": "0.6.5",
7474
"commander": "13.1.0",
7575
"dotenv": "16.4.7",

src/client.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ program
4848
.option('--report-target <reportTarget>', 'default: "file://<file_name_genrated_by_backend>" --- supported targets: "file://path_to_file", "s3://<bucket_name>[/<file_path>]"')
4949
.option('--slack-webhook-url <slackWebhookUrl>', 'default: "Disabled" --- supported formats: "https://....."')
5050
.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"')
5152
.on('--help', () => { // Extra information on help message
5253
console.log('')
5354
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')

src/extras/release-cd.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,21 @@
2929
const axios = require('axios').default
3030
const config = require('rc')('release_cd', {})
3131

32-
module.exports = async function (name, result) {
32+
module.exports = async function (name, { runtimeInformation, test_cases: testCases = [] }) {
3333
if (!config.reportUrl) return
3434
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+
}
3647
}
3748
console.log(`Sending report to ${config.reportUrl}`, data)
3849
await axios({

src/extras/slack-broadcast.js

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -31,24 +31,38 @@ const objectStore = require('../objectStore')
3131

3232
const config = objectStore.get('config')
3333

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) => {
3542
const slackBlocks = []
3643
let totalAssertionsCount = 0
3744
let totalPassedAssertionsCount = 0
3845
let totalRequestsCount = 0
46+
const failedTestCases = []
3947
progress.test_cases.forEach(testCase => {
4048
// console.log(fStr.yellow(testCase.name))
4149
totalRequestsCount += testCase.requests.length
42-
// let testCaseAssertionsCount = 0
43-
// let testCasePassedAssertionsCount = 0
50+
let testCaseAssertionsCount = 0
51+
let testCasePassedAssertionsCount = 0
4452
testCase.requests.forEach(req => {
4553
const passedAssertionsCount = req.request.tests && req.request.tests.passedAssertionsCount ? req.request.tests.passedAssertionsCount : 0
4654
const assertionsCount = req.request.tests && req.request.tests.assertions && req.request.tests.assertions.length ? req.request.tests.assertions.length : 0
4755
totalAssertionsCount += assertionsCount
4856
totalPassedAssertionsCount += passedAssertionsCount
49-
// testCaseAssertionsCount += assertionsCount
50-
// testCasePassedAssertionsCount += passedAssertionsCount
57+
testCaseAssertionsCount += assertionsCount
58+
testCasePassedAssertionsCount += passedAssertionsCount
5159
})
60+
if (testCaseAssertionsCount !== testCasePassedAssertionsCount) {
61+
failedTestCases.push({
62+
name: testCase.name,
63+
failedAssertions: testCaseAssertionsCount - testCasePassedAssertionsCount
64+
})
65+
}
5266
// const passed = testCasePassedAssertionsCount === testCaseAssertionsCount
5367
// // TODO: make sure this list should not be more than 40 because we can add only max 50 blocks in a slack message
5468
// if(!passed) {
@@ -61,6 +75,40 @@ const generateSlackBlocks = (progress) => {
6175
// })
6276
// }
6377
})
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+
64112
let summaryText = ''
65113

66114
summaryText += '>Total assertions: *' + totalAssertionsCount + '*\n'
@@ -107,40 +155,32 @@ const generateSlackBlocks = (progress) => {
107155
},
108156
...additionalParams
109157
})
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+
})
110170
return slackBlocks
111171
}
112172

113-
const sendSlackNotification = async (progress, reportURL = null) => {
173+
const sendSlackNotification = async (progress, reportURL = 'http://localhost/') => {
114174
if (config.slackWebhookUrl) {
115175
const url = config.slackWebhookUrl
116176
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+
139179
try {
140180
// console.log(JSON.stringify(slackBlocks, null, 2))
141181
await webhook.send({
142182
text: 'Test Report',
143-
blocks: slackBlocks
183+
blocks
144184
})
145185
console.log('Slack notification sent.')
146186
} catch (err) {

src/modes/outbound.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ const handleIncomingProgress = async (progress) => {
200200
}
201201
}
202202
try {
203-
await releaseCd(config.reportName, progress.totalResult.runtimeInformation)
203+
await releaseCd(config.reportName, progress.totalResult)
204204
} catch (err) {
205205
console.error(err)
206206
}

src/router.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ const cli = (commanderOptions) => {
6767
slackFailedImage: configFile.slackFailedImage,
6868
baseURL: commanderOptions.baseUrl || configFile.baseURL,
6969
extraSummaryInformation: commanderOptions.extraSummaryInformation || configFile.extraSummaryInformation,
70+
briefSummaryPrefix: commanderOptions.briefSummaryPrefix || configFile.briefSummaryPrefix,
7071
labels: commanderOptions.labels || configFile.labels,
7172
batchSize: commanderOptions.batchSize || configFile.batchSize || parseInt(process.env.TESTCASES_BATCH_SIZE, 10)
7273
}

test/unit/extras/release-cd.test.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,41 @@ describe('Release CD', () => {
4242
describe('Post test results', () => {
4343
it('Posts test result to configured URL', async () => {
4444
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+
}
4669
config.reportUrl = 'http://example.com'
4770
await releaseCd(name, result)
4871
expect(axios.default).toHaveBeenCalledWith({
4972
method: 'post',
5073
url: 'http://example.com',
5174
data: {
52-
[`tests.${name}`]: result
75+
[`tests.${name}`]: {
76+
assertions: {
77+
'test-case.request-id.assertion-id': 'SUCCESS'
78+
}
79+
}
5380
}
5481
})
5582
})

test/unit/extras/slack-broadcast.test.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,13 @@ describe('Cli client', () => {
102102
text: expect.any(String),
103103
blocks: expect.any(Array)
104104
}))
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+
}))
105112
})
106113
it('When failed case, it should call slack send function', async () => {
107114
config.slackWebhookUrl = 'http://some_url'

0 commit comments

Comments
 (0)