ml-testing-toolkit 18.13.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.
Files changed (319) hide show
  1. package/.dockerignore +10 -0
  2. package/.grype.yaml +16 -0
  3. package/.ncurc.yaml +9 -0
  4. package/.nvmrc +1 -0
  5. package/.versionrc.js +16 -0
  6. package/CHANGELOG.md +504 -0
  7. package/CODEOWNERS +30 -0
  8. package/Dockerfile +42 -0
  9. package/Dockerfile-newman +13 -0
  10. package/LICENSE.md +9 -0
  11. package/README.md +119 -0
  12. package/assets/diagrams/architectural/architectural-diagram.svg +3 -0
  13. package/assets/diagrams/flow/flow-diagram.svg +3 -0
  14. package/assets/images/Sample-Response-Failure.png +0 -0
  15. package/assets/images/Screenshot 2020-04-16 at 22.58.04.png +0 -0
  16. package/assets/images/TLS-Enabled-on-Environment.png +0 -0
  17. package/assets/images/adapter-mutual-tls-enabled.png +0 -0
  18. package/assets/images/add-additional-input-values.png +0 -0
  19. package/assets/images/add-condition-button.png +0 -0
  20. package/assets/images/add-new-assertion.png +0 -0
  21. package/assets/images/add-new-input-value.png +0 -0
  22. package/assets/images/add-new-input-variable.png +0 -0
  23. package/assets/images/additional-transfers.png +0 -0
  24. package/assets/images/api-provisioning-add-new-api-confirmation.png +0 -0
  25. package/assets/images/api-provisioning-file-input-window.png +0 -0
  26. package/assets/images/api-provisioning-list-apis-view.png +0 -0
  27. package/assets/images/api-provisioning-menu-item.png +0 -0
  28. package/assets/images/apply_and_restart.jpg +0 -0
  29. package/assets/images/assess-request-or-response.png +0 -0
  30. package/assets/images/assess-response-equation-save.png +0 -0
  31. package/assets/images/assess-response-equation.png +0 -0
  32. package/assets/images/assess-response-status.png +0 -0
  33. package/assets/images/building-new-rules-file.png +0 -0
  34. package/assets/images/callback-rules-screen.png +0 -0
  35. package/assets/images/configurable-parameter-assertion.png +0 -0
  36. package/assets/images/configurable-parameter-currency.png +0 -0
  37. package/assets/images/configurable-parameter.png +0 -0
  38. package/assets/images/connection-manager-ui-opening.png +0 -0
  39. package/assets/images/create-inbound-user-simulator.png +0 -0
  40. package/assets/images/creating-new-rule-file.png +0 -0
  41. package/assets/images/dfsp-client-cacert.png +0 -0
  42. package/assets/images/dfsp-client-submit.png +0 -0
  43. package/assets/images/dfsp-client.png +0 -0
  44. package/assets/images/dfsp-p2p-happy-path.png +0 -0
  45. package/assets/images/dfsp-server-cacert.png +0 -0
  46. package/assets/images/dfsp-server-cert.png +0 -0
  47. package/assets/images/download-report.png +0 -0
  48. package/assets/images/drive_have_not_been_shared.jpg +0 -0
  49. package/assets/images/event-response-options.png +0 -0
  50. package/assets/images/expand-monitoring-messages.png +0 -0
  51. package/assets/images/fixed-response-sample.png +0 -0
  52. package/assets/images/header-selection.png +0 -0
  53. package/assets/images/heap_error_windows.jpg +0 -0
  54. package/assets/images/hosted-mode-docker-compose-intro.png +0 -0
  55. package/assets/images/hub-client-cert.png +0 -0
  56. package/assets/images/import-template.png +0 -0
  57. package/assets/images/inbound-requests-environment.png +0 -0
  58. package/assets/images/inbound-requests-scripts.png +0 -0
  59. package/assets/images/jws-certificate-submit.png +0 -0
  60. package/assets/images/jws-certificate.png +0 -0
  61. package/assets/images/jws-certs-keys.png +0 -0
  62. package/assets/images/jws-hub-certs-keys.png +0 -0
  63. package/assets/images/local-enable-jws-publickey.png +0 -0
  64. package/assets/images/local-mutual-tls-enabled.png +0 -0
  65. package/assets/images/local_drives_to_be_available.jpg +0 -0
  66. package/assets/images/mcm-environment-opening.png +0 -0
  67. package/assets/images/menu-items.png +0 -0
  68. package/assets/images/mock-response-sample.png +0 -0
  69. package/assets/images/monitoring-initial-state.png +0 -0
  70. package/assets/images/monitoring-messages.png +0 -0
  71. package/assets/images/new-empty-assertion.png +0 -0
  72. package/assets/images/opened-imported-template.png +0 -0
  73. package/assets/images/opening-default-settings.png +0 -0
  74. package/assets/images/opening-sync-response-rules.png +0 -0
  75. package/assets/images/opening-view.png +0 -0
  76. package/assets/images/outbound-display-opening-hub.png +0 -0
  77. package/assets/images/outbound-display-opening.png +0 -0
  78. package/assets/images/override-with-environment-variable.png +0 -0
  79. package/assets/images/populate-with-sample-body.png +0 -0
  80. package/assets/images/resource-selection.png +0 -0
  81. package/assets/images/rule-builder-select-api.png +0 -0
  82. package/assets/images/sample-condition-add-configurable-params.png +0 -0
  83. package/assets/images/sample-condition.png +0 -0
  84. package/assets/images/sample-editor.png +0 -0
  85. package/assets/images/sample-request.png +0 -0
  86. package/assets/images/sample-test-assertion.png +0 -0
  87. package/assets/images/send-transfer.png +0 -0
  88. package/assets/images/sending-single-test-case-1.png +0 -0
  89. package/assets/images/sending-single-test-case-2.png +0 -0
  90. package/assets/images/sending-test-cases.png +0 -0
  91. package/assets/images/server-certificates-submitted.png +0 -0
  92. package/assets/images/simulator-response.png +0 -0
  93. package/assets/images/simulator-scheme-adapter-endpoint.png +0 -0
  94. package/assets/images/summarized-view-of-rule.png +0 -0
  95. package/assets/images/template-window.png +0 -0
  96. package/assets/images/test-case-editor-console-log.png +0 -0
  97. package/assets/images/test-case-editor-environment-state.png +0 -0
  98. package/assets/images/test-case-editor-scripts.png +0 -0
  99. package/assets/images/test-case-editor.png +0 -0
  100. package/assets/images/testcase-definition-download.png +0 -0
  101. package/assets/images/testcase-definition-edit-meta-info.png +0 -0
  102. package/assets/images/testing-toolkit-mojaloop-testing-toolkit-endpoint.png +0 -0
  103. package/assets/images/tls-hub-certs-keys.png +0 -0
  104. package/assets/images/tls-jws-enabled-on-environment.png +0 -0
  105. package/assets/images/updated-sample-body-data.png +0 -0
  106. package/assets/images/using-configurable-parameter.png +0 -0
  107. package/assets/images/validation-rules-screen.png +0 -0
  108. package/assets/images/view-response.png +0 -0
  109. package/audit-ci.jsonc +7 -0
  110. package/connection-manager/docker-compose.yml +55 -0
  111. package/database/docker-compose.yml +16 -0
  112. package/docker/hosted-mode/docker-compose.yaml +107 -0
  113. package/docker/hosted-mode/keycloak/keycloak-realm.json +2298 -0
  114. package/docker/hosted-mode/mongo-init.sh +1 -0
  115. package/docker/hosted-mode-tls/docker-compose.yaml +171 -0
  116. package/docker/hosted-mode-tls/keycloak/keycloak-realm.json +2298 -0
  117. package/docker/hosted-mode-tls/mongo-init.sh +1 -0
  118. package/docker-compose.yml +62 -0
  119. package/documents/Mojaloop-Testing-Toolkit.md +296 -0
  120. package/documents/RULES_ENGINE.md +403 -0
  121. package/documents/User-Guide-API-Provisioning.md +121 -0
  122. package/documents/User-Guide-CLI.md +218 -0
  123. package/documents/User-Guide-Connection-Manager.md +282 -0
  124. package/documents/User-Guide-Frequently-Asked-Questions.md +39 -0
  125. package/documents/User-Guide-Hosted-Mode-Docker-Compose.md +110 -0
  126. package/documents/User-Guide-Installation.md +163 -0
  127. package/documents/User-Guide-Mojaloop-Testing-Toolkit.md +642 -0
  128. package/documents/User-Guide-OAuth-Server-Deployment.md +283 -0
  129. package/documents/User-Guide-Onboarding-DFSP.md +197 -0
  130. package/documents/User-Guide-Onboarding-HUB.md +191 -0
  131. package/documents/User-Guide.md +53 -0
  132. package/examples/collections/dfsp/p2p_failed_tests.json +7161 -0
  133. package/examples/collections/dfsp/p2p_fx_happy_path.json +502 -0
  134. package/examples/collections/dfsp/p2p_happy_path.json +350 -0
  135. package/examples/collections/dfsp/p2p_happy_path_extended.json +6106 -0
  136. package/examples/collections/dfsp/p2p_happy_path_jws.json +511 -0
  137. package/examples/collections/dfsp/p2p_payee_assertions_websocket.json +441 -0
  138. package/examples/collections/dfsp/sample.json +5029 -0
  139. package/examples/collections/dfsp/transaction_request_service.json +240 -0
  140. package/examples/collections/fxp/FXP.json +264 -0
  141. package/examples/collections/fxp/SDK_backend.json +98 -0
  142. package/examples/collections/fxp/SDK_outbound.json +163 -0
  143. package/examples/collections/hub/hub_01_p2p_happy_path/hub_p2p_receive_quote.json +400 -0
  144. package/examples/collections/hub/hub_01_p2p_happy_path/hub_p2p_send_quote.json +395 -0
  145. package/examples/collections/hub/hub_02_block_transfer/hub_block_transfer.json +393 -0
  146. package/examples/collections/hub/hub_03_funds_in_out/hub_funds_in.json +224 -0
  147. package/examples/collections/hub/hub_03_funds_in_out/hub_funds_out.json +780 -0
  148. package/examples/collections/hub/hub_04_settlements/hub_settlements.json +3138 -0
  149. package/examples/collections/hub/hub_05_transfer_negative_scenarios/hub_transfer_negative_payee_abort.json +475 -0
  150. package/examples/collections/hub/hub_05_transfer_negative_scenarios/hub_transfer_negative_payee_invalid_fulfillment.json +370 -0
  151. package/examples/collections/hub/hub_05_transfer_negative_scenarios/hub_transfer_negative_transfer_timeout.json +262 -0
  152. package/examples/collections/hub/hub_06_transaction_requests_service/hub_trs_authorizations.json +117 -0
  153. package/examples/collections/hub/hub_06_transaction_requests_service/hub_trs_error_framework.json +591 -0
  154. package/examples/collections/hub/hub_06_transaction_requests_service/hub_trs_received_state.json +379 -0
  155. package/examples/collections/hub/hub_06_transaction_requests_service/hub_trs_reject_state.json +361 -0
  156. package/examples/collections/hub/hub_07_quoting_service.json +525 -0
  157. package/examples/collections/hub/hub_08_participant_inactive_stop_transfers.json +706 -0
  158. package/examples/collections/hub/hub_09_duplicate_handling_transfers.json +1377 -0
  159. package/examples/collections/hub/hub_10_on_us_transfers.json +245 -0
  160. package/examples/collections/hub/hub_11_accented_and_spl_chars.json +629 -0
  161. package/examples/collections/hub/hub_12_fspiop_version_1.1.json +646 -0
  162. package/examples/collections/hub/hub_13_bulk_transfers.json +1857 -0
  163. package/examples/collections/iso20022/self_referencing_iso20022.json +926 -0
  164. package/examples/collections/provisioning/testingtoolkitdfsp.json +904 -0
  165. package/examples/environments/dfsp_local_environment.json +46 -0
  166. package/examples/environments/hub_local_environment.json +57 -0
  167. package/jest.config.js +17 -0
  168. package/package.json +199 -0
  169. package/sbom-v18.12.4.csv +1553 -0
  170. package/secrets/keygen.sh +5 -0
  171. package/secrets/privatekey.pem +27 -0
  172. package/secrets/publickey.cer +21 -0
  173. package/secrets/tls/01.pem +132 -0
  174. package/secrets/tls/createSecrets.sh +20 -0
  175. package/secrets/tls/hub_client.csr +32 -0
  176. package/secrets/tls/hub_client_cacert.pem +35 -0
  177. package/secrets/tls/hub_client_cakey.pem +52 -0
  178. package/secrets/tls/hub_client_key.key +52 -0
  179. package/secrets/tls/hub_server.csr +31 -0
  180. package/secrets/tls/hub_server_cacert.pem +35 -0
  181. package/secrets/tls/hub_server_cakey.pem +52 -0
  182. package/secrets/tls/hub_server_cert.pem +132 -0
  183. package/secrets/tls/hub_server_key.key +52 -0
  184. package/secrets/tls/index.txt +1 -0
  185. package/secrets/tls/index.txt.attr +1 -0
  186. package/secrets/tls/openssl-client.cnf +36 -0
  187. package/secrets/tls/openssl-clientca.cnf +71 -0
  188. package/secrets/tls/openssl-server.cnf +39 -0
  189. package/secrets/tls/openssl-serverca.cnf +71 -0
  190. package/secrets/tls/serial.txt +1 -0
  191. package/spec_files/api_definitions/als_admin_1.1/api_spec.yaml +804 -0
  192. package/spec_files/api_definitions/central_admin_1.0/api_spec.yaml +1850 -0
  193. package/spec_files/api_definitions/central_admin_1.0/response_map.json +96 -0
  194. package/spec_files/api_definitions/central_admin_old_9.3/api_spec.yaml +2467 -0
  195. package/spec_files/api_definitions/central_admin_old_9.3/response_map.json +96 -0
  196. package/spec_files/api_definitions/fspiop_1.0/api_spec.yaml +4187 -0
  197. package/spec_files/api_definitions/fspiop_1.0/callback_map.json +568 -0
  198. package/spec_files/api_definitions/fspiop_1.0/mockRef.json +79 -0
  199. package/spec_files/api_definitions/fspiop_1.0/trigger_templates/transaction_request_followup.json +126 -0
  200. package/spec_files/api_definitions/fspiop_1.0/trigger_templates/transaction_request_followup_quotes_only.json +97 -0
  201. package/spec_files/api_definitions/fspiop_1.1/api_spec.yaml +3778 -0
  202. package/spec_files/api_definitions/fspiop_1.1/callback_map.json +568 -0
  203. package/spec_files/api_definitions/fspiop_1.1/mockRef.json +79 -0
  204. package/spec_files/api_definitions/fspiop_1.1/trigger_templates/transaction_request_followup.json +125 -0
  205. package/spec_files/api_definitions/fspiop_2.0/api_spec.yaml +4839 -0
  206. package/spec_files/api_definitions/fspiop_2.0/callback_map.json +716 -0
  207. package/spec_files/api_definitions/fspiop_2.0/mockRef.json +79 -0
  208. package/spec_files/api_definitions/fspiop_2.0/trigger_templates/transaction_request_followup.json +125 -0
  209. package/spec_files/api_definitions/fspiop_2.0_iso20022/api_spec.yaml +8331 -0
  210. package/spec_files/api_definitions/fspiop_2.0_iso20022/callback_map.json +508 -0
  211. package/spec_files/api_definitions/fspiop_2.0_iso20022/mockRef.json +66 -0
  212. package/spec_files/api_definitions/fx-api_2.0/api_spec.yaml +1768 -0
  213. package/spec_files/api_definitions/fx-api_2.0/callback_map.json +188 -0
  214. package/spec_files/api_definitions/fx-api_2.0/mockRef.json +83 -0
  215. package/spec_files/api_definitions/mojaloop_sdk_outbound_scheme_adapter_1.0/api_spec.yaml +2612 -0
  216. package/spec_files/api_definitions/mojaloop_sdk_outbound_scheme_adapter_1.0/mockRef.json +22 -0
  217. package/spec_files/api_definitions/mojaloop_sdk_outbound_scheme_adapter_1.0/response_map.json +35 -0
  218. package/spec_files/api_definitions/mojaloop_simulator_0.1/api_spec.yaml +225 -0
  219. package/spec_files/api_definitions/mojaloop_simulator_sim_1.4/api_spec.yaml +1087 -0
  220. package/spec_files/api_definitions/mojaloop_simulator_sim_1.4/mockRef.json +75 -0
  221. package/spec_files/api_definitions/mojaloop_simulator_sim_1.4/response_map.json +55 -0
  222. package/spec_files/api_definitions/payment_manager_1.4/api_spec.yaml +1389 -0
  223. package/spec_files/api_definitions/sdk-scheme-adapter-backend-v2_1_0-openapi3-snippets_2.1/api_spec.yaml +2834 -0
  224. package/spec_files/api_definitions/sdk-scheme-adapter-outbound-v2_1_0-openapi3-snippets_2.1/api_spec.yaml +3449 -0
  225. package/spec_files/api_definitions/settlements_1.0/api_spec.yaml +983 -0
  226. package/spec_files/api_definitions/settlements_1.0/mockRef.json +38 -0
  227. package/spec_files/api_definitions/settlements_1.0/response_map.json +34 -0
  228. package/spec_files/api_definitions/settlements_2.0/api_spec.yaml +1001 -0
  229. package/spec_files/api_definitions/settlements_2.0/mockRef.json +38 -0
  230. package/spec_files/api_definitions/settlements_2.0/response_map.json +34 -0
  231. package/spec_files/api_definitions/thirdparty_sdk_outbound_0.1/api_spec.yaml +2139 -0
  232. package/spec_files/reports/templates/newman/html_template.html +1202 -0
  233. package/spec_files/reports/templates/newman/pdf_template.html +790 -0
  234. package/spec_files/reports/templates/testcase_definition/table_view.html +1602 -0
  235. package/spec_files/rules_callback/config.json +3 -0
  236. package/spec_files/rules_callback/default.json +2698 -0
  237. package/spec_files/rules_callback/p2p-limit.json +129 -0
  238. package/spec_files/rules_forward/config.json +3 -0
  239. package/spec_files/rules_forward/default.json +482 -0
  240. package/spec_files/rules_response/config.json +3 -0
  241. package/spec_files/rules_response/default.json +295 -0
  242. package/spec_files/rules_validation/config.json +3 -0
  243. package/spec_files/rules_validation/default.json +1 -0
  244. package/spec_files/rules_validation/p2p-limit.json +55 -0
  245. package/spec_files/system_config.json +175 -0
  246. package/spec_files/user_config.json +109 -0
  247. package/src/index.js +67 -0
  248. package/src/lib/MyEventEmitter.js +54 -0
  249. package/src/lib/api-management.js +143 -0
  250. package/src/lib/api-routes/config.js +83 -0
  251. package/src/lib/api-routes/history.js +139 -0
  252. package/src/lib/api-routes/keycloak.js +54 -0
  253. package/src/lib/api-routes/longpolling.js +70 -0
  254. package/src/lib/api-routes/oauth2.js +149 -0
  255. package/src/lib/api-routes/objectstore.js +53 -0
  256. package/src/lib/api-routes/openapi.js +224 -0
  257. package/src/lib/api-routes/outbound.js +134 -0
  258. package/src/lib/api-routes/reports.js +72 -0
  259. package/src/lib/api-routes/rules.js +356 -0
  260. package/src/lib/api-routes/samples.js +92 -0
  261. package/src/lib/api-routes/server-logs.js +44 -0
  262. package/src/lib/api-routes/settings.js +71 -0
  263. package/src/lib/api-server.js +135 -0
  264. package/src/lib/arrayStore.js +101 -0
  265. package/src/lib/callbackHandler.js +201 -0
  266. package/src/lib/config.js +177 -0
  267. package/src/lib/configuration-providers/mb-connection-manager.js +625 -0
  268. package/src/lib/db/adapters/dbAdapter.js +184 -0
  269. package/src/lib/db/dfspMockUsers.js +64 -0
  270. package/src/lib/db/models/mongoDBWrapper.js +78 -0
  271. package/src/lib/eventListenerClient/inboundEventListener.js +176 -0
  272. package/src/lib/fileAdapter.js +57 -0
  273. package/src/lib/httpAgentStore.js +135 -0
  274. package/src/lib/importExport.js +186 -0
  275. package/src/lib/jws/JwsSigning.js +141 -0
  276. package/src/lib/loadSamples.js +128 -0
  277. package/src/lib/logger.js +20 -0
  278. package/src/lib/longpollingEmitter.js +56 -0
  279. package/src/lib/metrics.js +51 -0
  280. package/src/lib/mocking/custom-functions/generic.js +57 -0
  281. package/src/lib/mocking/middleware-functions/ilpModel.js +238 -0
  282. package/src/lib/mocking/middleware-functions/quotesAssociation.js +75 -0
  283. package/src/lib/mocking/middleware-functions/transactionRequestsService.js +78 -0
  284. package/src/lib/mocking/openApiDefinitionsModel.js +64 -0
  285. package/src/lib/mocking/openApiMockHandler.js +466 -0
  286. package/src/lib/mocking/openApiRulesEngine.js +492 -0
  287. package/src/lib/mocking/openApiVersionTools.js +136 -0
  288. package/src/lib/mocking/transformers/fspiopToISO20022.js +230 -0
  289. package/src/lib/mocking/transformers/index.js +41 -0
  290. package/src/lib/notificationEmitter.js +64 -0
  291. package/src/lib/oauth/KeycloakHelper.js +220 -0
  292. package/src/lib/oauth/LoginService.js +133 -0
  293. package/src/lib/oauth/OAuthHelper.js +181 -0
  294. package/src/lib/oauth/OAuthValidator.js +118 -0
  295. package/src/lib/oauth/Wso2Client.js +64 -0
  296. package/src/lib/objectStore/inMemoryImpl.js +50 -0
  297. package/src/lib/objectStore/objectStoreInterface.js +51 -0
  298. package/src/lib/objectStore.js +122 -0
  299. package/src/lib/report-generator/generator.js +126 -0
  300. package/src/lib/report-generator/helpers.js +154 -0
  301. package/src/lib/requestLogger.js +190 -0
  302. package/src/lib/resources/wso2carbon-publickey.cert +20 -0
  303. package/src/lib/rulesEngine.js +95 -0
  304. package/src/lib/rulesEngineModel.js +463 -0
  305. package/src/lib/scripting-engines/postman-sandbox.js +142 -0
  306. package/src/lib/scripting-engines/vm-javascript-sandbox.js +294 -0
  307. package/src/lib/server-logs/adapters/elastic-search.js +102 -0
  308. package/src/lib/server-logs/adapters/grafana.js +0 -0
  309. package/src/lib/server-logs/index.js +75 -0
  310. package/src/lib/socket-server.js +55 -0
  311. package/src/lib/storageAdapter.js +109 -0
  312. package/src/lib/test-outbound/TestCaseRunner.js +173 -0
  313. package/src/lib/test-outbound/getTracing.js +19 -0
  314. package/src/lib/test-outbound/outbound-initiator.js +1107 -0
  315. package/src/lib/uniqueIdGenerator.js +35 -0
  316. package/src/lib/utils.js +89 -0
  317. package/src/lib/utilsInternal.js +56 -0
  318. package/src/lib/webSocketClient/WebSocketClientManager.js +197 -0
  319. package/src/server.js +218 -0
@@ -0,0 +1,1107 @@
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
+ * ModusBox
26
+ * Georgi Logodazhki <georgi.logodazhki@modusbox.com>
27
+ * Vijaya Kumar Guthi <vijaya.guthi@modusbox.com> (Original Author)
28
+ --------------
29
+ ******/
30
+ /* eslint-disable camelcase */
31
+
32
+ const _ = require('lodash')
33
+ const axios = require('axios').default
34
+ const https = require('https')
35
+ const uuid = require('uuid')
36
+ const util = require('node:util')
37
+ const crypto = require('node:crypto')
38
+ require('request-to-curl')
39
+ require('atob') // eslint-disable-line
40
+
41
+ const Config = require('../config')
42
+ const customLogger = require('../requestLogger')
43
+ const MyEventEmitter = require('../MyEventEmitter')
44
+ const notificationEmitter = require('../notificationEmitter.js')
45
+ const { readFileAsync, headersToLowerCase } = require('../utils')
46
+ const expectOriginal = require('chai').expect // eslint-disable-line
47
+ const JwsSigning = require('../jws/JwsSigning')
48
+ const ConnectionProvider = require('../configuration-providers/mb-connection-manager')
49
+
50
+ const postmanContext = require('../scripting-engines/postman-sandbox')
51
+ const javascriptContext = require('../scripting-engines/vm-javascript-sandbox')
52
+ const openApiDefinitionsModel = require('../mocking/openApiDefinitionsModel')
53
+ const utilsInternal = require('../utilsInternal')
54
+ const dbAdapter = require('../db/adapters/dbAdapter')
55
+ const arrayStore = require('../arrayStore')
56
+ const UniqueIdGenerator = require('../../lib/uniqueIdGenerator')
57
+ const httpAgentStore = require('../httpAgentStore')
58
+ const Transformers = require('../mocking/transformers')
59
+ const getTracing = require('./getTracing')
60
+ const TestCaseRunner = require('./TestCaseRunner')
61
+
62
+ delete axios.defaults.headers.common.Accept
63
+
64
+ const terminateTraceIds = {}
65
+
66
+ const OutboundSend = async (
67
+ inputTemplate,
68
+ traceID,
69
+ dfspId,
70
+ sync = false,
71
+ metrics
72
+ ) => {
73
+ const totalCounts = getTotalCounts(inputTemplate)
74
+ const globalConfig = {
75
+ broadcastOutboundProgressEnabled: true,
76
+ scriptExecution: true,
77
+ testsExecution: true,
78
+ totalProgress: {
79
+ testCasesTotal: totalCounts.totalTestCases,
80
+ testCasesProcessed: 0,
81
+ requestsTotal: totalCounts.totalRequests,
82
+ requestsProcessed: 0,
83
+ assertionsTotal: totalCounts.totalAssertions,
84
+ assertionsProcessed: 0,
85
+ assertionsPassed: 0,
86
+ assertionsFailed: 0
87
+ }
88
+ }
89
+
90
+ const startedTimeStamp = new Date()
91
+ const tracing = getTracing(traceID, dfspId)
92
+
93
+ const variableData = {
94
+ environment: { ...inputTemplate.inputValues }
95
+ }
96
+ try {
97
+ await (new TestCaseRunner(Config)).runAll({
98
+ processTestCase, inputTemplate, traceID, variableData, dfspId, globalConfig, metrics
99
+ })
100
+
101
+ const completedTimeStamp = new Date()
102
+ const runDurationMs = completedTimeStamp.getTime() - startedTimeStamp.getTime()
103
+ // Send the total result to client
104
+ const runtimeInformation = {
105
+ testReportId: inputTemplate.name + '_' + completedTimeStamp.toISOString(),
106
+ completedTimeISO: completedTimeStamp.toISOString(),
107
+ startedTime: startedTimeStamp.toUTCString(),
108
+ completedTime: completedTimeStamp,
109
+ completedTimeUTC: completedTimeStamp.toUTCString(),
110
+ startedTS: startedTimeStamp.getTime(),
111
+ completedTS: completedTimeStamp.getTime(),
112
+ runDurationMs,
113
+ totalAssertions: 0,
114
+ totalPassedAssertions: 0,
115
+ isPassed: false
116
+ }
117
+ if (sync) {
118
+ return generateFinalReport(inputTemplate, runtimeInformation, metrics)
119
+ }
120
+ if (tracing.outboundID) {
121
+ const totalResult = generateFinalReport(inputTemplate, runtimeInformation, metrics)
122
+ if (Config.getSystemConfig().HOSTING_ENABLED) {
123
+ dbAdapter.upsert('reports', totalResult, { dfspId })
124
+ }
125
+ const saveReportStatus = {}
126
+ if (inputTemplate.saveReport) {
127
+ try {
128
+ await dbAdapter.upsertReport(totalResult)
129
+ saveReportStatus.isSaved = true
130
+ saveReportStatus.message = 'OK'
131
+ } catch (err) {
132
+ customLogger.logMessage('error', 'Error while saving report: ' + err.message)
133
+ saveReportStatus.isSaved = false
134
+ saveReportStatus.message = err.message
135
+ }
136
+ }
137
+ notificationEmitter.broadcastOutboundProgress({
138
+ status: 'FINISHED',
139
+ outboundID: tracing.outboundID,
140
+ saveReportStatus,
141
+ totalResult
142
+ }, tracing.sessionID)
143
+ }
144
+ } catch (err) {
145
+ console.log('error in OutboundSend:', err)
146
+ notificationEmitter.broadcastOutboundProgress({
147
+ status: 'TERMINATED',
148
+ outboundID: tracing.outboundID
149
+ }, tracing.sessionID)
150
+ }
151
+ }
152
+
153
+ /* istanbul ignore next */
154
+ const OutboundSendLoop = async (inputTemplate, traceID, dfspId, iterations, metrics) => {
155
+ const totalCounts = getTotalCounts(inputTemplate)
156
+
157
+ const globalConfig = {
158
+ broadcastOutboundProgressEnabled: false,
159
+ scriptExecution: true,
160
+ testsExecution: true,
161
+ totalProgress: {
162
+ testCasesTotal: totalCounts.totalTestCases,
163
+ testCasesProcessed: 0,
164
+ requestsTotal: totalCounts.totalRequests,
165
+ requestsProcessed: 0,
166
+ assertionsTotal: totalCounts.totalAssertions,
167
+ assertionsProcessed: 0,
168
+ assertionsPassed: 0,
169
+ assertionsFailed: 0
170
+ }
171
+ }
172
+ const tracing = getTracing(traceID, dfspId)
173
+
174
+ const environmentVariables = { ...inputTemplate.inputValues }
175
+ try {
176
+ const totalStartedTimeStamp = new Date()
177
+ const totalReport = {
178
+ iterations: []
179
+ }
180
+ for (let itn = 0; itn < iterations; itn++) {
181
+ const startedTimeStamp = new Date()
182
+ // Deep copy the template
183
+ const tmpTemplate = JSON.parse(JSON.stringify(inputTemplate))
184
+ // Execute all the test cases in the template
185
+ for (const i in tmpTemplate.test_cases) {
186
+ await processTestCase(
187
+ tmpTemplate.test_cases[i],
188
+ traceID,
189
+ tmpTemplate.inputValues,
190
+ environmentVariables,
191
+ dfspId,
192
+ globalConfig,
193
+ tmpTemplate.options,
194
+ metrics,
195
+ tmpTemplate.name,
196
+ tmpTemplate.saveReport,
197
+ i
198
+ )
199
+ }
200
+ const completedTimeStamp = new Date()
201
+ const runDurationMs = completedTimeStamp.getTime() - startedTimeStamp.getTime()
202
+ const runtimeInformation = {
203
+ iterationNumber: itn,
204
+ completedTimeISO: completedTimeStamp.toISOString(),
205
+ startedTime: startedTimeStamp.toUTCString(),
206
+ completedTime: completedTimeStamp.toUTCString(),
207
+ runDurationMs,
208
+ totalAssertions: 0,
209
+ totalPassedAssertions: 0,
210
+ isPassed: false
211
+ }
212
+ // TODO: This can be optimized by storing only results into the iterations array
213
+ totalReport.iterations.push(generateFinalReport(tmpTemplate, runtimeInformation, metrics))
214
+ notificationEmitter.broadcastOutboundProgress({
215
+ status: 'ITERATION_PROGRESS',
216
+ outboundID: tracing.outboundID,
217
+ iterationStatus: runtimeInformation
218
+ }, tracing.sessionID)
219
+ }
220
+
221
+ const totalCompletedTimeStamp = new Date()
222
+ const totalRunDurationMs = totalCompletedTimeStamp.getTime() - totalStartedTimeStamp.getTime()
223
+ // Send the total result to client
224
+ if (tracing.outboundID) {
225
+ notificationEmitter.broadcastOutboundProgress({
226
+ status: 'ITERATIONS_FINISHED',
227
+ outboundID: tracing.outboundID,
228
+ totalRunDurationMs,
229
+ totalReport
230
+ }, tracing.sessionID)
231
+ }
232
+ } catch (err) {
233
+ console.log('error in OutboundSendLoop:', err)
234
+ notificationEmitter.broadcastOutboundProgress({
235
+ status: 'ITERATIONS_TERMINATED',
236
+ outboundID: tracing.outboundID,
237
+ errorMessage: err.message
238
+ }, tracing.sessionID)
239
+ }
240
+ }
241
+
242
+ const terminateOutbound = (traceID) => {
243
+ terminateTraceIds[traceID] = true
244
+ }
245
+
246
+ const processTestCase = async (
247
+ testCase,
248
+ traceID,
249
+ inputValues,
250
+ variableData,
251
+ dfspId,
252
+ globalConfig,
253
+ templateOptions = {},
254
+ metrics,
255
+ templateName,
256
+ saveReport,
257
+ testCaseIndex
258
+ ) => {
259
+ const tracing = getTracing(traceID)
260
+ const testCaseStartedTime = new Date()
261
+
262
+ // Load the requests array into an object by the request id to access a particular object faster
263
+ const requestsObj = {}
264
+ // Store the request ids into a new array
265
+ const templateIDArr = []
266
+ for (const i in testCase.requests) {
267
+ requestsObj[testCase.requests[i].id] = testCase.requests[i]
268
+ templateIDArr.push(testCase.requests[i].id)
269
+ }
270
+
271
+ const apiDefinitions = await openApiDefinitionsModel.getApiDefinitions()
272
+ // Iterate the request ID array
273
+ for (let requestIndex = 0; requestIndex < templateIDArr.length; requestIndex++) {
274
+ if (terminateTraceIds[traceID]) {
275
+ delete terminateTraceIds[traceID]
276
+ throw new Error('Terminated')
277
+ }
278
+
279
+ const requestStartedTime = new Date()
280
+
281
+ const request = requestsObj[templateIDArr[requestIndex]]
282
+
283
+ let convertedRequest = JSON.parse(JSON.stringify(request))
284
+
285
+ if (request.disabled) {
286
+ await setSkippedResponse(convertedRequest, request, 'SKIPPED', tracing, testCase, {}, globalConfig)
287
+ continue
288
+ }
289
+
290
+ const reqApiDefinition = apiDefinitions.find((item) => {
291
+ return (
292
+ item.majorVersion === +request.apiVersion.majorVersion &&
293
+ item.minorVersion === +request.apiVersion.minorVersion &&
294
+ item.type === request.apiVersion.type
295
+ )
296
+ })
297
+
298
+ // Form the actual http request headers, body, path and method by replacing configurable parameters
299
+ // Replace the parameters
300
+ convertedRequest = replaceVariables(request, inputValues, request, requestsObj, templateOptions)
301
+ convertedRequest = replaceRequestVariables(convertedRequest)
302
+
303
+ if (convertedRequest.delay) {
304
+ await new Promise(resolve => setTimeout(resolve, convertedRequest.delay))
305
+ }
306
+
307
+ convertedRequest.headers = convertedRequest.headers || {}
308
+ // Form the path from params and operationPath
309
+ convertedRequest.path = replacePathVariables(request.operationPath, convertedRequest.params)
310
+ let baggage = convertedRequest.headers.baggage || ''
311
+ if (baggage) baggage = baggage + ','
312
+ convertedRequest.headers.baggage = baggage + `testCaseId=${testCase.id},requestId=${request.id}`
313
+
314
+ const retries = request.retries || 0
315
+ const retriesDelay = request.retriesDelay?.split(',').map(Number) || [250, 500, 1000, 2000, 4000]
316
+ for (convertedRequest.retry = 0; convertedRequest.retry <= retries; convertedRequest.retry++) {
317
+ if (convertedRequest.retry > 0) await new Promise(resolve => setTimeout(resolve, retriesDelay[Math.min(convertedRequest.retry, retriesDelay.length - 1)] || 5000))
318
+ const requestTraceId = saveReport ? crypto.randomBytes(16).toString('hex') : traceID
319
+
320
+ // Insert traceparent header if sessionID passed
321
+ if (tracing.sessionID || saveReport) {
322
+ convertedRequest.headers.traceparent = '00-' + requestTraceId + '-' + String(testCaseIndex).padStart(8, '0') + String(requestIndex).padStart(8, '0') + '-01'
323
+ // todo: think about proper traceparent header
324
+ }
325
+
326
+ const scriptsExecution = {}
327
+ let contextObj = null
328
+ if (globalConfig.scriptExecution) {
329
+ let context = postmanContext
330
+ if (convertedRequest.scriptingEngine && convertedRequest.scriptingEngine === 'javascript') {
331
+ context = javascriptContext
332
+ }
333
+ contextObj = await context.generateContextObj(variableData.environment)
334
+ }
335
+
336
+ // Get transformer if specified
337
+ if (contextObj.transformerObj && templateOptions?.transformerName) {
338
+ contextObj.transformerObj.transformer = Transformers.getTransformer(templateOptions.transformerName)
339
+ contextObj.transformerObj.transformerName = templateOptions.transformerName
340
+ // Currently no options are passed to the transformer in template level, we can add it later if needed
341
+ }
342
+
343
+ // Send http request
344
+ let status
345
+ try {
346
+ // Extra step to access request variables that consists of environment variables in scripts
347
+ const tmpRequest = replaceEnvironmentVariables(convertedRequest, variableData.environment)
348
+ if (globalConfig.scriptExecution) {
349
+ await executePreRequestScript(tmpRequest, scriptsExecution, contextObj, variableData)
350
+ }
351
+
352
+ // Mutating request based on script output
353
+ if (contextObj.requestVariables?.OVERRIDE_REQUEST?.appendMode) {
354
+ _.merge(convertedRequest.body, contextObj.requestVariables.OVERRIDE_REQUEST?.body)
355
+ }
356
+ convertedRequest = replaceEnvironmentVariables(convertedRequest, variableData.environment)
357
+ convertedRequest = replaceRequestLevelEnvironmentVariables(convertedRequest, contextObj.requestVariables)
358
+
359
+ // Change header names to lower case
360
+ convertedRequest.headers = headersToLowerCase(convertedRequest.headers || {})
361
+
362
+ let successCallbackUrl = null
363
+ let errorCallbackUrl = null
364
+ if (reqApiDefinition?.asynchronous === true) {
365
+ const cbMapRawdata = await readFileAsync(reqApiDefinition.callbackMapFile)
366
+ const reqCallbackMap = JSON.parse(cbMapRawdata)
367
+ if (reqCallbackMap[request.operationPath] && reqCallbackMap[request.operationPath][request.method]) {
368
+ const successCallback = reqCallbackMap[request.operationPath][request.method].successCallback
369
+ const errorCallback = reqCallbackMap[request.operationPath][request.method].errorCallback
370
+ successCallbackUrl = successCallback.method + ' ' + replaceVariables(successCallback.pathPattern, null, convertedRequest)
371
+ errorCallbackUrl = errorCallback.method + ' ' + replaceVariables(errorCallback.pathPattern, null, convertedRequest)
372
+ }
373
+ }
374
+ if (contextObj.requestVariables && contextObj.requestVariables.SKIP_REQUEST) {
375
+ status = 'SKIPPED'
376
+ await setSkippedResponse(convertedRequest, request, status, tracing, testCase, scriptsExecution, globalConfig)
377
+ } else {
378
+ // Replace transformer if it is specified in the request level
379
+ if (contextObj.transformerObj && contextObj.requestVariables && contextObj.requestVariables.TRANSFORM) {
380
+ contextObj.transformerObj.transformer = Transformers.getTransformer(contextObj.requestVariables.TRANSFORM.transformerName)
381
+ contextObj.transformerObj.transformerName = contextObj.requestVariables.TRANSFORM.transformerName
382
+ contextObj.transformerObj.options = contextObj.requestVariables.TRANSFORM.options
383
+ }
384
+ const resp = await sendRequest(convertedRequest, successCallbackUrl, errorCallbackUrl, dfspId, contextObj)
385
+ status = 'SUCCESS'
386
+ await setResponse(
387
+ convertedRequest,
388
+ resp,
389
+ variableData,
390
+ request,
391
+ status,
392
+ tracing,
393
+ testCase,
394
+ scriptsExecution,
395
+ contextObj,
396
+ globalConfig,
397
+ metrics,
398
+ templateName,
399
+ requestTraceId,
400
+ requestStartedTime.getTime(),
401
+ convertedRequest.retry === retries
402
+ )
403
+ }
404
+ } catch (err) {
405
+ customLogger.logMessage('error', err.message)
406
+ let resp
407
+ try {
408
+ resp = JSON.parse(err.message)
409
+ } catch (parsingErr) {
410
+ resp = err.message
411
+ }
412
+ status = 'ERROR'
413
+ await setResponse(
414
+ convertedRequest,
415
+ resp,
416
+ variableData,
417
+ request,
418
+ status,
419
+ tracing,
420
+ testCase,
421
+ scriptsExecution,
422
+ contextObj,
423
+ globalConfig,
424
+ metrics,
425
+ templateName,
426
+ requestTraceId,
427
+ requestStartedTime.getTime(),
428
+ convertedRequest.retry === retries
429
+ )
430
+ } finally {
431
+ if (contextObj) {
432
+ contextObj.ctx.dispose()
433
+ contextObj.ctx = null
434
+ }
435
+ if (request.appended?.assertionResults?.isFailed) {
436
+ if (convertedRequest.retry < retries) {
437
+ // safe continue outside of finally
438
+ } else if (templateOptions.breakOnError) {
439
+ // Terminate the test run if assertion failed
440
+ // eslint-disable-next-line
441
+ throw new Error('Terminated')
442
+ } else if (testCase.options?.breakOnError) {
443
+ // Disable the following requests if assertion failed
444
+ for (let j = requestIndex + 1; j < templateIDArr.length; j++) {
445
+ requestsObj[templateIDArr[j]].disabled = true
446
+ }
447
+ }
448
+ }
449
+ }
450
+ if (request.appended?.assertionResults?.isFailed) {
451
+ if (convertedRequest.retry < retries) continue
452
+ } else break
453
+ }
454
+
455
+ const requestCompletedTime = new Date()
456
+ request.appended.testResult = {
457
+ startedTS: requestStartedTime.getTime(),
458
+ completedTS: requestCompletedTime.getTime(),
459
+ runDurationMs: requestCompletedTime.getTime() - requestStartedTime.getTime()
460
+ }
461
+ }
462
+
463
+ const testCaseCompletedTime = new Date()
464
+ testCase.testResult = {
465
+ startedTS: testCaseStartedTime.getTime(),
466
+ completedTS: testCaseCompletedTime.getTime(),
467
+ runDurationMs: testCaseCompletedTime.getTime() - testCaseStartedTime.getTime()
468
+ }
469
+
470
+ // Return status report of this test case
471
+ return testCase
472
+ // Set a timeout if the response callback is not received in a particular time
473
+ }
474
+
475
+ const setResponse = async (
476
+ convertedRequest,
477
+ resp,
478
+ variableData,
479
+ request,
480
+ status,
481
+ tracing,
482
+ testCase,
483
+ scriptsExecution,
484
+ contextObj,
485
+ globalConfig,
486
+ metrics,
487
+ templateName,
488
+ traceId,
489
+ started,
490
+ lastRetry
491
+ ) => {
492
+ // Get the requestsHistory and callbacksHistory from the arrayStore
493
+ const requestsHistoryObj = arrayStore.get('requestsHistory')
494
+ const callbacksHistoryObj = arrayStore.get('callbacksHistory')
495
+ const backgroundData = {
496
+ requestsHistory: requestsHistoryObj,
497
+ callbacksHistory: callbacksHistoryObj
498
+ }
499
+
500
+ if (globalConfig.scriptExecution) {
501
+ await executePostRequestScript(convertedRequest, resp, scriptsExecution, contextObj, variableData, backgroundData)
502
+ }
503
+
504
+ let assertionResults = null
505
+ if (globalConfig.testsExecution) {
506
+ assertionResults = await handleTests(convertedRequest, resp.requestSent, resp.syncResponse, resp.callback, variableData.environment, backgroundData, contextObj.requestVariables)
507
+ }
508
+ request.appended = {
509
+ status,
510
+ assertionResults,
511
+ traceId,
512
+ traceUrl: util.format(Config.getSystemConfig().TRACE_URL || '//trace/%s', traceId, started - 60000, Date.now() + 60000),
513
+ response: resp.syncResponse,
514
+ callback: resp.callback,
515
+ request: convertedRequest,
516
+ transformedRequest: resp.transformedRequest,
517
+ additionalInfo: {
518
+ curlRequest: resp.curlRequest
519
+ }
520
+ }
521
+
522
+ // Update total progress counts
523
+ if (lastRetry || !assertionResults.isFailed) {
524
+ globalConfig.totalProgress.requestsProcessed++
525
+ globalConfig.totalProgress.assertionsProcessed += request.tests && request.tests.assertions ? request.tests.assertions.length : 0
526
+ globalConfig.totalProgress.assertionsPassed += assertionResults.passedCount
527
+ const failed = request.tests && request.tests.assertions ? (request.tests.assertions.length - assertionResults.passedCount) : 0
528
+ globalConfig.totalProgress.assertionsFailed += failed
529
+ const tags = { request: request.description, test: testCase.name }
530
+ metrics?.assertSuccess.add(assertionResults.passedCount, tags)
531
+ metrics?.assertFail.add(failed, tags)
532
+
533
+ if (tracing.outboundID && globalConfig.broadcastOutboundProgressEnabled) {
534
+ notificationEmitter.broadcastOutboundProgress({
535
+ outboundID: tracing.outboundID,
536
+ testCaseId: testCase.id,
537
+ testCaseName: testCase.name,
538
+ status,
539
+ requestId: request.id,
540
+ response: resp.syncResponse,
541
+ callback: resp.callback,
542
+ transformedRequest: resp.transformedRequest,
543
+ requestSent: convertedRequest,
544
+ additionalInfo: {
545
+ curlRequest: resp.curlRequest,
546
+ scriptsExecution
547
+ },
548
+ testResult: assertionResults, // This should be changed, but it breaks UI. So keeping it for now.
549
+ totalProgress: globalConfig.totalProgress
550
+ }, tracing.sessionID)
551
+ }
552
+ }
553
+ }
554
+
555
+ const setSkippedResponse = async (convertedRequest, request, status, tracing, testCase, scriptsExecution, globalConfig) => {
556
+ let assertionResults = null
557
+ if (globalConfig.testsExecution) {
558
+ assertionResults = await setAllTestsSkipped(convertedRequest)
559
+ }
560
+ request.appended = {
561
+ status,
562
+ assertionResults,
563
+ response: null,
564
+ callback: null,
565
+ request: convertedRequest,
566
+ additionalInfo: {
567
+ curlRequest: null
568
+ }
569
+ }
570
+
571
+ // Update total progress counts
572
+ globalConfig.totalProgress.requestsProcessed++
573
+ globalConfig.totalProgress.assertionsProcessed += request.tests && request.tests.assertions && request.tests.assertions.length
574
+ globalConfig.totalProgress.assertionsPassed += assertionResults.passedCount
575
+ // globalConfig.totalProgress.assertionsFailed += 0
576
+
577
+ if (tracing.outboundID && globalConfig.broadcastOutboundProgressEnabled) {
578
+ notificationEmitter.broadcastOutboundProgress({
579
+ outboundID: tracing.outboundID,
580
+ testCaseId: testCase.id,
581
+ testCaseName: testCase.name,
582
+ status,
583
+ requestId: request.id,
584
+ response: null,
585
+ callback: null,
586
+ requestSent: convertedRequest,
587
+ additionalInfo: {
588
+ curlRequest: null,
589
+ scriptsExecution
590
+ },
591
+ testResult: assertionResults, // This should be changed, but it breaks UI. So keeping it for now.
592
+ totalProgress: globalConfig.totalProgress
593
+ }, tracing.sessionID)
594
+ }
595
+ }
596
+
597
+ const executePreRequestScript = async (convertedRequest, scriptsExecution, contextObj, variableData) => {
598
+ if (convertedRequest.scripts && convertedRequest.scripts.preRequest && convertedRequest.scripts.preRequest.exec.length > 0) {
599
+ let context = postmanContext
600
+ if (convertedRequest.scriptingEngine && convertedRequest.scriptingEngine === 'javascript') {
601
+ context = javascriptContext
602
+ }
603
+ const requestToPass = {
604
+ url: convertedRequest.url,
605
+ method: convertedRequest.method,
606
+ path: convertedRequest.path,
607
+ queryParams: convertedRequest.queryParams,
608
+ headers: convertedRequest.headers,
609
+ body: convertedRequest.body
610
+ }
611
+ scriptsExecution.preRequest = await context.executeAsync(convertedRequest.scripts.preRequest.exec, { context: { request: requestToPass }, id: uuid.v4() }, contextObj)
612
+ variableData.environment = scriptsExecution.preRequest.environment
613
+ }
614
+ }
615
+
616
+ const executePostRequestScript = async (convertedRequest, resp, scriptsExecution, contextObj, variableData, backgroundData) => {
617
+ if (convertedRequest.scripts && convertedRequest.scripts.postRequest && convertedRequest.scripts.postRequest.exec.length > 0) {
618
+ let response
619
+ if (_.isString(resp)) {
620
+ response = resp
621
+ } else if (resp.syncResponse) {
622
+ response = { code: resp.syncResponse.status, status: resp.syncResponse.statusText, body: resp.syncResponse.body || resp.syncResponse.data }
623
+ }
624
+
625
+ let callback
626
+ if (resp.callback) {
627
+ callback = resp.callback
628
+ }
629
+
630
+ // Pass the requestsHistory and callbacksHistory to postman sandbox
631
+ const collectionVariables = []
632
+ collectionVariables.push(
633
+ {
634
+ type: 'any',
635
+ key: 'requestsHistory',
636
+ value: JSON.stringify(backgroundData.requestsHistory)
637
+ },
638
+ {
639
+ type: 'any',
640
+ key: 'callbacksHistory',
641
+ value: JSON.stringify(backgroundData.callbacksHistory)
642
+ }
643
+ )
644
+ let context = postmanContext
645
+ if (convertedRequest.scriptingEngine && convertedRequest.scriptingEngine === 'javascript') {
646
+ context = javascriptContext
647
+ }
648
+ scriptsExecution.postRequest = await context.executeAsync(convertedRequest.scripts.postRequest.exec, { context: { response, callback, collectionVariables }, id: uuid.v4() }, contextObj)
649
+ variableData.environment = scriptsExecution.postRequest.environment
650
+ }
651
+ }
652
+
653
+ const handleTests = async (request, requestSent, response = null, callback = null, environment = {}, backgroundData = {}, requestVariables = {}) => {
654
+ try {
655
+ const results = {}
656
+ let passedCount = 0
657
+ let isFailed = false
658
+ if (request.tests && request.tests.assertions.length > 0) {
659
+ for (const k in request.tests.assertions) {
660
+ const assertion = request.tests.assertions[k]
661
+ try {
662
+ let status = 'SKIPPED'
663
+ const expect = (args) => { // eslint-disable-line
664
+ status = 'SUCCESS'
665
+ return expectOriginal(args)
666
+ }
667
+ const testsString = assertion.exec.join('\n')
668
+
669
+ eval(testsString) // eslint-disable-line
670
+ results[assertion.id] = {
671
+ status
672
+ }
673
+ passedCount++
674
+ } catch (err) {
675
+ console.log(`error during eval testsString [assertion.id: ${assertion.id}]:`, err)
676
+ isFailed = true
677
+ results[assertion.id] = {
678
+ status: 'FAILED',
679
+ message: err.message
680
+ }
681
+ }
682
+ }
683
+ }
684
+ return { results, passedCount, isFailed }
685
+ } catch (err) {
686
+ console.log('error in handleTests:', err)
687
+ return null
688
+ }
689
+ }
690
+
691
+ const setAllTestsSkipped = async (request) => {
692
+ const results = {}
693
+ let passedCount = 0
694
+ if (request.tests && request.tests.assertions.length > 0) {
695
+ for (const k in request.tests.assertions) {
696
+ const testCase = request.tests.assertions[k]
697
+ results[testCase.id] = {
698
+ status: 'SKIPPED'
699
+ }
700
+ passedCount++
701
+ }
702
+ }
703
+ return { results, passedCount }
704
+ }
705
+
706
+ const getUrlPrefix = (baseUrl) => {
707
+ let returnUrl = baseUrl
708
+ if (!returnUrl.startsWith('http:') && !returnUrl.startsWith('https:')) {
709
+ returnUrl = 'http://' + returnUrl
710
+ }
711
+ if (returnUrl.endsWith('/')) {
712
+ returnUrl = returnUrl.slice(0, returnUrl.length - 1)
713
+ }
714
+ return returnUrl
715
+ }
716
+
717
+ const sendRequest = (convertedRequest, successCallbackUrl, errorCallbackUrl, dfspId, contextObj = {}) => {
718
+ const transformerObj = contextObj.transformerObj
719
+ const { url, method, path, queryParams, headers, body, params, ignoreCallbacks } = convertedRequest
720
+ const baseUrl = url
721
+ return new Promise((resolve, reject) => {
722
+ (async () => {
723
+ const httpAgentProps = {}
724
+ const user = dfspId ? { dfspId } : undefined
725
+ const userConfig = await Config.getUserConfig(user)
726
+ const uniqueId = UniqueIdGenerator.generateUniqueId()
727
+ let urlGenerated = userConfig.CALLBACK_ENDPOINT + path
728
+ if (baseUrl) {
729
+ urlGenerated = getUrlPrefix(baseUrl) + path
730
+ }
731
+ if (Config.getSystemConfig().OUTBOUND_MUTUAL_TLS_ENABLED) {
732
+ const tlsConfig = await ConnectionProvider.getTlsConfig()
733
+ if (!tlsConfig.dfsps[dfspId]) {
734
+ const errorMsg = 'Outbound TLS is enabled, but there is no TLS config found for DFSP ID: ' + dfspId
735
+ customLogger.logMessage('error', errorMsg, { user })
736
+ return reject(new Error(JSON.stringify({ errorCode: 4000, errorDescription: errorMsg })))
737
+ }
738
+ httpAgentProps.httpsAgent = new https.Agent({
739
+ cert: tlsConfig.dfsps[dfspId].hubClientCert,
740
+ key: tlsConfig.hubClientKey,
741
+ ca: [tlsConfig.dfsps[dfspId].dfspServerCaRootCert],
742
+ rejectUnauthorized: true
743
+ })
744
+ httpAgentProps.httpsAgent.toJSON = () => ({})
745
+ urlGenerated = urlGenerated.replace('http:', 'https:')
746
+ } else if (userConfig.CLIENT_MUTUAL_TLS_ENABLED) {
747
+ const urlObject = new URL(urlGenerated)
748
+ const cred = userConfig.CLIENT_TLS_CREDS.filter(item => item.HOST === urlObject.host)
749
+ if (Array.isArray(cred) && cred.length === 1) {
750
+ customLogger.logMessage('info', `Found the Client certificate for ${urlObject.host}`, { notification: false })
751
+ httpAgentProps.httpsAgent = httpAgentStore.getHttpsAgent(urlObject.host, {
752
+ cert: cred[0].CERT,
753
+ key: cred[0].KEY,
754
+ rejectUnauthorized: false
755
+ })
756
+ httpAgentProps.httpsAgent.toJSON = () => ({})
757
+ urlGenerated = urlGenerated.replace('http:', 'https:')
758
+ } else {
759
+ const errorMsg = `client mutual TLS is enabled, but there is no TLS config found for ${urlObject.host}`
760
+ customLogger.logMessage('error', errorMsg, { notification: false })
761
+ }
762
+ } else {
763
+ if (urlGenerated.startsWith('https:')) {
764
+ httpAgentProps.httpsAgent = httpAgentStore.getHttpsAgent('generic', {
765
+ rejectUnauthorized: false
766
+ })
767
+ } else {
768
+ httpAgentProps.httpAgent = httpAgentStore.getHttpAgent('generic')
769
+ }
770
+ }
771
+
772
+ const transformedRequest = {}
773
+
774
+ if (transformerObj && transformerObj.transformer && transformerObj.transformer.forwardTransform) {
775
+ const result = await transformerObj.transformer.forwardTransform({ method, path, headers, body, params })
776
+ transformedRequest.body = result.body
777
+ transformedRequest.headers = result.headers
778
+ }
779
+
780
+ const reqOpts = {
781
+ method,
782
+ url: urlGenerated,
783
+ path,
784
+ params: queryParams,
785
+ headers: transformedRequest.headers || headers,
786
+ data: transformedRequest.body || body,
787
+ timeout: (contextObj.requestVariables && contextObj.requestVariables.REQUEST_TIMEOUT) || userConfig.DEFAULT_REQUEST_TIMEOUT,
788
+ validateStatus: function (status) {
789
+ return status < 900 // Reject only if the status code is greater than or equal to 900
790
+ },
791
+ ...httpAgentProps
792
+ }
793
+
794
+ if (contextObj.requestVariables && contextObj.requestVariables.TTK_JWS_SIGN_KEY) {
795
+ try {
796
+ await JwsSigning.signWithKey(reqOpts, contextObj.requestVariables.TTK_JWS_SIGN_KEY)
797
+ } catch (err) {
798
+ customLogger.logMessage('error', err.message, { additionalData: err })
799
+ }
800
+ } else {
801
+ try {
802
+ await JwsSigning.sign(reqOpts)
803
+ customLogger.logOutboundRequest('info', 'JWS signed', { uniqueId, request: reqOpts })
804
+ } catch (err) {
805
+ customLogger.logMessage('error', err.message, { additionalData: err })
806
+ }
807
+ }
808
+
809
+ const requestSent = {
810
+ url: reqOpts.url,
811
+ method: reqOpts.method,
812
+ path: reqOpts.path,
813
+ headers: reqOpts.headers,
814
+ body: reqOpts.data
815
+ }
816
+
817
+ let syncResponse = {}
818
+ let curlRequest = ''
819
+ let timer = null
820
+ if (successCallbackUrl && errorCallbackUrl && (ignoreCallbacks !== true)) {
821
+ timer = setTimeout(() => {
822
+ MyEventEmitter.getEmitter('testOutbound', user).removeAllListeners(successCallbackUrl)
823
+ MyEventEmitter.getEmitter('testOutbound', user).removeAllListeners(errorCallbackUrl)
824
+ return reject(new Error(JSON.stringify({ curlRequest, syncResponse, errorCode: 4001, errorMessage: 'Timeout for receiving callback' })))
825
+ }, userConfig.CALLBACK_TIMEOUT)
826
+ // Listen for success callback
827
+ MyEventEmitter.getEmitter('testOutbound', user).once(successCallbackUrl, async (_callbackHeaders, _callbackBody, _callbackMethod, _callbackPath) => {
828
+ clearTimeout(timer)
829
+ MyEventEmitter.getEmitter('testOutbound', user).removeAllListeners(errorCallbackUrl)
830
+ let callbackHeaders = _callbackHeaders
831
+ let callbackBody = _callbackBody
832
+ let originalBody
833
+ let originalHeaders
834
+ if (transformerObj && transformerObj.transformer && transformerObj.transformer.reverseTransform) {
835
+ const result = await transformerObj.transformer.reverseTransform({ method: _callbackMethod, path: _callbackPath, headers: _callbackHeaders, body: _callbackBody })
836
+ originalBody = _callbackBody
837
+ callbackBody = result.body
838
+ originalHeaders = _callbackHeaders
839
+ callbackHeaders = result.headers
840
+ }
841
+ customLogger.logMessage('info', 'Received success callback ' + successCallbackUrl, { request: { headers: callbackHeaders, body: callbackBody }, notification: false })
842
+ return resolve({ curlRequest, requestSent, transformedRequest, syncResponse, callback: { url: successCallbackUrl, headers: callbackHeaders, body: callbackBody, originalHeaders, originalBody } })
843
+ })
844
+ // Listen for error callback
845
+ MyEventEmitter.getEmitter('testOutbound', user).once(errorCallbackUrl, async (_callbackHeaders, _callbackBody, _callbackMethod, _callbackPath) => {
846
+ clearTimeout(timer)
847
+ MyEventEmitter.getEmitter('testOutbound', user).removeAllListeners(successCallbackUrl)
848
+ let callbackHeaders = _callbackHeaders
849
+ let callbackBody = _callbackBody
850
+ let originalBody
851
+ let originalHeaders
852
+ if (transformerObj && transformerObj.transformer && transformerObj.transformer.reverseTransform) {
853
+ const result = await transformerObj.transformer.reverseTransform({ method: _callbackMethod, path: _callbackPath, headers: _callbackHeaders, body: _callbackBody })
854
+ originalBody = _callbackBody
855
+ callbackBody = result.body
856
+ originalHeaders = _callbackHeaders
857
+ callbackHeaders = result.headers
858
+ }
859
+ customLogger.logMessage('info', 'Received error callback ' + errorCallbackUrl, { request: { headers: callbackHeaders, body: callbackBody }, notification: false })
860
+ return reject(new Error(JSON.stringify({ curlRequest, requestSent, transformedRequest, syncResponse, callback: { url: errorCallbackUrl, headers: callbackHeaders, body: callbackBody, originalHeaders, originalBody } })))
861
+ })
862
+ }
863
+
864
+ customLogger.logOutboundRequest('info', 'Sending request ' + reqOpts.method + ' ' + reqOpts.url, { additionalData: { request: reqOpts }, user, uniqueId, request: reqOpts })
865
+
866
+ axios(reqOpts).then((result) => {
867
+ syncResponse = {
868
+ status: result.status,
869
+ statusText: result.statusText,
870
+ body: result.data,
871
+ headers: result.headers
872
+ }
873
+ curlRequest = result.request ? result.request.toCurl() : ''
874
+
875
+ if (result.status > 299) {
876
+ customLogger.logOutboundRequest('error', 'Received response ' + result.status + ' ' + result.statusText, { additionalData: { response: result }, user, uniqueId, request: reqOpts })
877
+ if (timer) {
878
+ clearTimeout(timer)
879
+ MyEventEmitter.getEmitter('testOutbound', user).removeAllListeners(successCallbackUrl)
880
+ MyEventEmitter.getEmitter('testOutbound', user).removeAllListeners(errorCallbackUrl)
881
+ }
882
+ return reject(new Error(JSON.stringify({ curlRequest, requestSent, transformedRequest, syncResponse })))
883
+ } else {
884
+ customLogger.logOutboundRequest('info', 'Received response ' + result.status + ' ' + result.statusText, { additionalData: { response: result }, user, uniqueId, request: reqOpts })
885
+ }
886
+
887
+ if (!successCallbackUrl || !errorCallbackUrl || ignoreCallbacks) {
888
+ return resolve({ curlRequest, requestSent, transformedRequest, syncResponse })
889
+ }
890
+ customLogger.logMessage('info', 'Received response ' + result.status + ' ' + result.statusText, { additionalData: result.data, notification: false, user })
891
+ }, (err) => {
892
+ syncResponse = {
893
+ status: 500,
894
+ statusText: err.message
895
+ }
896
+ curlRequest = err.request._currentRequest ? err.request._currentRequest.toCurl() : ''
897
+ customLogger.logOutboundRequest('error', 'Failed to send request ' + method + ' Error: ' + err.message, { additionalData: { errorStack: err.stack }, user, uniqueId, request: reqOpts })
898
+ customLogger.logMessage('error', 'Failed to send request ' + method + ' Error: ' + err.message, { additionalData: { errorStack: err.stack }, notification: false, user })
899
+ return reject(new Error(JSON.stringify({ errorCode: 4000, curlRequest, requestSent, transformedRequest, syncResponse })))
900
+ })
901
+ })()
902
+ })
903
+ }
904
+
905
+ const setResultObject = (inputObject) => {
906
+ if (typeof inputObject === 'string') {
907
+ return inputObject
908
+ } else if (typeof inputObject === 'object') {
909
+ return JSON.stringify(inputObject)
910
+ }
911
+ }
912
+
913
+ const replaceVariables = (inputObject, inputValues, request, requestsObj, templateOptions) => {
914
+ let resultObject = setResultObject(inputObject)
915
+ if (!resultObject) {
916
+ return inputObject
917
+ }
918
+ // Check the string for any inclusions like {$some_param}
919
+ const matchedArray = resultObject.match(/{\$([^}]+)}/g)
920
+ if (matchedArray) {
921
+ matchedArray.forEach(element => {
922
+ // Check for the function type of param, if its function we need to call a function in custom-functions and replace the returned value
923
+ const splitArr = element.split('.')
924
+ switch (splitArr[0]) {
925
+ case '{$function': {
926
+ resultObject = resultObject.replace(element, getFunctionResult(element, templateOptions, request))
927
+ break
928
+ }
929
+ case '{$prev': {
930
+ const temp = element.replace(/{\$prev.(.*)}/, '$1')
931
+ const tempArr = temp.split('.')
932
+ try {
933
+ const replacedValue = _.get(requestsObj[tempArr[0]].appended, temp.replace(tempArr[0] + '.', ''))
934
+ if (replacedValue) {
935
+ resultObject = resultObject.replace(element, replacedValue)
936
+ }
937
+ } catch (err) {
938
+ customLogger.logMessage('error', `${element} not found`, { notification: false })
939
+ }
940
+ break
941
+ }
942
+ case '{$request': {
943
+ const temp = element.replace(/{\$request.(.*)}/, '$1')
944
+ const replacedValue = _.get(request, temp)
945
+ if (replacedValue && !replacedValue.startsWith('{$')) {
946
+ resultObject = resultObject.replace(element, replacedValue)
947
+ }
948
+ break
949
+ }
950
+ case '{$inputs': {
951
+ const temp = element.replace(/{\$inputs.(.*)}/, '$1')
952
+ if (inputValues[temp]) {
953
+ resultObject = resultObject.replace(element, inputValues[temp])
954
+ }
955
+ break
956
+ }
957
+ default:
958
+ break
959
+ }
960
+ })
961
+ }
962
+
963
+ return (typeof inputObject === 'object') ? JSON.parse(resultObject) : resultObject
964
+ }
965
+
966
+ const replaceRequestVariables = (inputRequest) => {
967
+ return _replaceGenericVariables(inputRequest, inputRequest, 'request')
968
+ }
969
+
970
+ const replaceEnvironmentVariables = (inputRequest, environment) => {
971
+ return _replaceGenericVariables(inputRequest, environment, 'environment')
972
+ }
973
+
974
+ const replaceRequestLevelEnvironmentVariables = (inputRequest, requestVariables) => {
975
+ return _replaceGenericVariables(inputRequest, requestVariables, 'requestVariables')
976
+ }
977
+
978
+ const _replaceGenericVariables = (inputRequest, replaceObject, variablePrefix) => {
979
+ let resultObject = setResultObject(inputRequest)
980
+ if (!resultObject) {
981
+ return inputRequest
982
+ }
983
+
984
+ // Check once again for the replaced request variables
985
+ const matchedArray = resultObject.match(/{\$([^}]+)}/g)
986
+ if (matchedArray) {
987
+ matchedArray.forEach(element => {
988
+ // Check for the function type of param, if its function we need to call a function in custom-functions and replace the returned value
989
+ const splitArr = element.split('.')
990
+ if (splitArr[0] === '{$' + variablePrefix) {
991
+ const regExp1 = new RegExp('{\\$' + variablePrefix + '.(.*)}')
992
+ const temp2 = element.replace(regExp1, '$1')
993
+ const replacedValue2 = _.get(replaceObject, temp2)
994
+ if (replacedValue2 !== undefined) {
995
+ resultObject = resultObject.replace(element, replacedValue2)
996
+ }
997
+ }
998
+ })
999
+ }
1000
+
1001
+ return (typeof inputRequest === 'object') ? JSON.parse(resultObject) : resultObject
1002
+ }
1003
+
1004
+ const replacePathVariables = (operationPath, params) => {
1005
+ let resultObject = operationPath
1006
+
1007
+ // Check the string for any inclusions like {$some_param}
1008
+ const matchedArray = resultObject.match(/{([^}]+)}/g)
1009
+ if (matchedArray) {
1010
+ matchedArray.forEach(element => {
1011
+ const temp = element.replace(/{([^}]+)}/, '$1')
1012
+ if (params && params[temp]) {
1013
+ resultObject = resultObject.replace(element, params[temp])
1014
+ }
1015
+ })
1016
+ }
1017
+
1018
+ return resultObject
1019
+ }
1020
+
1021
+ // Execute the function and return the result
1022
+ const getFunctionResult = (param, templateOptions, request) => {
1023
+ return utilsInternal.getFunctionResult(param, templateOptions, request)
1024
+ }
1025
+
1026
+ // Get Total Counts
1027
+ const getTotalCounts = (inputTemplate) => {
1028
+ const result = {
1029
+ totalTestCases: 0,
1030
+ totalRequests: 0,
1031
+ totalAssertions: 0
1032
+ }
1033
+ const isParallelRun = Config.getSystemConfig().PARALLEL_RUN_ENABLED && inputTemplate.batchSize > 1
1034
+ const testCasesToRun = []
1035
+
1036
+ inputTemplate.test_cases.forEach(testCase => {
1037
+ if (isParallelRun && typeof testCase.options?.executionOrder !== 'number') return
1038
+ // todo: - think, if we need to skip testCases without executionOrder (in parallel run)
1039
+ // - optimise the logic to avoid additional looping in runAll (define executionBuckets here)
1040
+ // - check if all testCases should have "meta" field
1041
+ testCasesToRun.push(testCase)
1042
+ result.totalRequests += testCase.requests.length
1043
+ testCase.requests.forEach(request => {
1044
+ result.totalAssertions += (request.tests?.assertions?.length || 0)
1045
+ })
1046
+ })
1047
+ inputTemplate.test_cases = testCasesToRun
1048
+ result.totalTestCases = testCasesToRun.length
1049
+
1050
+ return result
1051
+ }
1052
+
1053
+ // Generate consolidated final report
1054
+ const generateFinalReport = (inputTemplate, runtimeInformation, metrics) => {
1055
+ const { test_cases, ...remaingPropsInTemplate } = inputTemplate
1056
+ const resultTestCases = test_cases.map(testCase => {
1057
+ const { requests, ...remainingPropsInTestCase } = testCase
1058
+ const resultRequests = requests.map(requestItem => {
1059
+ const { assertionResults, request, ...remainginPropsInRequest } = requestItem.appended
1060
+ if (request?.tests?.assertions) {
1061
+ request.tests.assertions = request.tests.assertions.map(assertion => {
1062
+ return {
1063
+ ...assertion,
1064
+ resultStatus: assertionResults.results[assertion.id]
1065
+ }
1066
+ })
1067
+ request.tests.passedAssertionsCount = assertionResults.passedCount
1068
+ runtimeInformation.totalAssertions += request.tests.assertions.length
1069
+ runtimeInformation.totalPassedAssertions += request.tests.passedAssertionsCount
1070
+ }
1071
+ return {
1072
+ request,
1073
+ ...remainginPropsInRequest
1074
+ }
1075
+ })
1076
+ return {
1077
+ ...remainingPropsInTestCase,
1078
+ requests: resultRequests
1079
+ }
1080
+ })
1081
+ if (runtimeInformation.totalPassedAssertions === runtimeInformation.totalAssertions) {
1082
+ runtimeInformation.isPassed = true
1083
+ metrics?.testSuccess.add(1, { template: inputTemplate.name })
1084
+ } else {
1085
+ metrics?.testFail.add(1, { template: inputTemplate.name })
1086
+ }
1087
+ return {
1088
+ ...remaingPropsInTemplate,
1089
+ test_cases: resultTestCases,
1090
+ runtimeInformation
1091
+ }
1092
+ }
1093
+
1094
+ module.exports = {
1095
+ OutboundSend,
1096
+ OutboundSendLoop,
1097
+ terminateOutbound,
1098
+ handleTests,
1099
+ sendRequest,
1100
+ replaceVariables,
1101
+ replaceRequestVariables,
1102
+ replaceEnvironmentVariables,
1103
+ replaceRequestLevelEnvironmentVariables,
1104
+ replacePathVariables,
1105
+ getFunctionResult,
1106
+ generateFinalReport
1107
+ }