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,492 @@
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
+ * Vijaya Kumar Guthi <vijaya.guthi@modusbox.com> (Original Author)
27
+ --------------
28
+ ******/
29
+
30
+ const fs = require('fs')
31
+ const path = require('path')
32
+ const { promisify } = require('util')
33
+ const readFileAsync = promisify(fs.readFile)
34
+ const customLogger = require('../requestLogger')
35
+ const _ = require('lodash')
36
+ const rulesEngineModel = require('../rulesEngineModel')
37
+ const Config = require('../config')
38
+ const objectStore = require('../objectStore')
39
+ const utilsInternal = require('../utilsInternal')
40
+ const uuid = require('uuid')
41
+ const postmanContext = require('../scripting-engines/postman-sandbox')
42
+ const javascriptContext = require('../scripting-engines/vm-javascript-sandbox')
43
+ const { OpenApiMockGenerator } = require('@mojaloop/ml-testing-toolkit-shared-lib')
44
+
45
+ // const jsfRefFilePathPrefix = 'spec_files/jsf_ref_files/'
46
+
47
+ const removeEmpty = obj => {
48
+ if (obj) {
49
+ Object.keys(obj).forEach(key => {
50
+ if (obj[key] && typeof obj[key] === 'object') removeEmpty(obj[key])
51
+ else if (obj[key] == null) delete obj[key]
52
+ })
53
+ }
54
+ }
55
+
56
+ const executeScripts = async (curEvent, req) => {
57
+ if (curEvent.params.scripts && curEvent.params.scripts.exec && curEvent.params.scripts.exec.length > 0 && curEvent.params.scripts.exec !== ['']) {
58
+ const sandboxEnvironment = objectStore.get('inboundEnvironment')
59
+ customLogger.logMessage('debug', 'Inbound Script: Starting...', { additionalData: curEvent.params.scripts.exec, request: req })
60
+ let context = postmanContext
61
+ if (curEvent.params.scripts.scriptingEngine && curEvent.params.scripts.scriptingEngine === 'javascript') {
62
+ context = javascriptContext
63
+ }
64
+ const contextObj = await context.generateContextObj(sandboxEnvironment)
65
+
66
+ const postmanRequest = {
67
+ body: JSON.stringify(req.payload),
68
+ method: req.method,
69
+ headers: req.headers,
70
+ url: {
71
+ path: req.path,
72
+ query: Object.keys(req.query || {}).length === 0 ? [] : req.query
73
+ }
74
+ }
75
+ // Set global variables userConfig
76
+ const globals = []
77
+ const userConfig = await Config.getUserConfig(req.customInfo.user)
78
+ globals.push(
79
+ {
80
+ type: 'any',
81
+ key: 'userConfig',
82
+ value: JSON.stringify(userConfig)
83
+ }
84
+ )
85
+ const postmanSandbox = await context.executeAsync(curEvent.params.scripts.exec, { context: { ...contextObj, request: postmanRequest, globals }, id: uuid.v4() }, contextObj)
86
+
87
+ const additionalData = {
88
+ consoleLogArray: postmanSandbox.consoleLog && postmanSandbox.consoleLog.map(log => log[2]),
89
+ environment: postmanSandbox.environment
90
+ }
91
+ customLogger.logMessage('debug', 'Inbound Script: Executed', { additionalData, request: req })
92
+ // replace inbound environment with the sandbox environment
93
+ const mergedInboundEnvironment = postmanSandbox.environment
94
+ objectStore.set('inboundEnvironment', mergedInboundEnvironment)
95
+ // Mutating event based on script output
96
+ if (contextObj.requestVariables?.OVERRIDE_EVENT?.appendMode) {
97
+ customLogger.logMessage('debug', 'Mutating event body based on script', {
98
+ additionalData: contextObj.requestVariables.OVERRIDE_EVENT,
99
+ request: req
100
+ })
101
+ _.merge(curEvent.params.body, contextObj.requestVariables.OVERRIDE_EVENT?.body)
102
+ }
103
+ contextObj.ctx.dispose()
104
+ contextObj.ctx = null
105
+ }
106
+ }
107
+
108
+ const replaceEnvironmentsFromRules = async (rulesObject) => {
109
+ const rules = JSON.parse(JSON.stringify(rulesObject || []))
110
+ const environment = objectStore.get('inboundEnvironment')
111
+
112
+ let reloadRules = false
113
+ rules.forEach(rule => {
114
+ Object.keys(rule.conditions).forEach(conditionType => {
115
+ rule.conditions[conditionType].forEach((condition) => {
116
+ if (condition.value && (typeof condition.value === 'string') && condition.value.split('.')[0] === '{$environment') {
117
+ condition.value = getEnvironmentValue(condition.value, environment)
118
+ reloadRules = true
119
+ }
120
+ })
121
+ })
122
+ })
123
+
124
+ return reloadRules ? rules : undefined
125
+ }
126
+
127
+ const validateRules = async (context, req) => {
128
+ const rules = await rulesEngineModel.getValidationRules(req.customInfo.user)
129
+
130
+ const newRules = await replaceEnvironmentsFromRules(rules)
131
+ const rulesEngine = await rulesEngineModel.getValidationRulesEngine(newRules, req.customInfo.user)
132
+
133
+ const curEvent = await evaluate(rulesEngine, context)
134
+
135
+ let generatedErrorCallback = {}
136
+
137
+ if (curEvent) {
138
+ customLogger.logMessage('debug', 'Validation rules matched', { additionalData: curEvent, request: req })
139
+ if (curEvent.params.delay) {
140
+ generatedErrorCallback.delay = curEvent.params.delay
141
+ }
142
+
143
+ await executeScripts(curEvent, req)
144
+
145
+ if (curEvent.type === 'FIXED_ERROR_CALLBACK') {
146
+ const operationCallback = req.customInfo.callbackInfo.errorCallback.path
147
+
148
+ generatedErrorCallback.path = (req.customInfo.callbackInfo.errorCallback.pathPattern)
149
+ ? await replaceVariablesFromRequest(req.customInfo.callbackInfo.errorCallback.pathPattern, context, req)
150
+ : operationCallback
151
+ generatedErrorCallback.callbackInfo = await replaceVariablesFromRequest(req.customInfo.callbackInfo, context, req)
152
+ generatedErrorCallback.method = req.customInfo.callbackInfo.errorCallback.method
153
+ generatedErrorCallback.body = await replaceVariablesFromRequest(curEvent.params.body, context, req)
154
+ generatedErrorCallback.headers = await replaceVariablesFromRequest(curEvent.params.headers, context, req)
155
+ } else if (curEvent.type === 'MOCK_ERROR_CALLBACK') {
156
+ if (req.customInfo.specFile) {
157
+ generatedErrorCallback = await generateMockErrorCallback(context, req)
158
+
159
+ _.merge(generatedErrorCallback.body, await replaceVariablesFromRequest(curEvent.params.body, context, req))
160
+ _.merge(generatedErrorCallback.headers, await replaceVariablesFromRequest(curEvent.params.headers, context, req))
161
+ removeEmpty(generatedErrorCallback.body)
162
+ } else {
163
+ customLogger.logMessage('error', 'No Specification file provided for validateRules function', { request: req })
164
+ }
165
+ } else if (curEvent.type === 'NO_CALLBACK') {
166
+ customLogger.logMessage('info', 'Skipping validation callback', { additionalData: curEvent, request: req })
167
+ generatedErrorCallback.skipCallback = true
168
+ }
169
+ }
170
+ return generatedErrorCallback
171
+ }
172
+
173
+ const generateMockErrorCallback = async (context, req) => {
174
+ const generatedErrorCallback = {}
175
+ const callbackGenerator = new OpenApiMockGenerator()
176
+ await callbackGenerator.load(path.join(req.customInfo.specFile))
177
+ let jsfRefs1 = []
178
+ if (req.customInfo.jsfRefFile) {
179
+ try {
180
+ const rawdata = await readFileAsync(req.customInfo.jsfRefFile)
181
+ jsfRefs1 = JSON.parse(rawdata)
182
+ } catch (err) {}
183
+ }
184
+ const operationCallback = req.customInfo.callbackInfo.errorCallback.path
185
+
186
+ if (req.customInfo.callbackInfo.errorCallback.pathPattern) {
187
+ generatedErrorCallback.path = await replaceVariablesFromRequest(req.customInfo.callbackInfo.errorCallback.pathPattern, context, req)
188
+ } else {
189
+ generatedErrorCallback.path = operationCallback
190
+ }
191
+ generatedErrorCallback.callbackInfo = await replaceVariablesFromRequest(req.customInfo.callbackInfo, context, req)
192
+ generatedErrorCallback.method = req.customInfo.callbackInfo.errorCallback.method
193
+ generatedErrorCallback.body = await callbackGenerator.generateRequestBody(operationCallback, generatedErrorCallback.method, jsfRefs1)
194
+ generatedErrorCallback.headers = await callbackGenerator.generateRequestHeaders(operationCallback, generatedErrorCallback.method, jsfRefs1)
195
+
196
+ // Override the values in generated callback with the values from callback map file
197
+ if (req.customInfo.callbackInfo.errorCallback.bodyOverride) {
198
+ _.merge(generatedErrorCallback.body, await replaceVariablesFromRequest(req.customInfo.callbackInfo.errorCallback.bodyOverride, context, req))
199
+ removeEmpty(generatedErrorCallback.body)
200
+ }
201
+ if (req.customInfo.callbackInfo.errorCallback.headerOverride) {
202
+ _.merge(generatedErrorCallback.headers, await replaceVariablesFromRequest(req.customInfo.callbackInfo.errorCallback.headerOverride, context, req))
203
+ }
204
+ return generatedErrorCallback
205
+ }
206
+
207
+ const evaluate = async (rulesEngine, context) => {
208
+ const facts = {
209
+ operationPath: context.operation.path,
210
+ path: context.request.path,
211
+ method: context.request.method,
212
+ body: context.request.body || {},
213
+ pathParams: context.request.params || {},
214
+ headers: context.request.headers || {},
215
+ queryParams: context.request.query ? JSON.parse(JSON.stringify(context.request.query)) : {}
216
+ }
217
+ const res = await rulesEngine.evaluate(facts)
218
+ if (res) {
219
+ const curEvent = res[0]
220
+ if (!curEvent.params) {
221
+ curEvent.params = {}
222
+ }
223
+ return curEvent
224
+ }
225
+ }
226
+
227
+ const callbackRules = async (context, req) => {
228
+ if (!req.customInfo.callbackInfo?.successCallback) {
229
+ customLogger.logMessage('error', 'No success callback specified', { request: req })
230
+ return {}
231
+ }
232
+
233
+ const rules = await rulesEngineModel.getCallbackRules(req.customInfo.user)
234
+
235
+ const newRules = await replaceEnvironmentsFromRules(rules)
236
+ const rulesEngine = await rulesEngineModel.getCallbackRulesEngine(newRules, req.customInfo.user)
237
+
238
+ const curEvent = await evaluate(rulesEngine, context)
239
+
240
+ const generatedCallback = {}
241
+ if (curEvent) {
242
+ customLogger.logMessage('debug', 'Callback rules are matched', { additionalData: curEvent, request: req })
243
+ if (curEvent.params.delay) {
244
+ generatedCallback.delay = curEvent.params.delay
245
+ }
246
+
247
+ await executeScripts(curEvent, req)
248
+
249
+ if (curEvent.type === 'FIXED_CALLBACK') {
250
+ // Add event info to generated callback
251
+ generatedCallback.eventInfo = {
252
+ ...curEvent
253
+ }
254
+ const operationCallback = req.customInfo.callbackInfo.successCallback.path
255
+
256
+ // Check if pathPattern from callback_map file exists and determine the callback path
257
+ if (req.customInfo.callbackInfo.successCallback.pathPattern) {
258
+ generatedCallback.path = await replaceVariablesFromRequest(req.customInfo.callbackInfo.successCallback.pathPattern, context, req)
259
+ } else {
260
+ generatedCallback.path = operationCallback
261
+ }
262
+ generatedCallback.callbackInfo = await replaceVariablesFromRequest(req.customInfo.callbackInfo, context, req)
263
+ generatedCallback.method = req.customInfo.callbackInfo.successCallback.method
264
+ generatedCallback.body = await replaceVariablesFromRequest(curEvent.params.body, context, req)
265
+ generatedCallback.headers = await replaceVariablesFromRequest(curEvent.params.headers, context, req)
266
+ } else if (curEvent.type === 'MOCK_CALLBACK') {
267
+ // Add event info to generated callback
268
+ generatedCallback.eventInfo = {
269
+ ...curEvent
270
+ }
271
+ if (req.customInfo.specFile) {
272
+ const callbackGenerator = new OpenApiMockGenerator()
273
+ await callbackGenerator.load(path.join(req.customInfo.specFile))
274
+ let jsfRefs1 = []
275
+ if (req.customInfo.jsfRefFile) {
276
+ try {
277
+ const rawdata = await readFileAsync(req.customInfo.jsfRefFile)
278
+ jsfRefs1 = JSON.parse(rawdata)
279
+ } catch (err) {}
280
+ }
281
+ const operationCallback = req.customInfo.callbackInfo.successCallback.path
282
+
283
+ // Check if pathPattern from callback_map file exists and determine the callback path
284
+ if (req.customInfo.callbackInfo.successCallback.pathPattern) {
285
+ generatedCallback.path = await replaceVariablesFromRequest(req.customInfo.callbackInfo.successCallback.pathPattern, context, req)
286
+ } else {
287
+ generatedCallback.path = operationCallback
288
+ }
289
+ generatedCallback.callbackInfo = await replaceVariablesFromRequest(req.customInfo.callbackInfo, context, req)
290
+ generatedCallback.method = req.customInfo.callbackInfo.successCallback.method
291
+ generatedCallback.body = await callbackGenerator.generateRequestBody(operationCallback, generatedCallback.method, jsfRefs1)
292
+ generatedCallback.headers = await callbackGenerator.generateRequestHeaders(operationCallback, generatedCallback.method, jsfRefs1)
293
+
294
+ // Override the values in generated callback with the values from callback map file
295
+ if (req.customInfo.callbackInfo.successCallback.bodyOverride) {
296
+ _.merge(generatedCallback.body, await replaceVariablesFromRequest(req.customInfo.callbackInfo.successCallback.bodyOverride, context, req))
297
+ removeEmpty(generatedCallback.body)
298
+ }
299
+ if (req.customInfo.callbackInfo.successCallback.headerOverride) {
300
+ _.merge(generatedCallback.headers, await replaceVariablesFromRequest(req.customInfo.callbackInfo.successCallback.headerOverride, context, req))
301
+ }
302
+
303
+ // Override the values in generated callback with the values from event params
304
+ _.merge(generatedCallback.body, await replaceVariablesFromRequest(curEvent.params.body, context, req))
305
+ removeEmpty(generatedCallback.body)
306
+ _.merge(generatedCallback.headers, await replaceVariablesFromRequest(curEvent.params.headers, context, req))
307
+ } else {
308
+ customLogger.logMessage('error', 'No Specification file provided for validateRules function', { request: req })
309
+ }
310
+ } else if (curEvent.type === 'NO_CALLBACK') {
311
+ customLogger.logMessage('info', 'Skipping callback', { additionalData: curEvent, request: req })
312
+ generatedCallback.skipCallback = true
313
+ }
314
+ } else {
315
+ customLogger.logMessage('error', 'No callback rules are matched', { request: req })
316
+ }
317
+
318
+ return generatedCallback
319
+ }
320
+
321
+ const responseRules = async (context, req) => {
322
+ const rules = await rulesEngineModel.getResponseRules(req.customInfo.user)
323
+
324
+ const newRules = await replaceEnvironmentsFromRules(rules)
325
+ const rulesEngine = await rulesEngineModel.getResponseRulesEngine(newRules, req.customInfo.user)
326
+
327
+ const curEvent = await evaluate(rulesEngine, context)
328
+
329
+ const generatedResponse = {}
330
+
331
+ if (curEvent) {
332
+ customLogger.logMessage('debug', 'Response rules are matched', { additionalData: curEvent, request: req })
333
+ if (curEvent.params.delay) {
334
+ generatedResponse.delay = curEvent.params.delay
335
+ }
336
+
337
+ await executeScripts(curEvent, req)
338
+
339
+ if (curEvent.type === 'FIXED_RESPONSE') {
340
+ generatedResponse.body = await replaceVariablesFromRequest(curEvent.params.body, context, req)
341
+ generatedResponse.status = +curEvent.params.statusCode
342
+ } else if (curEvent.type === 'MOCK_RESPONSE') {
343
+ if (req.customInfo.specFile) {
344
+ const responseGenerator = new OpenApiMockGenerator()
345
+ await responseGenerator.load(path.join(req.customInfo.specFile))
346
+ let jsfRefs1 = []
347
+ if (req.customInfo.jsfRefFile) {
348
+ try {
349
+ const rawdata = await readFileAsync(req.customInfo.jsfRefFile)
350
+ jsfRefs1 = JSON.parse(rawdata)
351
+ } catch (err) {}
352
+ }
353
+ const { body, status } = await responseGenerator.generateResponseBody(context.operation.path, context.request.method, jsfRefs1)
354
+ generatedResponse.body = body
355
+ generatedResponse.status = +status
356
+ // generatedResponse.headers = await responseGenerator.generateRequestHeaders(operationCallback, generatedResponse.method, jsfRefs1)
357
+
358
+ // Override the values in generated callback with the values from callback map file
359
+ if (req.customInfo.responseInfo && req.customInfo.responseInfo.response.bodyOverride) {
360
+ _.merge(generatedResponse.body, await replaceVariablesFromRequest(req.customInfo.responseInfo.response.bodyOverride, context, req))
361
+ removeEmpty(generatedResponse.body)
362
+ }
363
+
364
+ // Override the values in generated callback with the values from event params
365
+ _.merge(generatedResponse.body, await replaceVariablesFromRequest(curEvent.params.body, context, req))
366
+ removeEmpty(generatedResponse.body)
367
+ _.merge(generatedResponse.headers, await replaceVariablesFromRequest(curEvent.params.headers, context, req))
368
+ } else {
369
+ customLogger.logMessage('error', 'No Specification file provided for responseRules function', { request: req })
370
+ }
371
+ }
372
+ } else {
373
+ customLogger.logMessage('info', 'No response rules are matched', { request: req })
374
+ }
375
+ return generatedResponse
376
+ }
377
+
378
+ const forwardRules = async (context, req) => {
379
+ const rules = await rulesEngineModel.getForwardRules(req.customInfo.user)
380
+
381
+ const newRules = await replaceEnvironmentsFromRules(rules)
382
+ const rulesEngine = await rulesEngineModel.getForwardRulesEngine(newRules, req.customInfo.user)
383
+
384
+ const curEvent = await evaluate(rulesEngine, context)
385
+
386
+ if (curEvent) {
387
+ const forwardedRequest = {}
388
+ customLogger.logMessage('debug', 'Forward rules are matched', { additionalData: curEvent, request: req })
389
+ await executeScripts(curEvent, req)
390
+
391
+ if (curEvent.type === 'FORWARD') {
392
+ if (req.customInfo && req.customInfo.callbackInfo) {
393
+ forwardedRequest.callbackInfo = await replaceVariablesFromRequest(req.customInfo.callbackInfo, context, req)
394
+ } else {
395
+ forwardedRequest.callbackInfo = {}
396
+ }
397
+ if (curEvent.params.dfspId) {
398
+ forwardedRequest.callbackInfo.fspid = curEvent.params.dfspId
399
+ }
400
+ forwardedRequest.path = req.path
401
+ forwardedRequest.method = req.method
402
+ forwardedRequest.body = req.payload
403
+ forwardedRequest.headers = req.headers
404
+ }
405
+ return forwardedRequest
406
+ } else {
407
+ customLogger.logMessage('error', 'No forward rules are matched', { request: req })
408
+ return false
409
+ }
410
+ }
411
+
412
+ const replaceVariablesFromRequest = async (inputObject, context, req) => {
413
+ let resultObject
414
+ // Check whether inputObject is string or object. If it is object, then convert that to JSON string and parse it while return
415
+ if (typeof inputObject === 'string') {
416
+ resultObject = inputObject
417
+ } else if (typeof inputObject === 'object') {
418
+ resultObject = JSON.stringify(inputObject)
419
+ } else {
420
+ return inputObject
421
+ }
422
+
423
+ // Check the string for any inclusions like {$some_param}
424
+ const environment = objectStore.get('inboundEnvironment')
425
+ const matchedArray = resultObject.match(/{\$([^}]+)}/g)
426
+ if (matchedArray) {
427
+ const userConfig = await Config.getUserConfig(req.customInfo.user)
428
+ matchedArray.forEach(element => {
429
+ const splitArr = element.split('.')
430
+ switch (splitArr[0]) {
431
+ case '{$function':
432
+ resultObject = resultObject.replace(element, getFunctionResult(element, context, req))
433
+ break
434
+ case '{$config': {
435
+ resultObject = resultObject.replace(element, getConfigValue(element, userConfig))
436
+ break
437
+ }
438
+ case '{$session':
439
+ resultObject = resultObject.replace(element, getSessionValue(element, req.customInfo))
440
+ break
441
+ case '{$environment':
442
+ resultObject = resultObject.replace(element, getEnvironmentValue(element, environment))
443
+ break
444
+ case '{$request':
445
+ default:
446
+ resultObject = resultObject.replace(element, getVariableValue(element, context))
447
+ }
448
+ })
449
+ }
450
+
451
+ if (typeof inputObject === 'object') {
452
+ return JSON.parse(resultObject)
453
+ } else {
454
+ return resultObject
455
+ }
456
+ }
457
+
458
+ // Get the variable from the object using lodash library
459
+ const getVariableValue = (param, fromObject) => {
460
+ const temp = param.replace(/{\$(.*)}/, '$1')
461
+ return _.get(fromObject, temp)
462
+ }
463
+
464
+ // Get the config value from the object using lodash library
465
+ const getConfigValue = (param, fromObject) => {
466
+ const temp = param.replace(/{\$config.(.*)}/, '$1')
467
+ return _.get(fromObject, temp)
468
+ }
469
+
470
+ // Get the customInfo value from the object using lodash library
471
+ const getSessionValue = (param, fromObject) => {
472
+ const temp = param.replace(/{\$session.(.*)}/, '$1')
473
+ return _.get(fromObject, temp)
474
+ }
475
+
476
+ // Execute the function and return the result
477
+ const getFunctionResult = (param, fromObject, req) => {
478
+ return utilsInternal.getFunctionResult(param, fromObject, req)
479
+ }
480
+
481
+ const getEnvironmentValue = (param, fromObject) => {
482
+ const temp = param.replace(/{\$environment.(.*)}/, '$1')
483
+ return _.get(fromObject, temp)
484
+ }
485
+
486
+ module.exports = {
487
+ validateRules,
488
+ callbackRules,
489
+ responseRules,
490
+ forwardRules,
491
+ generateMockErrorCallback
492
+ }
@@ -0,0 +1,136 @@
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
+ * Vijaya Kumar Guthi <vijaya.guthi@modusbox.com> (Original Author)
27
+ --------------
28
+ ******/
29
+
30
+ const customLogger = require('../requestLogger')
31
+ // eslint-disable-next-line
32
+ const acceptHeaderRE = new RegExp('^application/vnd.interoperability\\.?([a-zA-Z0-9\\*]+)?\\.([a-zA-Z0-9\\*]+)?(\\+json)?(;)?(version=(([0-9]+)(\\.([0-9]+))?)?)?$')
33
+
34
+ module.exports.validateAcceptHeader = (acceptHeader) => {
35
+ const validationFailed = !acceptHeaderRE.test(acceptHeader)
36
+ if (validationFailed) {
37
+ customLogger.logMessage('debug', 'Invalid accept header ' + acceptHeader)
38
+ }
39
+ return {
40
+ validationFailed,
41
+ message: validationFailed ? 'Unknown Accept header format' : 'OK'
42
+ }
43
+ }
44
+
45
+ module.exports.validateContentTypeHeader = (contentTypeHeader) => {
46
+ const validationFailed = !acceptHeaderRE.test(contentTypeHeader)
47
+ if (validationFailed) {
48
+ customLogger.logMessage('debug', 'Invalid content-type header ' + contentTypeHeader)
49
+ }
50
+ return {
51
+ validationFailed,
52
+ message: validationFailed ? 'Unknown Content-Type header format' : 'OK'
53
+ }
54
+ }
55
+
56
+ module.exports.negotiateVersion = (req, apis) => {
57
+ let acceptHeader = req.headers['content-type']
58
+ if (req.method === 'post' || req.method === 'get') {
59
+ acceptHeader = req.headers.accept
60
+ }
61
+ let negotiatedIndex = null
62
+ let negotiationFailed = true
63
+ let responseContentTypeHeader = null
64
+
65
+ const parsedAcceptHeader = parseAcceptHeader(acceptHeader)
66
+ if (parsedAcceptHeader) {
67
+ if (parsedAcceptHeader.majorVersion >= 0) {
68
+ if (parsedAcceptHeader.minorVersion >= 0) { // Major version and Minor version are supplied, so find the exact match
69
+ const matchedApiIndex = apis.findIndex(item => {
70
+ return item.majorVersion === parsedAcceptHeader.majorVersion && item.minorVersion === parsedAcceptHeader.minorVersion
71
+ })
72
+ if (matchedApiIndex >= 0) { // Exact match found
73
+ negotiatedIndex = matchedApiIndex
74
+ negotiationFailed = false
75
+ }
76
+ } else { // No Minor version, so find out the highest minor version in this major version
77
+ const temp = apis.reduce((accumulatorIndex, currentValue, currentIndex) => {
78
+ let compareValue = -1
79
+ if (accumulatorIndex) {
80
+ compareValue = apis[accumulatorIndex].minorVersion
81
+ }
82
+ if (currentValue.majorVersion === parsedAcceptHeader.majorVersion && currentValue.minorVersion > compareValue) {
83
+ return currentIndex
84
+ } else {
85
+ return accumulatorIndex
86
+ }
87
+ }, null)
88
+ if (temp !== null) {
89
+ negotiatedIndex = temp
90
+ negotiationFailed = false
91
+ }
92
+ }
93
+ } else { // No Major version, find out the highest version of all the APIs
94
+ const temp = apis.reduce((accumulatorIndex, currentValue, currentIndex) => {
95
+ let compareValue = -1
96
+ if (accumulatorIndex) {
97
+ compareValue = apis[accumulatorIndex].majorVersion * 10 + apis[accumulatorIndex].minorVersion
98
+ }
99
+ if ((currentValue.majorVersion * 10 + currentValue.minorVersion) > compareValue) {
100
+ return currentIndex
101
+ } else {
102
+ return accumulatorIndex
103
+ }
104
+ }, null)
105
+ if (temp !== null) {
106
+ negotiatedIndex = temp
107
+ negotiationFailed = false
108
+ }
109
+ }
110
+ if (!negotiationFailed) {
111
+ responseContentTypeHeader = `application/vnd.interoperability${parsedAcceptHeader.apiType !== 'fspiop' ? '.' + parsedAcceptHeader.apiType : ''}.${parsedAcceptHeader.resource}+json;version=${apis[negotiatedIndex].majorVersion}.${apis[negotiatedIndex].minorVersion}`
112
+ }
113
+ }
114
+ customLogger.logMessage('debug', negotiationFailed ? 'Version negotiation failed for the Accept / Content-Type header ' + acceptHeader : 'Version negotiation succeeded, picked up the version ' + apis[negotiatedIndex].majorVersion + '.' + apis[negotiatedIndex].minorVersion, { request: req })
115
+ return {
116
+ negotiationFailed,
117
+ message: negotiationFailed ? 'Version negotiation failed for the Accept / Content-Type header ' + acceptHeader : 'OK',
118
+ negotiatedIndex,
119
+ responseContentTypeHeader
120
+ }
121
+ }
122
+
123
+ const parseAcceptHeader = (acceptHeader) => {
124
+ const parsedArray = acceptHeaderRE.exec(acceptHeader)
125
+ if (!parsedArray) {
126
+ return null
127
+ }
128
+ return {
129
+ apiType: parsedArray[1] ? parsedArray[1] : 'fspiop',
130
+ resource: parsedArray[2] ? parsedArray[2] : parsedArray[1],
131
+ majorVersion: +parsedArray[7],
132
+ minorVersion: +parsedArray[9]
133
+ }
134
+ }
135
+
136
+ module.exports.parseAcceptHeader = parseAcceptHeader