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.
- package/.dockerignore +10 -0
- package/.grype.yaml +16 -0
- package/.ncurc.yaml +9 -0
- package/.nvmrc +1 -0
- package/.versionrc.js +16 -0
- package/CHANGELOG.md +504 -0
- package/CODEOWNERS +30 -0
- package/Dockerfile +42 -0
- package/Dockerfile-newman +13 -0
- package/LICENSE.md +9 -0
- package/README.md +119 -0
- package/assets/diagrams/architectural/architectural-diagram.svg +3 -0
- package/assets/diagrams/flow/flow-diagram.svg +3 -0
- package/assets/images/Sample-Response-Failure.png +0 -0
- package/assets/images/Screenshot 2020-04-16 at 22.58.04.png +0 -0
- package/assets/images/TLS-Enabled-on-Environment.png +0 -0
- package/assets/images/adapter-mutual-tls-enabled.png +0 -0
- package/assets/images/add-additional-input-values.png +0 -0
- package/assets/images/add-condition-button.png +0 -0
- package/assets/images/add-new-assertion.png +0 -0
- package/assets/images/add-new-input-value.png +0 -0
- package/assets/images/add-new-input-variable.png +0 -0
- package/assets/images/additional-transfers.png +0 -0
- package/assets/images/api-provisioning-add-new-api-confirmation.png +0 -0
- package/assets/images/api-provisioning-file-input-window.png +0 -0
- package/assets/images/api-provisioning-list-apis-view.png +0 -0
- package/assets/images/api-provisioning-menu-item.png +0 -0
- package/assets/images/apply_and_restart.jpg +0 -0
- package/assets/images/assess-request-or-response.png +0 -0
- package/assets/images/assess-response-equation-save.png +0 -0
- package/assets/images/assess-response-equation.png +0 -0
- package/assets/images/assess-response-status.png +0 -0
- package/assets/images/building-new-rules-file.png +0 -0
- package/assets/images/callback-rules-screen.png +0 -0
- package/assets/images/configurable-parameter-assertion.png +0 -0
- package/assets/images/configurable-parameter-currency.png +0 -0
- package/assets/images/configurable-parameter.png +0 -0
- package/assets/images/connection-manager-ui-opening.png +0 -0
- package/assets/images/create-inbound-user-simulator.png +0 -0
- package/assets/images/creating-new-rule-file.png +0 -0
- package/assets/images/dfsp-client-cacert.png +0 -0
- package/assets/images/dfsp-client-submit.png +0 -0
- package/assets/images/dfsp-client.png +0 -0
- package/assets/images/dfsp-p2p-happy-path.png +0 -0
- package/assets/images/dfsp-server-cacert.png +0 -0
- package/assets/images/dfsp-server-cert.png +0 -0
- package/assets/images/download-report.png +0 -0
- package/assets/images/drive_have_not_been_shared.jpg +0 -0
- package/assets/images/event-response-options.png +0 -0
- package/assets/images/expand-monitoring-messages.png +0 -0
- package/assets/images/fixed-response-sample.png +0 -0
- package/assets/images/header-selection.png +0 -0
- package/assets/images/heap_error_windows.jpg +0 -0
- package/assets/images/hosted-mode-docker-compose-intro.png +0 -0
- package/assets/images/hub-client-cert.png +0 -0
- package/assets/images/import-template.png +0 -0
- package/assets/images/inbound-requests-environment.png +0 -0
- package/assets/images/inbound-requests-scripts.png +0 -0
- package/assets/images/jws-certificate-submit.png +0 -0
- package/assets/images/jws-certificate.png +0 -0
- package/assets/images/jws-certs-keys.png +0 -0
- package/assets/images/jws-hub-certs-keys.png +0 -0
- package/assets/images/local-enable-jws-publickey.png +0 -0
- package/assets/images/local-mutual-tls-enabled.png +0 -0
- package/assets/images/local_drives_to_be_available.jpg +0 -0
- package/assets/images/mcm-environment-opening.png +0 -0
- package/assets/images/menu-items.png +0 -0
- package/assets/images/mock-response-sample.png +0 -0
- package/assets/images/monitoring-initial-state.png +0 -0
- package/assets/images/monitoring-messages.png +0 -0
- package/assets/images/new-empty-assertion.png +0 -0
- package/assets/images/opened-imported-template.png +0 -0
- package/assets/images/opening-default-settings.png +0 -0
- package/assets/images/opening-sync-response-rules.png +0 -0
- package/assets/images/opening-view.png +0 -0
- package/assets/images/outbound-display-opening-hub.png +0 -0
- package/assets/images/outbound-display-opening.png +0 -0
- package/assets/images/override-with-environment-variable.png +0 -0
- package/assets/images/populate-with-sample-body.png +0 -0
- package/assets/images/resource-selection.png +0 -0
- package/assets/images/rule-builder-select-api.png +0 -0
- package/assets/images/sample-condition-add-configurable-params.png +0 -0
- package/assets/images/sample-condition.png +0 -0
- package/assets/images/sample-editor.png +0 -0
- package/assets/images/sample-request.png +0 -0
- package/assets/images/sample-test-assertion.png +0 -0
- package/assets/images/send-transfer.png +0 -0
- package/assets/images/sending-single-test-case-1.png +0 -0
- package/assets/images/sending-single-test-case-2.png +0 -0
- package/assets/images/sending-test-cases.png +0 -0
- package/assets/images/server-certificates-submitted.png +0 -0
- package/assets/images/simulator-response.png +0 -0
- package/assets/images/simulator-scheme-adapter-endpoint.png +0 -0
- package/assets/images/summarized-view-of-rule.png +0 -0
- package/assets/images/template-window.png +0 -0
- package/assets/images/test-case-editor-console-log.png +0 -0
- package/assets/images/test-case-editor-environment-state.png +0 -0
- package/assets/images/test-case-editor-scripts.png +0 -0
- package/assets/images/test-case-editor.png +0 -0
- package/assets/images/testcase-definition-download.png +0 -0
- package/assets/images/testcase-definition-edit-meta-info.png +0 -0
- package/assets/images/testing-toolkit-mojaloop-testing-toolkit-endpoint.png +0 -0
- package/assets/images/tls-hub-certs-keys.png +0 -0
- package/assets/images/tls-jws-enabled-on-environment.png +0 -0
- package/assets/images/updated-sample-body-data.png +0 -0
- package/assets/images/using-configurable-parameter.png +0 -0
- package/assets/images/validation-rules-screen.png +0 -0
- package/assets/images/view-response.png +0 -0
- package/audit-ci.jsonc +7 -0
- package/connection-manager/docker-compose.yml +55 -0
- package/database/docker-compose.yml +16 -0
- package/docker/hosted-mode/docker-compose.yaml +107 -0
- package/docker/hosted-mode/keycloak/keycloak-realm.json +2298 -0
- package/docker/hosted-mode/mongo-init.sh +1 -0
- package/docker/hosted-mode-tls/docker-compose.yaml +171 -0
- package/docker/hosted-mode-tls/keycloak/keycloak-realm.json +2298 -0
- package/docker/hosted-mode-tls/mongo-init.sh +1 -0
- package/docker-compose.yml +62 -0
- package/documents/Mojaloop-Testing-Toolkit.md +296 -0
- package/documents/RULES_ENGINE.md +403 -0
- package/documents/User-Guide-API-Provisioning.md +121 -0
- package/documents/User-Guide-CLI.md +218 -0
- package/documents/User-Guide-Connection-Manager.md +282 -0
- package/documents/User-Guide-Frequently-Asked-Questions.md +39 -0
- package/documents/User-Guide-Hosted-Mode-Docker-Compose.md +110 -0
- package/documents/User-Guide-Installation.md +163 -0
- package/documents/User-Guide-Mojaloop-Testing-Toolkit.md +642 -0
- package/documents/User-Guide-OAuth-Server-Deployment.md +283 -0
- package/documents/User-Guide-Onboarding-DFSP.md +197 -0
- package/documents/User-Guide-Onboarding-HUB.md +191 -0
- package/documents/User-Guide.md +53 -0
- package/examples/collections/dfsp/p2p_failed_tests.json +7161 -0
- package/examples/collections/dfsp/p2p_fx_happy_path.json +502 -0
- package/examples/collections/dfsp/p2p_happy_path.json +350 -0
- package/examples/collections/dfsp/p2p_happy_path_extended.json +6106 -0
- package/examples/collections/dfsp/p2p_happy_path_jws.json +511 -0
- package/examples/collections/dfsp/p2p_payee_assertions_websocket.json +441 -0
- package/examples/collections/dfsp/sample.json +5029 -0
- package/examples/collections/dfsp/transaction_request_service.json +240 -0
- package/examples/collections/fxp/FXP.json +264 -0
- package/examples/collections/fxp/SDK_backend.json +98 -0
- package/examples/collections/fxp/SDK_outbound.json +163 -0
- package/examples/collections/hub/hub_01_p2p_happy_path/hub_p2p_receive_quote.json +400 -0
- package/examples/collections/hub/hub_01_p2p_happy_path/hub_p2p_send_quote.json +395 -0
- package/examples/collections/hub/hub_02_block_transfer/hub_block_transfer.json +393 -0
- package/examples/collections/hub/hub_03_funds_in_out/hub_funds_in.json +224 -0
- package/examples/collections/hub/hub_03_funds_in_out/hub_funds_out.json +780 -0
- package/examples/collections/hub/hub_04_settlements/hub_settlements.json +3138 -0
- package/examples/collections/hub/hub_05_transfer_negative_scenarios/hub_transfer_negative_payee_abort.json +475 -0
- package/examples/collections/hub/hub_05_transfer_negative_scenarios/hub_transfer_negative_payee_invalid_fulfillment.json +370 -0
- package/examples/collections/hub/hub_05_transfer_negative_scenarios/hub_transfer_negative_transfer_timeout.json +262 -0
- package/examples/collections/hub/hub_06_transaction_requests_service/hub_trs_authorizations.json +117 -0
- package/examples/collections/hub/hub_06_transaction_requests_service/hub_trs_error_framework.json +591 -0
- package/examples/collections/hub/hub_06_transaction_requests_service/hub_trs_received_state.json +379 -0
- package/examples/collections/hub/hub_06_transaction_requests_service/hub_trs_reject_state.json +361 -0
- package/examples/collections/hub/hub_07_quoting_service.json +525 -0
- package/examples/collections/hub/hub_08_participant_inactive_stop_transfers.json +706 -0
- package/examples/collections/hub/hub_09_duplicate_handling_transfers.json +1377 -0
- package/examples/collections/hub/hub_10_on_us_transfers.json +245 -0
- package/examples/collections/hub/hub_11_accented_and_spl_chars.json +629 -0
- package/examples/collections/hub/hub_12_fspiop_version_1.1.json +646 -0
- package/examples/collections/hub/hub_13_bulk_transfers.json +1857 -0
- package/examples/collections/iso20022/self_referencing_iso20022.json +926 -0
- package/examples/collections/provisioning/testingtoolkitdfsp.json +904 -0
- package/examples/environments/dfsp_local_environment.json +46 -0
- package/examples/environments/hub_local_environment.json +57 -0
- package/jest.config.js +17 -0
- package/package.json +199 -0
- package/sbom-v18.12.4.csv +1553 -0
- package/secrets/keygen.sh +5 -0
- package/secrets/privatekey.pem +27 -0
- package/secrets/publickey.cer +21 -0
- package/secrets/tls/01.pem +132 -0
- package/secrets/tls/createSecrets.sh +20 -0
- package/secrets/tls/hub_client.csr +32 -0
- package/secrets/tls/hub_client_cacert.pem +35 -0
- package/secrets/tls/hub_client_cakey.pem +52 -0
- package/secrets/tls/hub_client_key.key +52 -0
- package/secrets/tls/hub_server.csr +31 -0
- package/secrets/tls/hub_server_cacert.pem +35 -0
- package/secrets/tls/hub_server_cakey.pem +52 -0
- package/secrets/tls/hub_server_cert.pem +132 -0
- package/secrets/tls/hub_server_key.key +52 -0
- package/secrets/tls/index.txt +1 -0
- package/secrets/tls/index.txt.attr +1 -0
- package/secrets/tls/openssl-client.cnf +36 -0
- package/secrets/tls/openssl-clientca.cnf +71 -0
- package/secrets/tls/openssl-server.cnf +39 -0
- package/secrets/tls/openssl-serverca.cnf +71 -0
- package/secrets/tls/serial.txt +1 -0
- package/spec_files/api_definitions/als_admin_1.1/api_spec.yaml +804 -0
- package/spec_files/api_definitions/central_admin_1.0/api_spec.yaml +1850 -0
- package/spec_files/api_definitions/central_admin_1.0/response_map.json +96 -0
- package/spec_files/api_definitions/central_admin_old_9.3/api_spec.yaml +2467 -0
- package/spec_files/api_definitions/central_admin_old_9.3/response_map.json +96 -0
- package/spec_files/api_definitions/fspiop_1.0/api_spec.yaml +4187 -0
- package/spec_files/api_definitions/fspiop_1.0/callback_map.json +568 -0
- package/spec_files/api_definitions/fspiop_1.0/mockRef.json +79 -0
- package/spec_files/api_definitions/fspiop_1.0/trigger_templates/transaction_request_followup.json +126 -0
- package/spec_files/api_definitions/fspiop_1.0/trigger_templates/transaction_request_followup_quotes_only.json +97 -0
- package/spec_files/api_definitions/fspiop_1.1/api_spec.yaml +3778 -0
- package/spec_files/api_definitions/fspiop_1.1/callback_map.json +568 -0
- package/spec_files/api_definitions/fspiop_1.1/mockRef.json +79 -0
- package/spec_files/api_definitions/fspiop_1.1/trigger_templates/transaction_request_followup.json +125 -0
- package/spec_files/api_definitions/fspiop_2.0/api_spec.yaml +4839 -0
- package/spec_files/api_definitions/fspiop_2.0/callback_map.json +716 -0
- package/spec_files/api_definitions/fspiop_2.0/mockRef.json +79 -0
- package/spec_files/api_definitions/fspiop_2.0/trigger_templates/transaction_request_followup.json +125 -0
- package/spec_files/api_definitions/fspiop_2.0_iso20022/api_spec.yaml +8331 -0
- package/spec_files/api_definitions/fspiop_2.0_iso20022/callback_map.json +508 -0
- package/spec_files/api_definitions/fspiop_2.0_iso20022/mockRef.json +66 -0
- package/spec_files/api_definitions/fx-api_2.0/api_spec.yaml +1768 -0
- package/spec_files/api_definitions/fx-api_2.0/callback_map.json +188 -0
- package/spec_files/api_definitions/fx-api_2.0/mockRef.json +83 -0
- package/spec_files/api_definitions/mojaloop_sdk_outbound_scheme_adapter_1.0/api_spec.yaml +2612 -0
- package/spec_files/api_definitions/mojaloop_sdk_outbound_scheme_adapter_1.0/mockRef.json +22 -0
- package/spec_files/api_definitions/mojaloop_sdk_outbound_scheme_adapter_1.0/response_map.json +35 -0
- package/spec_files/api_definitions/mojaloop_simulator_0.1/api_spec.yaml +225 -0
- package/spec_files/api_definitions/mojaloop_simulator_sim_1.4/api_spec.yaml +1087 -0
- package/spec_files/api_definitions/mojaloop_simulator_sim_1.4/mockRef.json +75 -0
- package/spec_files/api_definitions/mojaloop_simulator_sim_1.4/response_map.json +55 -0
- package/spec_files/api_definitions/payment_manager_1.4/api_spec.yaml +1389 -0
- package/spec_files/api_definitions/sdk-scheme-adapter-backend-v2_1_0-openapi3-snippets_2.1/api_spec.yaml +2834 -0
- package/spec_files/api_definitions/sdk-scheme-adapter-outbound-v2_1_0-openapi3-snippets_2.1/api_spec.yaml +3449 -0
- package/spec_files/api_definitions/settlements_1.0/api_spec.yaml +983 -0
- package/spec_files/api_definitions/settlements_1.0/mockRef.json +38 -0
- package/spec_files/api_definitions/settlements_1.0/response_map.json +34 -0
- package/spec_files/api_definitions/settlements_2.0/api_spec.yaml +1001 -0
- package/spec_files/api_definitions/settlements_2.0/mockRef.json +38 -0
- package/spec_files/api_definitions/settlements_2.0/response_map.json +34 -0
- package/spec_files/api_definitions/thirdparty_sdk_outbound_0.1/api_spec.yaml +2139 -0
- package/spec_files/reports/templates/newman/html_template.html +1202 -0
- package/spec_files/reports/templates/newman/pdf_template.html +790 -0
- package/spec_files/reports/templates/testcase_definition/table_view.html +1602 -0
- package/spec_files/rules_callback/config.json +3 -0
- package/spec_files/rules_callback/default.json +2698 -0
- package/spec_files/rules_callback/p2p-limit.json +129 -0
- package/spec_files/rules_forward/config.json +3 -0
- package/spec_files/rules_forward/default.json +482 -0
- package/spec_files/rules_response/config.json +3 -0
- package/spec_files/rules_response/default.json +295 -0
- package/spec_files/rules_validation/config.json +3 -0
- package/spec_files/rules_validation/default.json +1 -0
- package/spec_files/rules_validation/p2p-limit.json +55 -0
- package/spec_files/system_config.json +175 -0
- package/spec_files/user_config.json +109 -0
- package/src/index.js +67 -0
- package/src/lib/MyEventEmitter.js +54 -0
- package/src/lib/api-management.js +143 -0
- package/src/lib/api-routes/config.js +83 -0
- package/src/lib/api-routes/history.js +139 -0
- package/src/lib/api-routes/keycloak.js +54 -0
- package/src/lib/api-routes/longpolling.js +70 -0
- package/src/lib/api-routes/oauth2.js +149 -0
- package/src/lib/api-routes/objectstore.js +53 -0
- package/src/lib/api-routes/openapi.js +224 -0
- package/src/lib/api-routes/outbound.js +134 -0
- package/src/lib/api-routes/reports.js +72 -0
- package/src/lib/api-routes/rules.js +356 -0
- package/src/lib/api-routes/samples.js +92 -0
- package/src/lib/api-routes/server-logs.js +44 -0
- package/src/lib/api-routes/settings.js +71 -0
- package/src/lib/api-server.js +135 -0
- package/src/lib/arrayStore.js +101 -0
- package/src/lib/callbackHandler.js +201 -0
- package/src/lib/config.js +177 -0
- package/src/lib/configuration-providers/mb-connection-manager.js +625 -0
- package/src/lib/db/adapters/dbAdapter.js +184 -0
- package/src/lib/db/dfspMockUsers.js +64 -0
- package/src/lib/db/models/mongoDBWrapper.js +78 -0
- package/src/lib/eventListenerClient/inboundEventListener.js +176 -0
- package/src/lib/fileAdapter.js +57 -0
- package/src/lib/httpAgentStore.js +135 -0
- package/src/lib/importExport.js +186 -0
- package/src/lib/jws/JwsSigning.js +141 -0
- package/src/lib/loadSamples.js +128 -0
- package/src/lib/logger.js +20 -0
- package/src/lib/longpollingEmitter.js +56 -0
- package/src/lib/metrics.js +51 -0
- package/src/lib/mocking/custom-functions/generic.js +57 -0
- package/src/lib/mocking/middleware-functions/ilpModel.js +238 -0
- package/src/lib/mocking/middleware-functions/quotesAssociation.js +75 -0
- package/src/lib/mocking/middleware-functions/transactionRequestsService.js +78 -0
- package/src/lib/mocking/openApiDefinitionsModel.js +64 -0
- package/src/lib/mocking/openApiMockHandler.js +466 -0
- package/src/lib/mocking/openApiRulesEngine.js +492 -0
- package/src/lib/mocking/openApiVersionTools.js +136 -0
- package/src/lib/mocking/transformers/fspiopToISO20022.js +230 -0
- package/src/lib/mocking/transformers/index.js +41 -0
- package/src/lib/notificationEmitter.js +64 -0
- package/src/lib/oauth/KeycloakHelper.js +220 -0
- package/src/lib/oauth/LoginService.js +133 -0
- package/src/lib/oauth/OAuthHelper.js +181 -0
- package/src/lib/oauth/OAuthValidator.js +118 -0
- package/src/lib/oauth/Wso2Client.js +64 -0
- package/src/lib/objectStore/inMemoryImpl.js +50 -0
- package/src/lib/objectStore/objectStoreInterface.js +51 -0
- package/src/lib/objectStore.js +122 -0
- package/src/lib/report-generator/generator.js +126 -0
- package/src/lib/report-generator/helpers.js +154 -0
- package/src/lib/requestLogger.js +190 -0
- package/src/lib/resources/wso2carbon-publickey.cert +20 -0
- package/src/lib/rulesEngine.js +95 -0
- package/src/lib/rulesEngineModel.js +463 -0
- package/src/lib/scripting-engines/postman-sandbox.js +142 -0
- package/src/lib/scripting-engines/vm-javascript-sandbox.js +294 -0
- package/src/lib/server-logs/adapters/elastic-search.js +102 -0
- package/src/lib/server-logs/adapters/grafana.js +0 -0
- package/src/lib/server-logs/index.js +75 -0
- package/src/lib/socket-server.js +55 -0
- package/src/lib/storageAdapter.js +109 -0
- package/src/lib/test-outbound/TestCaseRunner.js +173 -0
- package/src/lib/test-outbound/getTracing.js +19 -0
- package/src/lib/test-outbound/outbound-initiator.js +1107 -0
- package/src/lib/uniqueIdGenerator.js +35 -0
- package/src/lib/utils.js +89 -0
- package/src/lib/utilsInternal.js +56 -0
- package/src/lib/webSocketClient/WebSocketClientManager.js +197 -0
- 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
|
+
}
|