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,466 @@
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
+
31
+ 'use strict'
32
+
33
+ const addFormats = require('ajv-formats')
34
+ const OpenApiBackend = require('openapi-backend').default
35
+ const OpenApiDefinitionsModel = require('./openApiDefinitionsModel')
36
+ const OpenApiVersionTools = require('./openApiVersionTools')
37
+ const customLogger = require('../requestLogger')
38
+ const OpenApiRulesEngine = require('./openApiRulesEngine')
39
+ const CallbackHandler = require('../callbackHandler')
40
+ const _ = require('lodash')
41
+ const MyEventEmitter = require('../MyEventEmitter')
42
+ const utils = require('../utils')
43
+ const Config = require('../config')
44
+ const objectStore = require('../objectStore')
45
+ const arrayStore = require('../arrayStore')
46
+ const JwsSigning = require('../jws/JwsSigning')
47
+
48
+ const path = require('path')
49
+
50
+ const IlpModel = require('./middleware-functions/ilpModel')
51
+ const { generateULID } = require('./custom-functions/generic')
52
+
53
+ let apis = []
54
+
55
+ // TODO: Implement a logger and log the messages with different verbosity
56
+ // TODO: Write unit tests
57
+
58
+ /**
59
+ * Operations on /
60
+ */
61
+ module.exports.initilizeMockHandler = async () => {
62
+ // Initialize ILP
63
+ IlpModel.init((await Config.getUserConfig()).ILP_SECRET)
64
+ // Get API Definitions from configuration
65
+ const apiDefinitions = await OpenApiDefinitionsModel.getApiDefinitions()
66
+ // Create create openApiBackend objects for all the api definitions
67
+ apis = apiDefinitions.map(item => {
68
+ const tempObj = new OpenApiBackend({
69
+ definition: path.join(item.specFile),
70
+ customizeAjv: ajv => addFormats(ajv),
71
+ strict: true,
72
+ quick: true,
73
+ handlers: {
74
+ notImplemented: async (context, req, h) => {
75
+ const resp = await openApiBackendNotImplementedHandler(context, req, h, item)
76
+ return resp
77
+ },
78
+ validationFail: async (context, req, h) => {
79
+ const extensionList = [{
80
+ key: 'keyword',
81
+ value: context.validation.errors[0].keyword
82
+ },
83
+ {
84
+ key: 'instancePath',
85
+ value: context.validation.errors[0].instancePath
86
+ }]
87
+ for (const [key, value] of Object.entries(context.validation.errors[0].params)) {
88
+ extensionList.push({ key, value })
89
+ }
90
+ return h.response(errorResponseBuilder('3100', context.validation.errors[0].message, { extensionList })).code(400)
91
+ },
92
+ notFound: async (context, req, h) => {
93
+ customLogger.logMessage('error', 'Resource not found', { request: req })
94
+ return h.response({ error: 'Not Found' }).code(404)
95
+ }
96
+ }
97
+ })
98
+
99
+ return {
100
+ ...item,
101
+ openApiBackendObject: tempObj
102
+ }
103
+ })
104
+
105
+ for (const api of apis) {
106
+ customLogger.logMessage('info', 'Initializing the api spec file: ' + api.specFile, { notification: false })
107
+ await api.openApiBackendObject.init()
108
+ }
109
+ }
110
+
111
+ module.exports.handleRequest = async (req, h) => {
112
+ // JWS Validation
113
+ try {
114
+ const jwsValidated = await JwsSigning.validate(req)
115
+ if (jwsValidated) {
116
+ customLogger.logMessage('debug', 'JWS Signature Validated', { request: req })
117
+ }
118
+ } catch (err) {
119
+ // TODO: The errorCode should be checked with the api specification
120
+ customLogger.logMessage('error', 'JWS validation failed', { additionalData: err.message, request: req })
121
+ return h.response(errorResponseBuilder('3105', 'Invalid signature')).code(400)
122
+ }
123
+
124
+ let selectedApi
125
+ // Pick a right api definition by searching for the requested resource in the definitions sequentially
126
+ // TODO: This should be optimized by defining a hash table (object) of all the resources in all definition files on startup.
127
+
128
+ const pickedApis = pickApiByMethodPathHostnameAndPrefix(req)
129
+
130
+ // Let's pick the first one for now and swap it with the right version later
131
+ selectedApi = pickedApis.length > 0 ? pickedApis[0] : null
132
+
133
+ if (!selectedApi) {
134
+ customLogger.logMessage('error', 'Resource not found', { request: req })
135
+ return h.response({ error: 'Not Found' }).code(404)
136
+ }
137
+
138
+ // Modify request
139
+ if (selectedApi && selectedApi.prefix) {
140
+ req.path = req.path.slice(selectedApi.prefix.length)
141
+ customLogger.logMessage('info', 'Trimmed prefix from request path: ' + selectedApi.prefix, { request: req })
142
+ }
143
+
144
+ if ((selectedApi.type === 'fspiop' || selectedApi.type === 'iso20022') && (await Config.getUserConfig(req.customInfo.user)).VERSIONING_SUPPORT_ENABLE) {
145
+ // Validate accept header for POST & GET.
146
+ if (req.method === 'post' || req.method === 'get') {
147
+ // Validate the accept header here
148
+ const acceptHeaderValidationResult = OpenApiVersionTools.validateAcceptHeader(req.headers.accept)
149
+ if (acceptHeaderValidationResult.validationFailed) {
150
+ return h.response(errorResponseBuilder('3001', acceptHeaderValidationResult.message)).code(400)
151
+ }
152
+ } else {
153
+ // Validate content-type header for all the remaining requests
154
+ const contentTypeHeaderValidationResult = OpenApiVersionTools.validateContentTypeHeader(req.headers['content-type'])
155
+ if (contentTypeHeaderValidationResult.validationFailed) {
156
+ return h.response(errorResponseBuilder('3001', contentTypeHeaderValidationResult.message)).code(400)
157
+ }
158
+ }
159
+ // Pick the right API object based on the major and minor versions (Version negotiation as per the API Definition file)
160
+ const versionNegotiationResult = OpenApiVersionTools.negotiateVersion(req, pickedApis)
161
+ if (versionNegotiationResult.negotiationFailed) {
162
+ // Create extensionList property as per the API specification document with supported versions
163
+ const extensionList = pickedApis.map(item => {
164
+ return {
165
+ key: item.majorVersion + '',
166
+ value: item.minorVersion + ''
167
+ }
168
+ })
169
+ return h.response(errorResponseBuilder('3001', 'The Client requested an unsupported version, see extension list for supported version(s).', { extensionList })).code(406)
170
+ }
171
+ req.customInfo.negotiatedContentType = versionNegotiationResult.responseContentTypeHeader
172
+ selectedApi = pickedApis[versionNegotiationResult.negotiatedIndex]
173
+ req.customInfo.selectedApi = selectedApi
174
+ }
175
+ customLogger.logMessage('info', 'API matched: ' + selectedApi.type, { request: req })
176
+ try {
177
+ return await selectedApi.openApiBackendObject.handleRequest(
178
+ {
179
+ method: req.method,
180
+ path: req.path,
181
+ body: req.payload,
182
+ query: req.query,
183
+ headers: req.headers
184
+ },
185
+ req,
186
+ h
187
+ )
188
+ } catch (err) {
189
+ customLogger.logMessage('error', err.message, { request: req })
190
+ return h.response({ error: 'Not Found' }).code(404)
191
+ }
192
+ }
193
+
194
+ const pickApiByMethodPathHostnameAndPrefix = (req) => {
195
+ let potentialApis = apis
196
+
197
+ // Match path
198
+ const matchedPrefixApis = apis.filter(item => {
199
+ return item.prefix ? req.path.startsWith(item.prefix) : false
200
+ })
201
+ if (matchedPrefixApis.length > 0) {
202
+ return matchedPrefixApis.filter(item => {
203
+ return item.openApiBackendObject.matchOperation({ ...req, path: req.path.slice(item.prefix.length) })
204
+ })
205
+ } else {
206
+ // apis without prefix
207
+ potentialApis = potentialApis.filter(item => {
208
+ return !item.prefix
209
+ })
210
+ }
211
+
212
+ // Match hostnames
213
+ const matchedHostnameApis = potentialApis.filter(item => {
214
+ return item.hostnames && req.info && req.info.hostname ? item.hostnames.includes(req.info.hostname) : false
215
+ })
216
+ potentialApis = matchedHostnameApis.length > 0 ? matchedHostnameApis : potentialApis.filter(item => !item.hostnames || item.hostnames.length === 0)
217
+
218
+ // FSPIOP and ISO20022 version negotiation
219
+ let headerToCompare = req.headers['content-type']
220
+ if (req.method === 'get') {
221
+ headerToCompare = req.headers.accept
222
+ }
223
+ const headerComparisonResult = OpenApiVersionTools.parseAcceptHeader(headerToCompare)
224
+ if (headerComparisonResult && (headerComparisonResult.apiType === 'fspiop' || headerComparisonResult.apiType === 'iso20022')) {
225
+ potentialApis = potentialApis.filter(item => {
226
+ return item.type === headerComparisonResult.apiType
227
+ })
228
+ } else {
229
+ // Filter out the apis fspiop or iso20022
230
+ potentialApis = potentialApis.filter(item => {
231
+ return item.type !== 'fspiop' && item.type !== 'iso20022'
232
+ })
233
+ }
234
+
235
+ const pickedApis = potentialApis.filter(item => {
236
+ return item.openApiBackendObject.matchOperation(req)
237
+ })
238
+
239
+ return pickedApis
240
+ }
241
+
242
+ const errorResponseBuilder = (errorCode, errorDescription, additionalProperties = null) => {
243
+ return {
244
+ errorInformation: {
245
+ errorCode,
246
+ errorDescription,
247
+ ...additionalProperties
248
+ }
249
+ }
250
+ }
251
+
252
+ module.exports.getOpenApiObjects = () => {
253
+ return apis
254
+ }
255
+
256
+ const openApiBackendNotImplementedHandler = async (context, req, h, item) => {
257
+ customLogger.logMessage('debug', 'Schema Validation Passed', { request: req })
258
+ MyEventEmitter.getEmitter('inboundRequest', req.customInfo.user).emit('newInbound', { method: req.method, path: req.path, headers: req.headers, body: req.payload })
259
+ if (req.method === 'put') {
260
+ MyEventEmitter.getEmitter('testOutbound', req.customInfo.user).emit(req.method + ' ' + req.path, req.headers, req.payload, req.method, req.path)
261
+ let assertionPath = req.path
262
+ const assertionData = { headers: req.headers, body: req.payload }
263
+ if (assertionPath.endsWith('/error')) {
264
+ assertionPath = assertionPath.replace('/error', '')
265
+ assertionData.error = true
266
+ }
267
+ objectStore.push('requests', assertionPath, assertionData)
268
+ MyEventEmitter.getEmitter('assertionRequest', req.customInfo.user).emit(assertionPath, assertionData)
269
+ }
270
+ // Store all the inbound requests
271
+ arrayStore.push('requestsHistory', { timestamp: Date.now(), method: req.method, path: req.path, headers: req.headers, body: req.payload })
272
+
273
+ req.customInfo.specFile = item.specFile
274
+ req.customInfo.jsfRefFile = item.jsfRefFile
275
+ let responseBody, responseStatus
276
+ // Check for response map file
277
+ try {
278
+ const respMapRawdata = await utils.readFileAsync(item.responseMapFile)
279
+ const responseMap = JSON.parse(respMapRawdata)
280
+ if (responseMap && responseMap[context.operation.path] && responseMap[context.operation.path][context.request.method]) {
281
+ const responseInfo = responseMap[context.operation.path][context.request.method]
282
+ req.customInfo.responseInfo = responseInfo
283
+ } else {
284
+ customLogger.logMessage('error', 'Response info not found for method in response map file for ' + context.request.method + ' ' + context.operation.path, { request: req })
285
+ return
286
+ }
287
+ } catch (err) { }
288
+ // Check for the synchronous response rules
289
+ const generatedResponse = await OpenApiRulesEngine.responseRules(context, req)
290
+ responseStatus = +generatedResponse.status
291
+ responseBody = generatedResponse.body
292
+ customLogger.logMessage('info', 'Generated response', { additionalData: generatedResponse, request: req })
293
+ if (generatedResponse.delay) {
294
+ await new Promise(resolve => setTimeout(resolve, generatedResponse.delay))
295
+ }
296
+ if (!responseBody) {
297
+ // Generate mock response from openAPI spec file
298
+ const { status, mock } = context.api.mockResponseForOperation(context.operation.operationId)
299
+ responseBody = mock
300
+ responseStatus = +status
301
+ }
302
+ // Verify that it is a success code, then generate callback
303
+ if (item.asynchronous && (req.method === 'post' || req.method === 'get' || req.method === 'put') && responseStatus >= 200 && responseStatus <= 299) {
304
+ // Generate callback asynchronously
305
+ setImmediate(generateAsyncCallback, item, context, req)
306
+ }
307
+ if (_.isEmpty(responseBody)) {
308
+ return h.response().code(responseStatus)
309
+ } else {
310
+ return h.response(responseBody).code(responseStatus)
311
+ }
312
+ }
313
+
314
+ const generateAsyncCallback = async (item, context, req) => {
315
+ const userConfig = await Config.getUserConfig(req.customInfo.user)
316
+ if (req.method === 'put') {
317
+ if (!userConfig.HUB_ONLY_MODE) {
318
+ return
319
+ }
320
+ } else {
321
+ // Getting callback info from callback map file
322
+ try {
323
+ const cbMapRawdata = await utils.readFileAsync(item.callbackMapFile)
324
+ const callbackMap = JSON.parse(cbMapRawdata)
325
+ if (!callbackMap[context.operation.path]) {
326
+ customLogger.logMessage('error', 'Callback not found for path in callback map file for ' + context.operation.path, req.customInfo.user, { request: req })
327
+ return
328
+ }
329
+ const callbackInfo = callbackMap[context.operation.path][context.request.method]
330
+ if (!callbackInfo) {
331
+ customLogger.logMessage('error', 'Callback info not found for method in callback map file for ' + context.operation.path + context.request.method, { request: req })
332
+ return
333
+ }
334
+ req.customInfo.callbackInfo = callbackInfo
335
+ } catch (err) {
336
+ customLogger.logMessage('error', 'Callback file not found.', { request: req })
337
+ return
338
+ }
339
+ // Additional validation based on the Json Rules Engine, send error callback on failure
340
+ const generatedErrorCallback = await OpenApiRulesEngine.validateRules(context, req)
341
+ if (generatedErrorCallback.skipCallback) {
342
+ return
343
+ }
344
+ if (generatedErrorCallback.body) {
345
+ // TODO: Handle method and path verifications against the generated ones
346
+ customLogger.logMessage('error', 'Sending error callback', { request: req })
347
+ CallbackHandler.handleCallback(generatedErrorCallback, context, req)
348
+ return
349
+ }
350
+ }
351
+
352
+ // Callback Rules engine - match the rules and generate the specified callback
353
+ const generatedCallback = await OpenApiRulesEngine.callbackRules(context, req)
354
+ if (generatedCallback.skipCallback) {
355
+ return
356
+ }
357
+
358
+ // forward request after validation
359
+ if (userConfig.HUB_ONLY_MODE) {
360
+ const forwardRequest = await OpenApiRulesEngine.forwardRules(context, req)
361
+ if (forwardRequest) {
362
+ CallbackHandler.handleCallback(forwardRequest, context, req)
363
+ }
364
+ return
365
+ }
366
+ // TODO: Need to handle iso20022 error payloads.
367
+ // Handle quotes and transfer association - should do this first to get the associated quote
368
+ if (userConfig.TRANSFERS_VALIDATION_WITH_PREVIOUS_QUOTES) {
369
+ const matchFound = require('./middleware-functions/quotesAssociation').handleTransfers(context, req)
370
+ if (!matchFound) {
371
+ customLogger.logMessage('error', 'Matching Quote Not Found', { request: req })
372
+ const generatedErrorCallback = await OpenApiRulesEngine.generateMockErrorCallback(context, req)
373
+ if (req.customInfo.selectedApi?.type === 'iso20022') {
374
+ _handleISO20022ErrorCallback(generatedErrorCallback, '3208')
375
+ } else {
376
+ generatedErrorCallback.body = {
377
+ errorInformation: {
378
+ errorCode: '3208',
379
+ errorDescription: 'Provided Transfer ID was not found on the server.'
380
+ }
381
+ }
382
+ }
383
+ CallbackHandler.handleCallback(generatedErrorCallback, context, req)
384
+ return
385
+ }
386
+ }
387
+
388
+ // Handle transfer validation against decoded ILP Packet
389
+ if (userConfig.TRANSFERS_VALIDATION_ILP_PACKET) {
390
+ const validated = IlpModel.validateTransferIlpPacket(context, req)
391
+ if (!validated) {
392
+ customLogger.logMessage('error', 'ILP Packet is not matching with the content', { request: req })
393
+ const generatedErrorCallback = await OpenApiRulesEngine.generateMockErrorCallback(context, req)
394
+ if (req.customInfo.selectedApi?.type === 'iso20022') {
395
+ _handleISO20022ErrorCallback(generatedErrorCallback, '3106')
396
+ } else {
397
+ generatedErrorCallback.body = {
398
+ errorInformation: {
399
+ errorCode: '3106',
400
+ errorDescription: 'ILP Packet is not matching with the content.'
401
+ }
402
+ }
403
+ }
404
+ CallbackHandler.handleCallback(generatedErrorCallback, context, req)
405
+ return
406
+ }
407
+ }
408
+
409
+ // Handle condition validation in transfer request
410
+ if (userConfig.TRANSFERS_VALIDATION_CONDITION) {
411
+ const validated = IlpModel.validateTransferCondition(context, req)
412
+ if (!validated) {
413
+ customLogger.logMessage('error', 'Condition can not be validated', { request: req })
414
+ const generatedErrorCallback = await OpenApiRulesEngine.generateMockErrorCallback(context, req)
415
+ if (req.customInfo.selectedApi?.type === 'iso20022') {
416
+ _handleISO20022ErrorCallback(generatedErrorCallback, '3106')
417
+ } else {
418
+ generatedErrorCallback.body = {
419
+ errorInformation: {
420
+ errorCode: '3106',
421
+ errorDescription: 'Condition can not be validated.'
422
+ }
423
+ }
424
+ }
425
+ CallbackHandler.handleCallback(generatedErrorCallback, context, req)
426
+ return
427
+ }
428
+ }
429
+
430
+ if (generatedCallback.body) {
431
+ // Append ILP properties to callback
432
+ const fulfilment = IlpModel.handleQuoteIlp(context, generatedCallback)
433
+ IlpModel.handleTransferIlp(context, generatedCallback)
434
+ if (userConfig.TRANSFERS_VALIDATION_WITH_PREVIOUS_QUOTES) {
435
+ require('./middleware-functions/quotesAssociation').handleQuotes(context, req, fulfilment)
436
+ }
437
+ // TODO: Handle method and path verifications against the generated ones
438
+ CallbackHandler.handleCallback(generatedCallback, context, req)
439
+ // Handle triggers for a transaction request
440
+ require('./middleware-functions/transactionRequestsService').handleRequest(context, req, generatedCallback, item.triggerTemplatesFolder)
441
+ }
442
+ }
443
+
444
+ const _handleISO20022ErrorCallback = (generatedErrorCallback, errorCode) => {
445
+ generatedErrorCallback.headers = {
446
+ ...generatedErrorCallback.headers,
447
+ 'content-type': 'application/vnd.interoperability.iso20022.transfers+json;version=2.0'
448
+ }
449
+ generatedErrorCallback.body = {
450
+ GrpHdr: {
451
+ MsgId: generateULID(),
452
+ CreDtTm: new Date().toISOString()
453
+ },
454
+ TxInfAndSts: {
455
+ StsRsnInf: {
456
+ Rsn: {
457
+ Cd: errorCode
458
+ }
459
+ }
460
+ }
461
+ }
462
+ return generatedErrorCallback
463
+ }
464
+
465
+ module.exports.openApiBackendNotImplementedHandler = openApiBackendNotImplementedHandler
466
+ module.exports.generateAsyncCallback = generateAsyncCallback