Skip to content

Commit

Permalink
Feature/add iterations functionality (#113)
Browse files Browse the repository at this point in the history
* Initial implementation

* Added iteration cont to teplate_iterations

* Added report to iterations

* Added total runtime

* Added unit tests

* Bumped up the version and postponed the audits

* Removed an old comment

* Changed the versions in docker compose file
  • Loading branch information
vijayg10 authored Nov 17, 2020
1 parent d0e98ee commit f37b70b
Show file tree
Hide file tree
Showing 8 changed files with 16,176 additions and 16,034 deletions.
32,034 changes: 16,017 additions & 16,017 deletions audit-resolve.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: "3.7"

services:
mojaloop-testing-toolkit:
image: mojaloop/ml-testing-toolkit:v11.4.0
image: mojaloop/ml-testing-toolkit:v11.5.0
#image: mojaloop-testing-toolkit:local
#build:
# context: .
Expand All @@ -18,7 +18,7 @@ services:
- -c
- "npm start"
mojaloop-testing-toolkit-ui:
image: mojaloop/ml-testing-toolkit-ui:v11.4.0
image: mojaloop/ml-testing-toolkit-ui:v11.5.0
ports:
- "6060:6060"
environment:
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ml-testing-toolkit",
"description": "Testing Toolkit for Mojaloop implementations",
"version": "11.4.0",
"version": "11.5.0",
"license": "Apache-2.0",
"author": "Vijaya Kumar Guthi, ModusBox Inc.",
"contributors": [
Expand Down
25 changes: 25 additions & 0 deletions src/lib/api-routes/outbound.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,31 @@ router.post('/template/:traceID', [
}
})

router.post('/template_iterations/:traceID', [
check('name').notEmpty(),
check('iterationCount').notEmpty(),
check('test_cases').notEmpty()
], async (req, res, next) => {
try {
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() })
}
if (+req.query.iterationCount === 0) {
throw new Error('Iteration count is zero')
}
const traceID = req.params.traceID
const inputJson = JSON.parse(JSON.stringify(req.body))
// TODO: Change the following value to the dfspId based ont he login incase HOSTING_ENABLED
const dfspId = req.user ? req.user.dfspId : Config.getUserConfig().DEFAULT_USER_FSPID
outbound.OutboundSendLoop(inputJson, traceID, dfspId, req.query.iterationCount)

return res.status(200).json({ status: 'OK' })
} catch (err) {
next(err)
}
})

// Route to terminate the given execution
router.delete('/template/:traceID', async (req, res, next) => {
try {
Expand Down
108 changes: 95 additions & 13 deletions src/lib/test-outbound/outbound-initiator.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ const getTracing = (traceID, dfspId) => {
}

const OutboundSend = async (inputTemplate, traceID, dfspId) => {
const globalConfig = {
broadcastOutboundProgressEnabled: true,
scriptExecution: true,
testsExecution: true
}
const startedTimeStamp = new Date()
const tracing = getTracing(traceID, dfspId)

Expand All @@ -72,7 +77,7 @@ const OutboundSend = async (inputTemplate, traceID, dfspId) => {
}
try {
for (const i in inputTemplate.test_cases) {
await processTestCase(inputTemplate.test_cases[i], traceID, inputTemplate.inputValues, environmentVariables, dfspId)
await processTestCase(inputTemplate.test_cases[i], traceID, inputTemplate.inputValues, environmentVariables, dfspId, globalConfig)
}

const completedTimeStamp = new Date()
Expand Down Expand Up @@ -108,11 +113,75 @@ const OutboundSend = async (inputTemplate, traceID, dfspId) => {
}
}

const OutboundSendLoop = async (inputTemplate, traceID, dfspId, iterations) => {
const globalConfig = {
broadcastOutboundProgressEnabled: false,
scriptExecution: false,
testsExecution: true
}
const tracing = getTracing(traceID, dfspId)

const environmentVariables = {
items: Object.entries(inputTemplate.inputValues || {}).map((item) => { return { type: 'any', key: item[0], value: item[1] } })
}
try {
const totalStartedTimeStamp = new Date()
const totalReport = {
iterations: []
}
for (let itn = 0; itn < iterations; itn++) {
const startedTimeStamp = new Date()
// Deep copy the template
const tmpTemplate = JSON.parse(JSON.stringify(inputTemplate))
// Execute all the test cases in the template
for (const i in tmpTemplate.test_cases) {
await processTestCase(tmpTemplate.test_cases[i], traceID, tmpTemplate.inputValues, environmentVariables, dfspId, globalConfig)
}
const completedTimeStamp = new Date()
const runDurationMs = completedTimeStamp.getTime() - startedTimeStamp.getTime()
const runtimeInformation = {
iterationNumber: itn,
completedTimeISO: completedTimeStamp.toISOString(),
startedTime: startedTimeStamp.toUTCString(),
completedTime: completedTimeStamp.toUTCString(),
runDurationMs: runDurationMs,
totalAssertions: 0,
totalPassedAssertions: 0
}
// TODO: This can be optimized by storing only results into the iterations array
totalReport.iterations.push(generateFinalReport(tmpTemplate, runtimeInformation))
notificationEmitter.broadcastOutboundProgress({
status: 'ITERATION_PROGRESS',
outboundID: tracing.outboundID,
iterationStatus: runtimeInformation
}, tracing.sessionID)
}

const totalCompletedTimeStamp = new Date()
const totalRunDurationMs = totalCompletedTimeStamp.getTime() - totalStartedTimeStamp.getTime()
// Send the total result to client
if (tracing.outboundID) {
notificationEmitter.broadcastOutboundProgress({
status: 'ITERATIONS_FINISHED',
outboundID: tracing.outboundID,
totalRunDurationMs,
totalReport
}, tracing.sessionID)
}
} catch (err) {
notificationEmitter.broadcastOutboundProgress({
status: 'ITERATIONS_TERMINATED',
outboundID: tracing.outboundID,
errorMessage: err.message
}, tracing.sessionID)
}
}

const terminateOutbound = (traceID) => {
terminateTraceIds[traceID] = true
}

const processTestCase = async (testCase, traceID, inputValues, environmentVariables, dfspId) => {
const processTestCase = async (testCase, traceID, inputValues, environmentVariables, dfspId, globalConfig) => {
const tracing = getTracing(traceID)

// Load the requests array into an object by the request id to access a particular object faster
Expand Down Expand Up @@ -165,10 +234,15 @@ const processTestCase = async (testCase, traceID, inputValues, environmentVariab
const environment = {
data: {}
}
const contextObj = await context.generageContextObj(environmentVariables.items)
let contextObj = null
if (globalConfig.scriptExecution) {
contextObj = await context.generageContextObj(environmentVariables.items)
}
// Send http request
try {
await executePreRequestScript(convertedRequest, scriptsExecution, contextObj, environmentVariables)
if (globalConfig.scriptExecution) {
await executePreRequestScript(convertedRequest, scriptsExecution, contextObj, environmentVariables)
}

environment.data = environmentVariables.items.reduce((envObj, item) => { envObj[item.key] = item.value; return envObj }, {})

Expand All @@ -191,19 +265,20 @@ const processTestCase = async (testCase, traceID, inputValues, environmentVariab
await new Promise(resolve => setTimeout(resolve, request.delay))
}
const resp = await sendRequest(convertedRequest.url, convertedRequest.method, convertedRequest.path, convertedRequest.queryParams, convertedRequest.headers, convertedRequest.body, successCallbackUrl, errorCallbackUrl, convertedRequest.ignoreCallbacks, dfspId)

await setResponse(convertedRequest, resp, environment, environmentVariables, request, 'SUCCESS', tracing, testCase, scriptsExecution, contextObj)
await setResponse(convertedRequest, resp, environment, environmentVariables, request, 'SUCCESS', tracing, testCase, scriptsExecution, contextObj, globalConfig)
} catch (err) {
let resp
try {
resp = JSON.parse(err.message)
} catch (parsingErr) {
resp = err.message
}
await setResponse(convertedRequest, resp, environment, environmentVariables, request, 'ERROR', tracing, testCase, scriptsExecution, contextObj)
await setResponse(convertedRequest, resp, environment, environmentVariables, request, 'ERROR', tracing, testCase, scriptsExecution, contextObj, globalConfig)
} finally {
contextObj.ctx.dispose()
contextObj.ctx = null
if (contextObj) {
contextObj.ctx.dispose()
contextObj.ctx = null
}
}
}

Expand All @@ -212,7 +287,7 @@ const processTestCase = async (testCase, traceID, inputValues, environmentVariab
// Set a timeout if the response callback is not received in a particular time
}

const setResponse = async (convertedRequest, resp, environment, environmentVariables, request, status, tracing, testCase, scriptsExecution, contextObj) => {
const setResponse = async (convertedRequest, resp, environment, environmentVariables, request, status, tracing, testCase, scriptsExecution, contextObj, globalConfig) => {
// Get the requestsHistory and callbacksHistory from the objectStore
const requestsHistoryObj = objectStore.get('requestsHistory')
const callbacksHistoryObj = objectStore.get('callbacksHistory')
Expand All @@ -221,9 +296,15 @@ const setResponse = async (convertedRequest, resp, environment, environmentVaria
callbacksHistory: callbacksHistoryObj
}

await executePostRequestScript(convertedRequest, resp, scriptsExecution, contextObj, environmentVariables, backgroundData)
if (globalConfig.scriptExecution) {
await executePostRequestScript(convertedRequest, resp, scriptsExecution, contextObj, environmentVariables, backgroundData)
}
environment.data = environmentVariables.items.reduce((envObj, item) => { envObj[item.key] = item.value; return envObj }, {})
const testResult = await handleTests(convertedRequest, resp.syncResponse, resp.callback, environment.data, backgroundData)

let testResult = null
if (globalConfig.testsExecution) {
testResult = await handleTests(convertedRequest, resp.syncResponse, resp.callback, environment.data, backgroundData)
}
request.appended = {
status: status,
testResult,
Expand All @@ -234,7 +315,7 @@ const setResponse = async (convertedRequest, resp, environment, environmentVaria
curlRequest: resp.curlRequest
}
}
if (tracing.outboundID) {
if (tracing.outboundID && globalConfig.broadcastOutboundProgressEnabled) {
notificationEmitter.broadcastOutboundProgress({
outboundID: tracing.outboundID,
testCaseId: testCase.id,
Expand Down Expand Up @@ -630,6 +711,7 @@ const generateFinalReport = (inputTemplate, runtimeInformation) => {

module.exports = {
OutboundSend,
OutboundSendLoop,
terminateOutbound,
handleTests,
sendRequest,
Expand Down
18 changes: 18 additions & 0 deletions test/unit/lib/api-routes/config.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,24 @@ describe('API route /config', () => {
expect(res.body).toHaveProperty('runtime')
expect(res.body).toHaveProperty('stored')
})
it('Getting config with Hosting enabled', async () => {
Config.getUserConfig.mockReturnValueOnce({key: 'value'})
Config.getStoredUserConfig.mockResolvedValueOnce({})
Config.getSystemConfig.mockReturnValueOnce({HOSTING_ENABLED: true})
const res = await request(app).get(`/api/config/user`)
expect(res.statusCode).toEqual(200)
expect(res.body).toHaveProperty('runtime')
expect(res.body).toHaveProperty('stored')
})
it('Getting config with Hosting enabled and hub only mode', async () => {
Config.getUserConfig.mockReturnValueOnce({HUB_ONLY_MODE: true})
Config.getStoredUserConfig.mockResolvedValueOnce({})
Config.getSystemConfig.mockReturnValueOnce({HOSTING_ENABLED: true})
const res = await request(app).get(`/api/config/user`)
expect(res.statusCode).toEqual(200)
expect(res.body).toHaveProperty('runtime')
expect(res.body).toHaveProperty('stored')
})
it('Getting config throws an error', async () => {
Config.getUserConfig.mockReturnValueOnce({})
Config.getStoredUserConfig.mockRejectedValueOnce()
Expand Down
17 changes: 17 additions & 0 deletions test/unit/lib/api-routes/outbound.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,23 @@ describe('API route /api/outbound', () => {
const postResponse = await request(app).post(`/api/outbound/template/123`).send(properTemplateAsync)
expect(postResponse.statusCode).toEqual(200)
})
it('Send a proper template in loop without iteration count', async () => {
const res = await request(app).post(`/api/outbound/template_iterations/12`).send(properTemplateAsync)
expect(res.statusCode).toEqual(422)
})
it('Send a proper template in loop with iteration count zero', async () => {
const res = await request(app).post(`/api/outbound/template_iterations/12`).query({iterationCount: 0}).send(properTemplateAsync)
expect(res.statusCode).toEqual(500)
})
it('Send a proper template in loop with wrong template', async () => {
const {test_cases, ...wrongTemplate} = properTemplateAsync
const res = await request(app).post(`/api/outbound/template_iterations/12`).query({iterationCount: 1}).send(wrongTemplate)
expect(res.statusCode).toEqual(422)
})
it('Send a proper template in loop', async () => {
const res = await request(app).post(`/api/outbound/template_iterations/12`).query({iterationCount: 1}).send(properTemplateAsync)
expect(res.statusCode).toEqual(200)
})
})
describe('DELETE /api/outbound/template/:outboundID', () => {
it('Send request to delete template with outboundID 12', async () => {
Expand Down

0 comments on commit f37b70b

Please sign in to comment.