ml-testing-toolkit 18.18.0 → 18.19.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/.grype.yaml CHANGED
@@ -1,28 +1,24 @@
1
1
  ignore:
2
+ # fast-xml-parser vulnerability
2
3
  - vulnerability: GHSA-37qj-frw5-hhjh
3
- reason: "fast-xml-parser 4.5.3→5.3.4"
4
+ # lodash vulnerabilities
4
5
  - vulnerability: GHSA-xxjr-mmjv-4gpg
5
- reason: "lodash/lodash-es 4.17.21→4.17.23"
6
+ # @isaacs/brace-expansion vulnerability
6
7
  - vulnerability: GHSA-7h2j-956f-4vf2
7
- reason: "@isaacs/brace-expansion 5.0.0→5.0.1"
8
+ # busybox vulnerabilities
8
9
  - vulnerability: CVE-2025-60876
9
- reason: "busybox 1.37.0-r30 (busybox-binsh, ssl_client)"
10
+ # glob vulnerabilities
10
11
  - vulnerability: GHSA-5j98-mcp5-4vw2
11
- include-aliases: true
12
- reason: "glob 10.4.5→10.5.0 and 11.0.3→11.1.0"
12
+ # tar vulnerabilities
13
13
  - vulnerability: GHSA-34x7-hfp2-rc4v
14
- reason: "tar 7.5.1→7.5.7"
15
- - vulnerability: GHSA-73rr-hh4g-fpgx
16
- reason: "diff 8.0.2→8.0.3"
17
14
  - vulnerability: GHSA-r6q2-hw4h-h46w
18
- reason: "tar 7.5.1→7.5.4"
19
- - vulnerability: GHSA-3966-f6p6-2qr9
20
- reason: "npm 11.6.2"
21
15
  - vulnerability: GHSA-8qq5-rm4j-mr97
22
- reason: "tar 7.5.1→7.5.3"
23
16
  - vulnerability: GHSA-29xp-372q-xqph
24
- reason: "tar 7.5.1→7.5.2"
25
-
17
+ # diff vulnerability
18
+ - vulnerability: GHSA-73rr-hh4g-fpgx
19
+ # npm vulnerability
20
+ - vulnerability: GHSA-3966-f6p6-2qr9
21
+ # Set output format defaults
26
22
  output:
27
23
  - "table"
28
24
  - "json"
package/CHANGELOG.md CHANGED
@@ -1,4 +1,11 @@
1
1
  # Changelog: [mojaloop/thirdparty-api-svc](https://github.com/mojaloop/thirdparty-api-svc)
2
+ ## [18.19.0](https://github.com/mojaloop/ml-testing-toolkit/compare/v18.18.0...v18.19.0) (2026-02-06)
3
+
4
+
5
+ ### Features
6
+
7
+ * revert performance-optimization ([#367](https://github.com/mojaloop/ml-testing-toolkit/issues/367)) ([5b7c6c5](https://github.com/mojaloop/ml-testing-toolkit/commit/5b7c6c52d8ffe949ca16fa8374bea3fbc08c1a0c)), closes [#366](https://github.com/mojaloop/ml-testing-toolkit/issues/366)
8
+
2
9
  ## [18.18.0](https://github.com/mojaloop/ml-testing-toolkit/compare/v18.17.3...v18.18.0) (2026-02-06)
3
10
 
4
11
 
@@ -159,6 +159,13 @@
159
159
  "expect(callback.headers['Content-Length']).to.not.equal('0')"
160
160
  ]
161
161
  },
162
+ {
163
+ "id": 4,
164
+ "description": "Callback FSP Destination equal to request FSP Source",
165
+ "exec": [
166
+ "expect(callback.headers['fspiop-destination']).to.equal('{$request.headers['FSPIOP-Source']}')"
167
+ ]
168
+ },
162
169
  {
163
170
  "id": 5,
164
171
  "description": "Callback body should contain conversionTerms",
@@ -204,8 +211,8 @@
204
211
  "operationPath": "/quotes",
205
212
  "method": "post",
206
213
  "headers": {
207
- "Accept": "{$inputs.acceptQuotes}",
208
- "Content-Type": "{$inputs.contentTypeQuotes}",
214
+ "Accept": "application/vnd.interoperability.quotes+json;version=1.0",
215
+ "Content-Type": "application/vnd.interoperability.quotes+json;version=1.0",
209
216
  "Date": "{$function.generic.curDate}",
210
217
  "FSPIOP-Source": "{$inputs.fromFspId}",
211
218
  "FSPIOP-Destination": "{$prev.1.callback.body.party.partyIdInfo.fspId}"
@@ -269,6 +276,13 @@
269
276
  "expect(callback.headers['Content-Length']).to.not.equal('0')"
270
277
  ]
271
278
  },
279
+ {
280
+ "id": 4,
281
+ "description": "Callback FSP Destination equal to request FSP Source",
282
+ "exec": [
283
+ "expect(callback.headers['fspiop-destination']).to.equal('{$request.headers['FSPIOP-Source']}')"
284
+ ]
285
+ },
272
286
  {
273
287
  "id": 5,
274
288
  "description": "Callback body should contain transferAmount",
@@ -334,8 +348,8 @@
334
348
  "path": "/fxTransfers",
335
349
  "method": "post",
336
350
  "headers": {
337
- "Accept": "application/vnd.interoperability.fxTransfers+json;version=2.0",
338
- "Content-Type": "application/vnd.interoperability.fxTransfers+json;version=2.0",
351
+ "Accept": "application/vnd.interoperability.fxTransfers+json;version=1.0",
352
+ "Content-Type": "application/vnd.interoperability.fxTransfers+json;version=1.0",
339
353
  "Date": "{$function.generic.curDate}",
340
354
  "FSPIOP-Source": "{$inputs.fromFspId}"
341
355
  },
@@ -378,6 +392,13 @@
378
392
  "exec": [
379
393
  "expect(callback.headers['Content-Length']).to.not.equal('0')"
380
394
  ]
395
+ },
396
+ {
397
+ "id": 4,
398
+ "description": "Callback FSP Destination equal to request FSP Source",
399
+ "exec": [
400
+ "expect(callback.headers['fspiop-destination']).to.equal('{$request.headers['FSPIOP-Source']}')"
401
+ ]
381
402
  }
382
403
  ]
383
404
  },
@@ -395,8 +416,8 @@
395
416
  "operationPath": "/transfers",
396
417
  "method": "post",
397
418
  "headers": {
398
- "Accept": "{$inputs.acceptTransfers}",
399
- "Content-Type": "{$inputs.contentTransfers}",
419
+ "Accept": "application/vnd.interoperability.transfers+json;version=1.0",
420
+ "Content-Type": "application/vnd.interoperability.transfers+json;version=1.0",
400
421
  "Date": "{$function.generic.curDate}",
401
422
  "FSPIOP-Source": "{$inputs.fromFspId}"
402
423
  },
@@ -435,6 +456,13 @@
435
456
  "expect(callback.headers['Content-Length']).to.not.equal('0')"
436
457
  ]
437
458
  },
459
+ {
460
+ "id": 4,
461
+ "description": "Callback FSP Destination equal to request FSP Source",
462
+ "exec": [
463
+ "expect(callback.headers['fspiop-destination']).to.equal('{$request.headers['FSPIOP-Source']}')"
464
+ ]
465
+ },
438
466
  {
439
467
  "id": 5,
440
468
  "description": "Callback transferState to be COMMITTED",
@@ -1,5 +1,26 @@
1
1
  {
2
2
  "name": "dfsp-p2p-tests",
3
+ "inputValues": {
4
+ "fromIdType": "MSISDN",
5
+ "fromIdValue": "44123456789",
6
+ "fromFirstName": "Firstname-Test",
7
+ "fromLastName": "Lastname-Test",
8
+ "fromDOB": "1984-01-01",
9
+ "note": "Test Payment",
10
+ "currency": "USD",
11
+ "amount": "100",
12
+ "homeTransactionId": "123ABC",
13
+ "fromFspId": "testingtoolkitdfsp",
14
+ "accept": "application/vnd.interoperability.parties+json;version=1.0",
15
+ "contentType": "application/vnd.interoperability.parties+json;version=1.0",
16
+ "toIdValue": "9876543210",
17
+ "toIdType": "MSISDN",
18
+ "toFspId": "userdfsp",
19
+ "acceptQuotes": "application/vnd.interoperability.quotes+json;version=1.0",
20
+ "contentTypeQuotes": "application/vnd.interoperability.quotes+json;version=1.0",
21
+ "acceptTransfers": "application/vnd.interoperability.transfers+json;version=1.0",
22
+ "contentTransfers": "application/vnd.interoperability.transfers+json;version=1.0"
23
+ },
3
24
  "test_cases": [
4
25
  {
5
26
  "id": 1,
@@ -100,8 +121,8 @@
100
121
  "operationPath": "/quotes",
101
122
  "method": "post",
102
123
  "headers": {
103
- "Accept": "{$inputs.acceptQuotes}",
104
- "Content-Type": "{$inputs.contentTypeQuotes}",
124
+ "Accept": "application/vnd.interoperability.quotes+json;version=1.0",
125
+ "Content-Type": "application/vnd.interoperability.quotes+json;version=1.0",
105
126
  "Date": "{$function.generic.curDate}",
106
127
  "FSPIOP-Source": "{$inputs.fromFspId}",
107
128
  "FSPIOP-Destination": "{$prev.1.callback.body.party.partyIdInfo.fspId}"
@@ -187,6 +208,13 @@
187
208
  "expect(callback.body.transferAmount.currency).to.equal('{$request.body.amount.currency}')"
188
209
  ]
189
210
  },
211
+ {
212
+ "id": 7,
213
+ "description": "Callback content-type to be quotes",
214
+ "exec": [
215
+ "expect(callback.headers['content-type']).to.equal('application/vnd.interoperability.quotes+json;version=1.0')"
216
+ ]
217
+ },
190
218
  {
191
219
  "id": 8,
192
220
  "description": "Request amountType to be SEND",
@@ -230,8 +258,8 @@
230
258
  "operationPath": "/transfers",
231
259
  "method": "post",
232
260
  "headers": {
233
- "Accept": "{$inputs.acceptTransfers}",
234
- "Content-Type": "{$inputs.contentTransfers}",
261
+ "Accept": "application/vnd.interoperability.transfers+json;version=1.0",
262
+ "Content-Type": "application/vnd.interoperability.transfers+json;version=1.0",
235
263
  "Date": "{$function.generic.curDate}",
236
264
  "FSPIOP-Source": "{$inputs.fromFspId}"
237
265
  },
@@ -284,6 +312,13 @@
284
312
  "expect(callback.body.transferState).to.equal('COMMITTED')"
285
313
  ]
286
314
  },
315
+ {
316
+ "id": 6,
317
+ "description": "Callback content-type to be transfers",
318
+ "exec": [
319
+ "expect(callback.headers['content-type']).to.equal('application/vnd.interoperability.transfers+json;version=1.0')"
320
+ ]
321
+ },
287
322
  {
288
323
  "id": 7,
289
324
  "description": "Request transferId same as quote request transferId",
@@ -9,15 +9,15 @@
9
9
  "HOST_SIMULATOR": "http://moja-simulator.local",
10
10
  "HOST_TRANSACTION_REQUESTS_SERVICE": "http://transaction-request-service.local",
11
11
  "HUB_OPERATOR_BEARER_TOKEN": "NOT_APPLICABLE",
12
- "accept": "application/vnd.interoperability.parties+json;version=2.0",
13
- "acceptQuotes": "application/vnd.interoperability.quotes+json;version=2.0",
14
- "acceptTransfers": "application/vnd.interoperability.transfers+json;version=2.0",
12
+ "accept": "application/vnd.interoperability.parties+json;version=1.0",
13
+ "acceptQuotes": "application/vnd.interoperability.quotes+json;version=1.0",
14
+ "acceptTransfers": "application/vnd.interoperability.transfers+json;version=1.0",
15
15
  "accountId": "6",
16
16
  "amount": "100",
17
17
  "condition": "HOr22-H3AfTDHrSkPjJtVPRdKouuMkDXTR4ejlQa8Ks",
18
- "contentTransfers": "application/vnd.interoperability.transfers+json;version=2.0",
19
- "contentType": "application/vnd.interoperability.parties+json;version=2.0",
20
- "contentTypeQuotes": "application/vnd.interoperability.quotes+json;version=2.0",
18
+ "contentTransfers": "application/vnd.interoperability.transfers+json;version=1.0",
19
+ "contentType": "application/vnd.interoperability.parties+json;version=1.0",
20
+ "contentTypeQuotes": "application/vnd.interoperability.quotes+json;version=1.0",
21
21
  "currency": "USD",
22
22
  "fromDOB": "1984-01-01",
23
23
  "fromFirstName": "Firstname-Test",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ml-testing-toolkit",
3
3
  "description": "Testing Toolkit for Mojaloop implementations",
4
- "version": "18.18.0",
4
+ "version": "18.19.0",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Vijaya Kumar Guthi, ModusBox Inc. ",
7
7
  "contributors": [
@@ -2665,12 +2665,12 @@
2665
2665
  "counterPartyFsp": "fxp_123",
2666
2666
  "amountType": "RECEIVE",
2667
2667
  "sourceAmount": {
2668
- "currency": "{$request.body.conversionTerms.sourceAmount.currency}",
2669
- "amount": "{$request.body.conversionTerms.sourceAmount.amount}"
2668
+ "currency": "USD",
2669
+ "amount": "105.23"
2670
2670
  },
2671
2671
  "targetAmount": {
2672
- "currency": "{$request.body.conversionTerms.sourceAmount.currency}",
2673
- "amount": "{$request.body.conversionTerms.sourceAmount.amount}"
2672
+ "currency": "EUR",
2673
+ "amount": "100"
2674
2674
  },
2675
2675
  "expiration": "2016-05-24T08:38:08.699-04:00",
2676
2676
  "charges": [
@@ -2690,7 +2690,7 @@
2690
2690
  }
2691
2691
  }
2692
2692
  },
2693
- "type": "FIXED_CALLBACK"
2693
+ "type": "MOCK_CALLBACK"
2694
2694
  },
2695
2695
  "type": "callback",
2696
2696
  "version": 1
package/src/index.js CHANGED
@@ -64,20 +64,4 @@ const init = async () => {
64
64
  RequestLogger.logMessage('info', 'Toolkit Initialization completed.', { notification: false, additionalData: welcomeMessage })
65
65
  }
66
66
 
67
- // Graceful shutdown handler
68
- const shutdown = async (signal) => {
69
- RequestLogger.logMessage('info', `${signal} received. Starting graceful shutdown...`, { notification: false })
70
-
71
- // Stop servers
72
- await apiServer.stopServer()
73
- await server.stop()
74
-
75
- RequestLogger.logMessage('info', 'Graceful shutdown completed', { notification: false })
76
- process.exit(0)
77
- }
78
-
79
- // Register shutdown handlers
80
- process.on('SIGTERM', () => shutdown('SIGTERM'))
81
- process.on('SIGINT', () => shutdown('SIGINT'))
82
-
83
67
  init()
@@ -40,13 +40,7 @@ const apiDefinitionsPath = 'spec_files/api_definitions/'
40
40
  const validateDefinition = async (apiFilePath) => {
41
41
  const newApi = new OpenApiBackend({
42
42
  definition: await Utils.checkUrl(path.join(apiFilePath)),
43
- customizeAjv: ajv => {
44
- addFormats(ajv)
45
- // Enable caching and optimization
46
- ajv.opts.cache = new Map()
47
- ajv.opts.serialize = false
48
- return ajv
49
- },
43
+ customizeAjv: ajv => addFormats(ajv),
50
44
  strict: true,
51
45
  quick: true
52
46
  })
@@ -80,26 +80,4 @@ router.put('/user', [
80
80
  }
81
81
  })
82
82
 
83
- // Get cache statistics
84
- router.get('/cache', async (req, res, next) => {
85
- try {
86
- const perfOptimizer = require('../performanceOptimizer')
87
- const stats = perfOptimizer.getCacheStats()
88
- res.status(200).json(stats)
89
- } catch (err) {
90
- res.status(500).json({ error: err && err.message })
91
- }
92
- })
93
-
94
- // Clear all caches
95
- router.post('/cache/clear', async (req, res, next) => {
96
- try {
97
- const perfOptimizer = require('../performanceOptimizer')
98
- perfOptimizer.clearAllCaches()
99
- res.status(200).json({ status: 'OK', message: 'All caches cleared' })
100
- } catch (err) {
101
- res.status(500).json({ error: err && err.message })
102
- }
103
- })
104
-
105
83
  module.exports = router
@@ -97,41 +97,6 @@ const startServer = port => {
97
97
  }
98
98
 
99
99
  const stopServer = port => {
100
- // Clean up performance caches and context pools
101
- try {
102
- const perfOptimizer = require('./performanceOptimizer')
103
- perfOptimizer.clearAllCaches()
104
-
105
- // Clean up context pools
106
- const postmanContext = require('./scripting-engines/postman-sandbox')
107
- if (postmanContext.cleanupContextPool) {
108
- postmanContext.cleanupContextPool()
109
- }
110
-
111
- const javascriptContext = require('./scripting-engines/vm-javascript-sandbox')
112
- if (javascriptContext.cleanupContextPool) {
113
- javascriptContext.cleanupContextPool()
114
- }
115
-
116
- // Stop interval timers
117
- const arrayStore = require('./arrayStore')
118
- if (arrayStore.stopArrayStore) {
119
- arrayStore.stopArrayStore()
120
- }
121
-
122
- const objectStore = require('./objectStore')
123
- if (objectStore.stopObjectStore) {
124
- objectStore.stopObjectStore()
125
- }
126
-
127
- const httpAgentStore = require('./httpAgentStore')
128
- if (httpAgentStore.stop) {
129
- httpAgentStore.stop()
130
- }
131
- } catch (err) {
132
- customLogger.logMessage('warn', 'Error during cleanup', { additionalData: err.message })
133
- }
134
-
135
100
  http.close()
136
101
  customLogger.logMessage('info', 'API Server stopped', { notification: false })
137
102
  }
@@ -88,27 +88,14 @@ const clearOldObjects = () => {
88
88
  clear('callbacksHistory', interval)
89
89
  }
90
90
 
91
- let cleanupIntervalId = null
92
-
93
91
  const initArrayStore = () => {
94
- if (cleanupIntervalId) {
95
- clearInterval(cleanupIntervalId)
96
- }
97
- cleanupIntervalId = setInterval(clearOldObjects, 1000)
98
- }
99
-
100
- const stopArrayStore = () => {
101
- if (cleanupIntervalId) {
102
- clearInterval(cleanupIntervalId)
103
- cleanupIntervalId = null
104
- }
92
+ setInterval(clearOldObjects, 1000)
105
93
  }
106
94
 
107
95
  module.exports = {
108
96
  get,
109
97
  reset,
110
98
  initArrayStore,
111
- stopArrayStore,
112
99
  push,
113
100
  clear
114
101
  }
@@ -122,34 +122,14 @@ const _clearAgents = (unUsedAgentsExpiryMs) => {
122
122
  _clear(httpAgentStore, unUsedAgentsExpiryMs)
123
123
  }
124
124
 
125
- let cleanupIntervalId = null
126
-
127
125
  const init = () => {
128
126
  const timerInterval = (Config.getSystemConfig().HTTP_CLIENT && Config.getSystemConfig().HTTP_CLIENT.UNUSED_AGENTS_CHECK_TIMER_MS !== undefined) ? Config.getSystemConfig().HTTP_CLIENT.UNUSED_AGENTS_CHECK_TIMER_MS : (5 * 60 * 1000) // Check for the cleanup every 5min
129
127
  const unUsedAgentsExpiryMs = (Config.getSystemConfig().HTTP_CLIENT && Config.getSystemConfig().HTTP_CLIENT.UNUSED_AGENTS_EXPIRY_MS !== undefined) ? Config.getSystemConfig().HTTP_CLIENT.UNUSED_AGENTS_EXPIRY_MS : (30 * 60 * 1000) // Clear http agents not being used for more this time
130
- if (cleanupIntervalId) {
131
- clearInterval(cleanupIntervalId)
132
- }
133
- cleanupIntervalId = setInterval(_clearAgents, timerInterval, unUsedAgentsExpiryMs)
134
- }
135
-
136
- const stop = () => {
137
- if (cleanupIntervalId) {
138
- clearInterval(cleanupIntervalId)
139
- cleanupIntervalId = null
140
- }
141
- // Destroy all agents
142
- Object.values(httpsAgentStore).forEach(agentInfo => {
143
- if (agentInfo.agent) agentInfo.agent.destroy()
144
- })
145
- Object.values(httpAgentStore).forEach(agentInfo => {
146
- if (agentInfo.agent) agentInfo.agent.destroy()
147
- })
128
+ setInterval(_clearAgents, timerInterval, unUsedAgentsExpiryMs)
148
129
  }
149
130
 
150
131
  module.exports = {
151
132
  getHttpAgent,
152
133
  getHttpsAgent,
153
- init,
154
- stop
134
+ init
155
135
  }
@@ -142,23 +142,23 @@ const handleTransferIlp = (context, response) => {
142
142
  }
143
143
  }
144
144
 
145
- customLogger.logMessage('debug', 'Generated callback body', { additionalData: { context, response }, notification: false })
145
+ customLogger.logMessage('debug', 'Generated callback body', { additionalData: { context, response } })
146
146
  if (context.request.method === 'get' && response.method === 'put' && pathMatch.test(response.path)) {
147
147
  const transferId = response.path.match(pathMatch)[1]
148
148
  // Use objectStore to get the stored transfer
149
149
  const storedTransfer = objectStore.get('storedTransfers', transferId)
150
- customLogger.logMessage('debug', 'Stored transfer fetched for fulfilment', { additionalData: { transferId }, notification: false })
150
+ customLogger.logMessage('debug', 'Stored transfer fetched for fulfilment', { additionalData: { transferId } })
151
151
  if (storedTransfer) {
152
152
  if (storedTransfer.data?.request?.ilpPacket) {
153
- customLogger.logMessage('debug', 'Stored transfer has ilpPacket. Generating fulfilment.', { additionalData: { transferId, ilpPacket: storedTransfer.data.request.ilpPacket }, notification: false })
153
+ customLogger.logMessage('debug', 'Stored transfer has ilpPacket. Generating fulfilment.', { additionalData: { transferId, ilpPacket: storedTransfer.data.request.ilpPacket } })
154
154
  const generatedFulfilment = ilpObj.calculateFulfil(storedTransfer.data.request.ilpPacket).replace('"', '')
155
155
  response.body.fulfilment = generatedFulfilment
156
156
  } else if (storedTransfer.data?.request?.CdtTrfTxInf?.VrfctnOfTerms?.IlpV4PrepPacket) {
157
- customLogger.logMessage('debug', 'Stored transfer has IlpV4PrepPacket. Generating fulfilment.', { additionalData: { transferId }, notification: false })
157
+ customLogger.logMessage('debug', 'Stored transfer has IlpV4PrepPacket. Generating fulfilment.', { additionalData: { transferId } })
158
158
  const generatedFulfilment = ilpV4Obj.calculateFulfil(storedTransfer.data.request.CdtTrfTxInf.VrfctnOfTerms.IlpV4PrepPacket).replace('"', '')
159
159
  response.body.TxInfAndSts.ExctnConf = generatedFulfilment
160
160
  } else {
161
- customLogger.logMessage('warn', 'No ILP packet or IlpV4PrepPacket found in stored transfer request', { additionalData: { transferId }, notification: false })
161
+ customLogger.logMessage('warn', 'No ILP packet or IlpV4PrepPacket found in stored transfer request', { additionalData: { transferId } })
162
162
  }
163
163
  // Remove the stored transfer from objectStore regardless of success or failure
164
164
  objectStore.deleteObject('storedTransfers', transferId)
@@ -67,13 +67,7 @@ module.exports.initilizeMockHandler = async () => {
67
67
  apis = await Promise.all(apiDefinitions.map(async item => {
68
68
  const tempObj = new OpenApiBackend({
69
69
  definition: await utils.checkUrl(path.join(item.specFile)),
70
- customizeAjv: ajv => {
71
- addFormats(ajv)
72
- // Enable caching and optimization to avoid repeated schema compilation
73
- ajv.opts.cache = new Map()
74
- ajv.opts.serialize = false
75
- return ajv
76
- },
70
+ customizeAjv: ajv => addFormats(ajv),
77
71
  strict: true,
78
72
  quick: true,
79
73
  handlers: {
@@ -32,13 +32,6 @@ const SocketServer = require('./socket-server')
32
32
  const broadcast = (log, sessionID = null, type) => {
33
33
  const io = SocketServer.getIO()
34
34
  if (io) {
35
- // Performance optimization: Only broadcast if clients are connected
36
- // This prevents expensive encoding operations when no one is listening
37
- const hasClients = io.engine?.clientsCount > 0
38
- if (!hasClients) {
39
- return
40
- }
41
-
42
35
  io.emit(type, log)
43
36
  if (sessionID) {
44
37
  io.emit(`${type}/` + sessionID, log)
@@ -121,30 +121,17 @@ const clearOldObjects = () => {
121
121
  clear('storedTransfers', interval)
122
122
  }
123
123
 
124
- let cleanupIntervalId = null
125
-
126
124
  const initObjectStore = (initConfig = null) => {
127
125
  if (initConfig) {
128
126
  _.merge(storedObject.data, initConfig)
129
127
  }
130
- if (cleanupIntervalId) {
131
- clearInterval(cleanupIntervalId)
132
- }
133
- cleanupIntervalId = setInterval(clearOldObjects, 1000)
134
- }
135
-
136
- const stopObjectStore = () => {
137
- if (cleanupIntervalId) {
138
- clearInterval(cleanupIntervalId)
139
- cleanupIntervalId = null
140
- }
128
+ setInterval(clearOldObjects, 1000)
141
129
  }
142
130
 
143
131
  module.exports = {
144
132
  set,
145
133
  get,
146
134
  initObjectStore,
147
- stopObjectStore,
148
135
  init,
149
136
  push,
150
137
  clear,
@@ -121,8 +121,7 @@ const logResponse = async (request, user) => {
121
121
  const logMessage = (verbosity, message, externalData = {}) => {
122
122
  const data = {
123
123
  additionalData: externalData.additionalData,
124
- // Performance optimization: Don't broadcast debug logs by default (high volume, low priority)
125
- notification: typeof externalData.notification !== 'undefined' ? externalData.notification : (verbosity !== 'debug'),
124
+ notification: typeof externalData.notification !== 'undefined' ? externalData.notification : true,
126
125
  messageType: externalData.messageType || 'generic',
127
126
  request: externalData.request || null,
128
127
  user: externalData.user
@@ -33,15 +33,10 @@ const axios = require('axios').default
33
33
  const customLogger = require('../requestLogger')
34
34
  const UniqueIdGenerator = require('../../lib/uniqueIdGenerator')
35
35
 
36
- // Context pool for reusing sandbox contexts
37
- const CONTEXT_POOL_SIZE = 5
38
- const contextPool = []
39
- let contextPoolInitialized = false
40
-
41
36
  // const createContextAsync = util.promisify(Sandbox.createContext)
42
- const createContextAsync = (options) => {
37
+ const createContextAsync = () => {
43
38
  return new Promise((resolve, reject) => {
44
- Sandbox.createContext(options || {}, function (err, ctx) {
39
+ Sandbox.createContext(function (err, ctx) {
45
40
  if (err) {
46
41
  reject(err)
47
42
  return console.error(err)
@@ -51,72 +46,13 @@ const createContextAsync = (options) => {
51
46
  })
52
47
  }
53
48
 
54
- // Initialize context pool on first use
55
- const initializeContextPool = async () => {
56
- if (contextPoolInitialized) return
57
- contextPoolInitialized = true
58
-
59
- customLogger.logMessage('info', `Initializing sandbox context pool with ${CONTEXT_POOL_SIZE} contexts`, { notification: false })
60
- const promises = []
61
- for (let i = 0; i < CONTEXT_POOL_SIZE; i++) {
62
- promises.push(createContextAsync())
63
- }
64
- const contexts = await Promise.all(promises)
65
-
66
- contexts.forEach(ctx => {
67
- ctx.executeAsync = util.promisify(ctx.execute)
68
- ctx.on('error', function (cursor, err) {
69
- // log the error in postman sandbox
70
- console.log(cursor, err)
71
- })
72
- contextPool.push({ ctx, inUse: false })
73
- })
74
- customLogger.logMessage('info', 'Sandbox context pool initialized', { notification: false })
75
- }
76
-
77
- // Get context from pool or create new one
78
- const acquireContext = async () => {
79
- await initializeContextPool()
80
-
81
- // Try to find an available context in the pool
82
- const available = contextPool.find(item => !item.inUse)
83
- if (available) {
84
- available.inUse = true
85
- return available.ctx
86
- }
87
-
88
- // If pool is exhausted, create a new context (temporary)
89
- customLogger.logMessage('debug', 'Context pool exhausted, creating temporary context', { notification: false })
90
- const ctx = await createContextAsync()
49
+ const generateContextObj = async (environment = {}) => {
50
+ const ctx = await createContextAsync({ timeout: 30000 })
91
51
  ctx.executeAsync = util.promisify(ctx.execute)
92
52
  ctx.on('error', function (cursor, err) {
53
+ // log the error in postman sandbox
93
54
  console.log(cursor, err)
94
55
  })
95
- return ctx
96
- }
97
-
98
- // Release context back to pool
99
- const releaseContext = (ctx) => {
100
- const poolItem = contextPool.find(item => item.ctx === ctx)
101
- if (poolItem) {
102
- // Context is being returned to pool
103
- // executeAsync already cleans up execution-specific listeners in its finally block
104
- // so we don't need to do additional cleanup here
105
- poolItem.inUse = false
106
- } else {
107
- // This was a temporary context (not in pool), dispose it
108
- try {
109
- if (ctx && typeof ctx.dispose === 'function') {
110
- ctx.dispose()
111
- }
112
- } catch (err) {
113
- customLogger.logMessage('warn', 'Failed to dispose temporary context', { additionalData: err.message })
114
- }
115
- }
116
- }
117
-
118
- const generateContextObj = async (environment = {}) => {
119
- const ctx = await acquireContext()
120
56
  const transformerObj = {
121
57
  transformer: null,
122
58
  transformerName: null,
@@ -125,37 +61,11 @@ const generateContextObj = async (environment = {}) => {
125
61
  const contextObj = {
126
62
  ctx,
127
63
  environment,
128
- transformerObj,
129
- _release: () => releaseContext(ctx),
130
- _isPooled: contextPool.some(item => item.ctx === ctx)
64
+ transformerObj
131
65
  }
132
66
  return contextObj
133
67
  }
134
68
 
135
- // Cleanup function for graceful shutdown
136
- const cleanupContextPool = () => {
137
- customLogger.logMessage('info', 'Cleaning up sandbox context pool', { notification: false })
138
- contextPool.forEach(({ ctx }) => {
139
- try {
140
- ctx.removeAllListeners()
141
- ctx.dispose()
142
- } catch (err) {
143
- customLogger.logMessage('warn', 'Failed to dispose context', { additionalData: err.message })
144
- }
145
- })
146
- contextPool.length = 0
147
- contextPoolInitialized = false
148
- customLogger.logMessage('info', 'Context pool cleanup complete', { notification: false })
149
- }
150
-
151
- // Register cleanup with performance optimizer
152
- try {
153
- const perfOptimizer = require('../performanceOptimizer')
154
- perfOptimizer.registerCache(cleanupContextPool, 'Sandbox Context Pool')
155
- } catch (err) {
156
- // Performance optimizer not available
157
- }
158
-
159
69
  const executeAsync = async (script, data, contextObj) => {
160
70
  let consoleLog = []
161
71
  const uniqueId = UniqueIdGenerator.generateUniqueId()
@@ -166,12 +76,9 @@ const executeAsync = async (script, data, contextObj) => {
166
76
  data.context.ctx = contextObj.ctx
167
77
  data.context.environment = Object.entries(contextObj.environment || {}).map((item) => { return { type: 'any', key: item[0], value: item[1] } })
168
78
 
169
- // Define event handlers that we'll remove later
170
- const consoleHandler = function () {
79
+ contextObj.ctx.on('console', function () {
171
80
  consoleLog.push(Array.from(arguments))
172
- }
173
-
174
- contextObj.ctx.on('console', consoleHandler)
81
+ })
175
82
 
176
83
  contextObj.ctx.on(`execution.request.${data.id}`, async (cursor, id, requestId, req) => {
177
84
  const host = `${req.url.protocol}://${req.url.host.join('.')}${req.url.port ? ':' + req.url.port : ''}/`
@@ -220,16 +127,6 @@ const executeAsync = async (script, data, contextObj) => {
220
127
  contextObj.environment = resp.environment.values.reduce((envObj, item) => { envObj[item.key] = item.value; return envObj }, {})
221
128
  } catch (err) {
222
129
  // consoleLog.push([{execution: 0}, 'executionError', err.message])
223
- } finally {
224
- // Clean up event listeners to prevent memory leaks
225
- try {
226
- contextObj.ctx.removeListener('console', consoleHandler)
227
- contextObj.ctx.removeAllListeners(`execution.request.${data.id}`)
228
- contextObj.ctx.removeAllListeners(`execution.response.${data.id}`)
229
- contextObj.ctx.removeAllListeners(`execution.error.${data.id}`)
230
- } catch (cleanupErr) {
231
- // Ignore cleanup errors
232
- }
233
130
  }
234
131
  const result = {
235
132
  consoleLog,
@@ -241,7 +138,5 @@ const executeAsync = async (script, data, contextObj) => {
241
138
 
242
139
  module.exports = {
243
140
  generateContextObj,
244
- executeAsync,
245
- releaseContext,
246
- cleanupContextPool
141
+ executeAsync
247
142
  }
@@ -40,109 +40,6 @@ const UniqueIdGenerator = require('../../lib/uniqueIdGenerator')
40
40
  const customLogger = require('../requestLogger')
41
41
  const { getHeader, urlToPath } = require('../utils')
42
42
 
43
- const CONTEXT_POOL_SIZE = 5
44
- const contextPool = []
45
-
46
- // Initialize the context pool at startup
47
- const initializeContextPool = async () => {
48
- if (contextPool.length === 0) {
49
- for (let i = 0; i < CONTEXT_POOL_SIZE; i++) {
50
- const ctx = await generateContextObj()
51
- ctx._inUse = false
52
- contextPool.push(ctx)
53
- }
54
- }
55
- }
56
-
57
- // Acquire a context from the pool
58
- const acquireContext = async (environmentObj = {}) => {
59
- // Initialize pool on first use
60
- if (contextPool.length === 0) {
61
- await initializeContextPool()
62
- }
63
-
64
- // Find available context
65
- const availableContext = contextPool.find(ctx => !ctx._inUse)
66
-
67
- if (availableContext) {
68
- availableContext._inUse = true
69
- // Update environment for this execution
70
- availableContext.environment = { ...environmentObj }
71
- return availableContext
72
- }
73
-
74
- // If no available context, create temporary one
75
- const tempContext = await generateContextObj(environmentObj)
76
- tempContext._temporary = true
77
- tempContext._inUse = true
78
- return tempContext
79
- }
80
-
81
- // Release a context back to the pool
82
- const releaseContext = (contextObj) => {
83
- if (!contextObj) return
84
-
85
- if (contextObj._temporary) {
86
- // Dispose temporary context
87
- try {
88
- if (contextObj.websocket && contextObj.websocket.destroy) {
89
- contextObj.websocket.destroy()
90
- }
91
- if (contextObj.inboundEvent && contextObj.inboundEvent.destroy) {
92
- // Destroy all event listeners
93
- Object.keys(contextObj.inboundEvent.eventListeners || {}).forEach(clientName => {
94
- contextObj.inboundEvent.destroy(clientName)
95
- })
96
- // Remove all listeners from emitter
97
- if (contextObj.inboundEvent.emitter) {
98
- contextObj.inboundEvent.emitter.removeAllListeners('newInbound')
99
- }
100
- }
101
- } catch (err) {
102
- customLogger.logMessage('warn', 'Failed to cleanup temporary context', { additionalData: err.message })
103
- }
104
- return
105
- }
106
-
107
- // Clean up and reset context state
108
- clearConsole(contextObj.consoleOutObj)
109
- contextObj.requestVariables = {}
110
- contextObj.environment = {}
111
-
112
- // Note: WebSocketClientManager and InboundEventListener manage their own
113
- // event listeners internally, no need to clean them up here
114
-
115
- // Reset transformer
116
- contextObj.transformerObj.transformer = null
117
- contextObj.transformerObj.transformerName = null
118
- contextObj.transformerObj.options = {}
119
-
120
- // Mark as available
121
- contextObj._inUse = false
122
- }
123
-
124
- // Clean up all contexts in the pool
125
- const cleanupContextPool = () => {
126
- contextPool.forEach(ctx => {
127
- try {
128
- if (ctx.websocket && ctx.websocket.destroy) ctx.websocket.destroy()
129
- if (ctx.inboundEvent && ctx.inboundEvent.destroy) {
130
- // Destroy all event listeners
131
- Object.keys(ctx.inboundEvent.eventListeners || {}).forEach(clientName => {
132
- ctx.inboundEvent.destroy(clientName)
133
- })
134
- // Remove all listeners from emitter
135
- if (ctx.inboundEvent.emitter) {
136
- ctx.inboundEvent.emitter.removeAllListeners('newInbound')
137
- }
138
- }
139
- } catch (err) {
140
- customLogger.logMessage('warn', 'Failed to cleanup context', { additionalData: err.message })
141
- }
142
- })
143
- contextPool.length = 0
144
- }
145
-
146
43
  const registerAxiosRequestInterceptor = (userConfig, axios, transformerObj) => {
147
44
  // istanbul ignore next
148
45
  axios.interceptors.request.use(async config => {
@@ -246,7 +143,6 @@ const customWrapperFn = (requestVariables, consoleFn) => {
246
143
  consoleFn.log(`Setting transformer '${transformerName}' if exists...`)
247
144
  if (!transformerName) {
248
145
  requestVariables.TRANSFORM = undefined
249
- return
250
146
  }
251
147
  requestVariables.TRANSFORM = {
252
148
  transformerName,
@@ -368,14 +264,7 @@ const executeAsync = async (script, data, contextObj) => {
368
264
  consoleLog.push([{ execution: 0 }, 'log', ...contextObj.consoleOutObj.stdOut[i]])
369
265
  }
370
266
  consoleLog.push([{ execution: 0 }, 'executionError', err.toString()])
371
- } finally {
372
- // Clean up context-specific properties after execution
373
- delete contextObj.request
374
- delete contextObj.response
375
- delete contextObj.callback
376
- delete contextObj.collectionVariables
377
267
  }
378
-
379
268
  const result = {
380
269
  consoleLog,
381
270
  environment: { ...contextObj.environment }
@@ -401,8 +290,5 @@ async function _runScript (code, context = {}, options = {}) {
401
290
  module.exports = {
402
291
  registerAxiosRequestInterceptor,
403
292
  generateContextObj,
404
- acquireContext,
405
- releaseContext,
406
- executeAsync,
407
- cleanupContextPool
293
+ executeAsync
408
294
  }
@@ -330,15 +330,7 @@ const processTestCase = async (
330
330
  if (convertedRequest.scriptingEngine && convertedRequest.scriptingEngine === 'javascript') {
331
331
  context = javascriptContext
332
332
  }
333
- // Use context pooling for both engines
334
- if (context.acquireContext) {
335
- contextObj = await context.acquireContext(variableData.environment)
336
- // Add release method
337
- contextObj._release = () => context.releaseContext(contextObj)
338
- } else {
339
- // Fallback for engines without pooling support
340
- contextObj = await context.generateContextObj(variableData.environment)
341
- }
333
+ contextObj = await context.generateContextObj(variableData.environment)
342
334
  }
343
335
 
344
336
  // Get transformer if specified
@@ -436,9 +428,9 @@ const processTestCase = async (
436
428
  convertedRequest.retry === retries
437
429
  )
438
430
  } finally {
439
- if (contextObj && contextObj._release) {
440
- // Release context back to pool instead of disposing
441
- contextObj._release()
431
+ if (contextObj) {
432
+ contextObj.ctx.dispose()
433
+ contextObj.ctx = null
442
434
  }
443
435
  if (request.appended?.assertionResults?.isFailed) {
444
436
  if (convertedRequest.retry < retries) {
@@ -1,102 +0,0 @@
1
- /*****
2
- License
3
- --------------
4
- Copyright © 2020-2025 Mojaloop Foundation
5
- The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at
6
-
7
- http://www.apache.org/licenses/LICENSE-2.0
8
-
9
- Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
10
-
11
- Contributors
12
- --------------
13
- This is the official list of the Mojaloop project contributors for this file.
14
- Names of the original copyright holders (individuals or organizations)
15
- should be listed with a '*' in the first column. People who have
16
- contributed from an organization can be listed under the organization
17
- that actually holds the copyright for their contributions (see the
18
- Mojaloop Foundation for an example). Those individuals should have
19
- their names indented and be marked with a '-'. Email address can be added
20
- optionally within square brackets <email>.
21
-
22
- * Mojaloop Foundation
23
- - Name Surname <name.surname@mojaloop.io>
24
- --------------
25
- ******/
26
-
27
- 'use strict'
28
-
29
- /**
30
- * Performance Optimizer Module
31
- *
32
- * This module provides utilities for managing caches and optimizing
33
- * performance across the application.
34
- *
35
- * Key Optimizations:
36
- * 1. Postman Sandbox Context Pooling - Reuse postman-sandbox contexts
37
- * 2. JavaScript Sandbox Context Pooling - Reuse vm-javascript-sandbox contexts
38
- * 3. Schema Compilation Caching - Cache AJV compiled schemas
39
- */
40
-
41
- const customLogger = require('./requestLogger')
42
-
43
- // Registry of cache management functions
44
- const cacheManagers = []
45
-
46
- /**
47
- * Register a cache management function
48
- * @param {Function} clearFn - Function to clear/manage the cache
49
- * @param {String} name - Name of the cache for logging
50
- */
51
- const registerCache = (clearFn, name) => {
52
- cacheManagers.push({ clearFn, name })
53
- }
54
-
55
- /**
56
- * Clear all registered caches
57
- * Use this when you need to reset all caches (e.g., configuration reload)
58
- */
59
- const clearAllCaches = () => {
60
- customLogger.logMessage('info', 'Clearing all performance caches', { notification: false })
61
- cacheManagers.forEach(({ clearFn, name }) => {
62
- try {
63
- clearFn()
64
- customLogger.logMessage('debug', `Cleared cache: ${name}`, { notification: false })
65
- } catch (err) {
66
- customLogger.logMessage('error', `Failed to clear cache: ${name}`, { additionalData: err.message, notification: false })
67
- }
68
- })
69
- customLogger.logMessage('info', `Cleared ${cacheManagers.length} caches`, { notification: false })
70
- }
71
-
72
- /**
73
- * Get cache statistics
74
- * @returns {Object} Statistics about registered caches
75
- */
76
- const getCacheStats = () => {
77
- return {
78
- totalCaches: cacheManagers.length,
79
- caches: cacheManagers.map(({ name }) => name)
80
- }
81
- }
82
-
83
- /**
84
- * Setup periodic cache cleanup
85
- * @param {Number} intervalMs - Interval in milliseconds (default: 1 hour)
86
- */
87
- const setupPeriodicCleanup = (intervalMs = 3600000) => {
88
- setInterval(() => {
89
- customLogger.logMessage('debug', 'Running periodic cache cleanup', { notification: false })
90
- // Trigger garbage collection hint if available
91
- if (global.gc) {
92
- global.gc()
93
- }
94
- }, intervalMs)
95
- }
96
-
97
- module.exports = {
98
- registerCache,
99
- clearAllCaches,
100
- getCacheStats,
101
- setupPeriodicCleanup
102
- }