From e09077a75ffb219e00e63b18a643fbbc3b25d8ff Mon Sep 17 00:00:00 2001 From: Pa1NarK <69745008+pixincreate@users.noreply.github.com> Date: Mon, 10 Jul 2023 15:02:44 +0530 Subject: [PATCH] ci(runner): rewrite `collection_runner.sh` in rust (#1604) --- .github/secrets/connector_auth.toml.gpg | Bin 2689 -> 2783 bytes .../workflows/postman-collection-runner.yml | 20 +- Cargo.lock | 16 ++ connector-template/test.rs | 6 +- connector_auth.toml.gpg | Bin 1636 -> 0 bytes crates/common_utils/Cargo.toml | 2 +- crates/router/Cargo.toml | 3 + .../router/tests/connectors/connector_auth.rs | 149 ---------- .../router/tests/connectors/dummyconnector.rs | 6 +- crates/router/tests/connectors/main.rs | 2 +- crates/router/tests/integration_demo.rs | 13 +- crates/test_utils/Cargo.toml | 25 ++ crates/test_utils/README.md | 13 + crates/test_utils/src/connector_auth.rs | 258 ++++++++++++++++++ crates/test_utils/src/lib.rs | 1 + crates/test_utils/src/main.rs | 156 +++++++++++ scripts/postman_test_automation.sh | 66 ----- 17 files changed, 496 insertions(+), 240 deletions(-) delete mode 100644 connector_auth.toml.gpg delete mode 100644 crates/router/tests/connectors/connector_auth.rs create mode 100644 crates/test_utils/Cargo.toml create mode 100644 crates/test_utils/README.md create mode 100644 crates/test_utils/src/connector_auth.rs create mode 100644 crates/test_utils/src/lib.rs create mode 100644 crates/test_utils/src/main.rs delete mode 100755 scripts/postman_test_automation.sh diff --git a/.github/secrets/connector_auth.toml.gpg b/.github/secrets/connector_auth.toml.gpg index 0038228e651ecf018f4ef511d98c6b28ec9ebe27..9f1e13ea895a69b0d1acf4e1f397a1aa7970c9fb 100644 GIT binary patch literal 2783 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 literal 2689 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, - pub adyen: Option, - pub adyen_uk: Option, - pub airwallex: Option, - pub authorizedotnet: Option, - pub bambora: Option, - pub bitpay: Option, - pub bluesnap: Option, - pub cashtocode: Option, - pub checkout: Option, - pub coinbase: Option, - pub cryptopay: Option, - pub cybersource: Option, - pub dlocal: Option, - #[cfg(feature = "dummy_connector")] - pub dummyconnector: Option, - pub fiserv: Option, - pub forte: Option, - pub globalpay: Option, - pub globepay: Option, - pub iatapay: Option, - pub mollie: Option, - pub multisafepay: Option, - pub nexinets: Option, - pub noon: Option, - pub nmi: Option, - pub nuvei: Option, - pub opayo: Option, - pub opennode: Option, - pub payeezy: Option, - pub payme: Option, - pub paypal: Option, - pub payu: Option, - pub powertranz: Option, - pub rapyd: Option, - pub shift4: Option, - pub stripe: Option, - pub stripe_au: Option, - pub stripe_uk: Option, - pub trustpay: Option, - pub worldpay: Option, - pub worldline: Option, - pub zen: Option, - pub automation_configs: Option, -} - -impl ConnectorAuthentication { - #[allow(clippy::expect_used)] - pub(crate) fn new() -> Self { - // before running tests - let path = env::var("CONNECTOR_AUTH_FILE_PATH") - .expect("connector authentication file path not set"); - toml::from_str( - &std::fs::read_to_string(path).expect("connector authentication config file not found"), - ) - .expect("Failed to read connector authentication config file") - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct HeaderKey { - pub api_key: String, -} - -impl From for ConnectorAuthType { - fn from(key: HeaderKey) -> Self { - Self::HeaderKey { - api_key: key.api_key, - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct BodyKey { - pub api_key: String, - pub key1: String, -} - -impl From for ConnectorAuthType { - fn from(key: BodyKey) -> Self { - Self::BodyKey { - api_key: key.api_key, - key1: key.key1, - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct SignatureKey { - pub api_key: String, - pub key1: String, - pub api_secret: String, -} - -impl From for ConnectorAuthType { - fn from(key: SignatureKey) -> Self { - Self::SignatureKey { - api_key: key.api_key, - key1: key.key1, - api_secret: key.api_secret, - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct MultiAuthKey { - pub api_key: String, - pub key1: String, - pub api_secret: String, - pub key2: String, -} - -impl From for ConnectorAuthType { - fn from(key: MultiAuthKey) -> Self { - Self::MultiAuthKey { - api_key: key.api_key, - key1: key.key1, - api_secret: key.api_secret, - key2: key.key2, - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct AutomationConfigs { - pub hs_base_url: Option, - pub hs_api_key: Option, - pub hs_test_browser: Option, - pub chrome_profile_path: Option, - pub firefox_profile_path: Option, - pub pypl_email: Option, - pub pypl_pass: Option, - pub gmail_email: Option, - pub gmail_pass: Option, - pub configs_url: Option, - pub stripe_pub_key: Option, - pub testcases_path: Option, - pub bluesnap_gateway_merchant_id: Option, - pub globalpay_gateway_merchant_id: Option, - pub run_minimum_steps: Option, - pub airwallex_merchant_name: Option, -} diff --git a/crates/router/tests/connectors/dummyconnector.rs b/crates/router/tests/connectors/dummyconnector.rs index c8a9d1acb8..35e68e99f8 100644 --- a/crates/router/tests/connectors/dummyconnector.rs +++ b/crates/router/tests/connectors/dummyconnector.rs @@ -3,11 +3,9 @@ use std::str::FromStr; use cards::CardNumber; use masking::Secret; use router::types::{self, api, storage::enums}; +use test_utils::connector_auth; -use crate::{ - connector_auth, - utils::{self, ConnectorActions}, -}; +use crate::utils::{self, ConnectorActions}; #[derive(Clone, Copy)] struct DummyConnectorTest; diff --git a/crates/router/tests/connectors/main.rs b/crates/router/tests/connectors/main.rs index 64ce4ad14f..690e8f1dc0 100644 --- a/crates/router/tests/connectors/main.rs +++ b/crates/router/tests/connectors/main.rs @@ -4,6 +4,7 @@ clippy::unwrap_in_result, clippy::unwrap_used )] +use test_utils::connector_auth; mod aci; mod adyen; @@ -20,7 +21,6 @@ mod cashtocode; mod checkout; mod checkout_ui; mod coinbase; -mod connector_auth; mod cryptopay; mod cybersource; mod dlocal; diff --git a/crates/router/tests/integration_demo.rs b/crates/router/tests/integration_demo.rs index 1fc675ffa9..16e7ead0a3 100644 --- a/crates/router/tests/integration_demo.rs +++ b/crates/router/tests/integration_demo.rs @@ -1,13 +1,8 @@ #![allow(clippy::unwrap_used)] mod utils; - -#[allow(dead_code)] -mod auth { - include!("connectors/connector_auth.rs"); -} - -use auth::ConnectorAuthentication; +use masking::PeekInterface; +use test_utils::connector_auth::ConnectorAuthentication; use utils::{mk_service, ApiKey, AppClient, MerchantId, PaymentId, Status}; /// Example of unit test @@ -77,7 +72,7 @@ async fn partial_refund() { &server, &merchant_id, "stripe", - &authentication.checkout.unwrap().api_key, + authentication.checkout.unwrap().api_key.peek(), ) .await; @@ -143,7 +138,7 @@ async fn exceed_refund() { &server, &merchant_id, "stripe", - &authentication.checkout.unwrap().api_key, + authentication.checkout.unwrap().api_key.peek(), ) .await; diff --git a/crates/test_utils/Cargo.toml b/crates/test_utils/Cargo.toml new file mode 100644 index 0000000000..920cdc1405 --- /dev/null +++ b/crates/test_utils/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "test_utils" +description = "Postman collection runner and utility" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +readme = "README.md" +license.workspace = true + +[features] +default = ["dummy_connector"] +dummy_connector = ["api_models/dummy_connector"] + + +[dependencies] +clap = { version = "4.3.2", default-features = false, features = ["std", "derive", "help", "usage", "cargo"] } +serde = { version = "1.0.163", features = ["derive"] } +serde_json = "1.0.96" +serde_path_to_error = "0.1.11" +toml = "0.7.4" + +# First party crates +router = { version = "0.2.0", path = "../router" } +api_models = { version = "0.1.0", path = "../api_models", features = ["errors"] } +masking = { version = "0.1.0", path = "../masking" } diff --git a/crates/test_utils/README.md b/crates/test_utils/README.md new file mode 100644 index 0000000000..5a7f913a96 --- /dev/null +++ b/crates/test_utils/README.md @@ -0,0 +1,13 @@ +# Test Runner + +The main part of running tests through `newman`. + +# Usage + +- Make sure you that you've the postman collection for the connector available in the `postman` dir with the name `.postman_collection.json` +- Add the connector credentials to the `connector_auth.toml` / `auth.toml` +- In terminal, execute: + ```zsh + export CONNECTOR_AUTH_FILE_PATH=/path/to/auth.toml + cargo run --package test_utils --bin test_utils -- --connector_name= --base_url= --admin_api_key= + ``` diff --git a/crates/test_utils/src/connector_auth.rs b/crates/test_utils/src/connector_auth.rs new file mode 100644 index 0000000000..4c4a5f023a --- /dev/null +++ b/crates/test_utils/src/connector_auth.rs @@ -0,0 +1,258 @@ +use std::{collections::HashMap, env}; + +use masking::{PeekInterface, Secret}; +use router::types::ConnectorAuthType; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ConnectorAuthentication { + pub aci: Option, + pub adyen: Option, + pub adyen_uk: Option, + pub airwallex: Option, + pub authorizedotnet: Option, + pub bambora: Option, + pub bitpay: Option, + pub bluesnap: Option, + pub cashtocode: Option, + pub checkout: Option, + pub coinbase: Option, + pub cryptopay: Option, + pub cybersource: Option, + pub dlocal: Option, + #[cfg(feature = "dummy_connector")] + pub dummyconnector: Option, + pub fiserv: Option, + pub forte: Option, + pub globalpay: Option, + pub globepay: Option, + pub iatapay: Option, + pub mollie: Option, + pub multisafepay: Option, + pub nexinets: Option, + pub noon: Option, + pub nmi: Option, + pub nuvei: Option, + pub opayo: Option, + pub opennode: Option, + pub payeezy: Option, + pub payme: Option, + pub paypal: Option, + pub payu: Option, + pub powertranz: Option, + pub rapyd: Option, + pub shift4: Option, + pub stripe: Option, + pub stripe_au: Option, + pub stripe_uk: Option, + pub trustpay: Option, + pub worldpay: Option, + pub worldline: Option, + pub zen: Option, + pub automation_configs: Option, +} + +impl Default for ConnectorAuthentication { + fn default() -> Self { + Self::new() + } +} + +#[allow(dead_code)] +impl ConnectorAuthentication { + #[allow(clippy::expect_used)] + pub fn new() -> Self { + // Do `export CONNECTOR_AUTH_FILE_PATH="/hyperswitch/crates/router/tests/connectors/sample_auth.toml"` + // before running tests in shell + let path = env::var("CONNECTOR_AUTH_FILE_PATH") + .expect("Connector authentication file path not set"); + toml::from_str( + &std::fs::read_to_string(path).expect("connector authentication config file not found"), + ) + .expect("Failed to read connector authentication config file") + } +} + +#[derive(Clone, Debug, Deserialize)] +pub struct ConnectorAuthenticationMap(HashMap); + +impl Default for ConnectorAuthenticationMap { + fn default() -> Self { + Self::new() + } +} + +// This is a temporary solution to avoid rust compiler from complaining about unused function +#[allow(dead_code)] +impl ConnectorAuthenticationMap { + pub fn inner(&self) -> &HashMap { + &self.0 + } + + #[allow(clippy::expect_used)] + pub fn new() -> Self { + // Do `export CONNECTOR_AUTH_FILE_PATH="/hyperswitch/crates/router/tests/connectors/sample_auth.toml"` + // before running tests in shell + let path = env::var("CONNECTOR_AUTH_FILE_PATH") + .expect("connector authentication file path not set"); + + // Read the file contents to a JsonString + let contents = + &std::fs::read_to_string(path).expect("Failed to read connector authentication file"); + + // Deserialize the JsonString to a HashMap + let auth_config: HashMap = + toml::from_str(contents).expect("Failed to deserialize TOML file"); + + // auth_config contains the data in below given format: + // { + // "connector_name": Table( + // { + // "api_key": String( + // "API_Key", + // ), + // "api_secret": String( + // "Secret key", + // ), + // "key1": String( + // "key1", + // ), + // "key2": String( + // "key2", + // ), + // }, + // ), + // "connector_name": Table( + // ... + // } + + // auth_map refines and extracts required information + let auth_map = auth_config + .into_iter() + .map(|(connector_name, config)| { + let auth_type = match config { + toml::Value::Table(table) => { + match ( + table.get("api_key"), + table.get("key1"), + table.get("api_secret"), + table.get("key2"), + ) { + (Some(api_key), None, None, None) => ConnectorAuthType::HeaderKey { + api_key: api_key.as_str().unwrap_or_default().to_string(), + }, + (Some(api_key), Some(key1), None, None) => ConnectorAuthType::BodyKey { + api_key: api_key.as_str().unwrap_or_default().to_string(), + key1: key1.as_str().unwrap_or_default().to_string(), + }, + (Some(api_key), Some(key1), Some(api_secret), None) => { + ConnectorAuthType::SignatureKey { + api_key: api_key.as_str().unwrap_or_default().to_string(), + key1: key1.as_str().unwrap_or_default().to_string(), + api_secret: api_secret.as_str().unwrap_or_default().to_string(), + } + } + (Some(api_key), Some(key1), Some(api_secret), Some(key2)) => { + ConnectorAuthType::MultiAuthKey { + api_key: api_key.as_str().unwrap_or_default().to_string(), + key1: key1.as_str().unwrap_or_default().to_string(), + api_secret: api_secret.as_str().unwrap_or_default().to_string(), + key2: key2.as_str().unwrap_or_default().to_string(), + } + } + _ => ConnectorAuthType::NoKey, + } + } + _ => ConnectorAuthType::NoKey, + }; + (connector_name, auth_type) + }) + .collect(); + + Self(auth_map) + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct HeaderKey { + pub api_key: Secret, +} + +impl From for ConnectorAuthType { + fn from(key: HeaderKey) -> Self { + Self::HeaderKey { + api_key: key.api_key.peek().to_string(), + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct BodyKey { + pub api_key: Secret, + pub key1: Secret, +} + +impl From for ConnectorAuthType { + fn from(key: BodyKey) -> Self { + Self::BodyKey { + api_key: key.api_key.peek().to_string(), + key1: key.key1.peek().to_string(), + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct SignatureKey { + pub api_key: Secret, + pub key1: Secret, + pub api_secret: Secret, +} + +impl From for ConnectorAuthType { + fn from(key: SignatureKey) -> Self { + Self::SignatureKey { + api_key: key.api_key.peek().to_string(), + key1: key.key1.peek().to_string(), + api_secret: key.api_secret.peek().to_string(), + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct MultiAuthKey { + pub api_key: Secret, + pub key1: Secret, + pub api_secret: Secret, + pub key2: Secret, +} + +impl From for ConnectorAuthType { + fn from(key: MultiAuthKey) -> Self { + Self::MultiAuthKey { + api_key: key.api_key.peek().to_string(), + key1: key.key1.peek().to_string(), + api_secret: key.api_secret.peek().to_string(), + key2: key.key2.peek().to_string(), + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct AutomationConfigs { + pub hs_base_url: Option, + pub hs_api_key: Option, + pub hs_test_browser: Option, + pub chrome_profile_path: Option, + pub firefox_profile_path: Option, + pub pypl_email: Option, + pub pypl_pass: Option, + pub gmail_email: Option, + pub gmail_pass: Option, + pub configs_url: Option, + pub stripe_pub_key: Option, + pub testcases_path: Option, + pub bluesnap_gateway_merchant_id: Option, + pub globalpay_gateway_merchant_id: Option, + pub run_minimum_steps: Option, + pub airwallex_merchant_name: Option, +} diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs new file mode 100644 index 0000000000..82b4addfb9 --- /dev/null +++ b/crates/test_utils/src/lib.rs @@ -0,0 +1 @@ +pub mod connector_auth; diff --git a/crates/test_utils/src/main.rs b/crates/test_utils/src/main.rs new file mode 100644 index 0000000000..650ba6cd29 --- /dev/null +++ b/crates/test_utils/src/main.rs @@ -0,0 +1,156 @@ +use std::{ + env, + process::{exit, Command as cmd}, +}; + +use clap::{arg, command, Parser}; +use router::types::ConnectorAuthType; +use test_utils::connector_auth::ConnectorAuthenticationMap; + +// Just by the name of the connector, this function generates the name of the collection +// Example: CONNECTOR_NAME="stripe" -> OUTPUT: postman/stripe.postman_collection.json +#[inline] +fn path_generation(name: impl AsRef) -> String { + format!("postman/{}.postman_collection.json", name.as_ref()) +} + +#[derive(Parser)] +#[command(version, about = "Postman collection runner using newman!", long_about = None)] +struct Args { + /// Name of the connector + #[arg(short, long = "connector_name")] + connector_name: String, + /// Base URL of the Hyperswitch environment + #[arg(short, long = "base_url")] + base_url: String, + /// Admin API Key of the environment + #[arg(short, long = "admin_api_key")] + admin_api_key: String, +} + +fn main() { + let args = Args::parse(); + + let connector_name = args.connector_name; + let base_url = args.base_url; + let admin_api_key = args.admin_api_key; + + let collection_path = path_generation(&connector_name); + let auth_map = ConnectorAuthenticationMap::new(); + + let inner_map = auth_map.inner(); + + // Newman runner + // Depending on the conditions satisfied, variables are added. Since certificates of stripe have already + // been added to the postman collection, those conditions are set to true and collections that have + // variables set up for certificate, will consider those variables and will fail. + + let mut newman_command = cmd::new("newman"); + + newman_command.args(["run", &collection_path]); + newman_command.args(["--env-var", &format!("admin_api_key={admin_api_key}")]); + newman_command.args(["--env-var", &format!("baseUrl={base_url}")]); + + if let Some(auth_type) = inner_map.get(&connector_name) { + match auth_type { + ConnectorAuthType::HeaderKey { api_key } => { + newman_command.args(["--env-var", &format!("connector_api_key={api_key}")]); + } + ConnectorAuthType::BodyKey { api_key, key1 } => { + newman_command.args([ + "--env-var", + &format!("connector_api_key={api_key}"), + "--env-var", + &format!("connector_key1={key1}"), + ]); + } + ConnectorAuthType::SignatureKey { + api_key, + key1, + api_secret, + } => { + newman_command.args([ + "--env-var", + &format!("connector_api_key={api_key}"), + "--env-var", + &format!("connector_key1={key1}"), + "--env-var", + &format!("connector_api_secret={api_secret}"), + ]); + } + ConnectorAuthType::MultiAuthKey { + api_key, + key1, + key2, + api_secret, + } => { + newman_command.args([ + "--env-var", + &format!("connector_api_key={api_key}"), + "--env-var", + &format!("connector_key1={key1}"), + "--env-var", + &format!("connector_key1={key2}"), + "--env-var", + &format!("connector_api_secret={api_secret}"), + ]); + } + // Handle other ConnectorAuthType variants + _ => { + eprintln!("Invalid authentication type."); + } + } + } else { + eprintln!("Connector not found."); + } + + // Add additional environment variables if present + if let Ok(gateway_merchant_id) = env::var("GATEWAY_MERCHANT_ID") { + newman_command.args([ + "--env-var", + &format!("gateway_merchant_id={gateway_merchant_id}"), + ]); + } + + if let Ok(gpay_certificate) = env::var("GPAY_CERTIFICATE") { + newman_command.args(["--env-var", &format!("certificate={gpay_certificate}")]); + } + + if let Ok(gpay_certificate_keys) = env::var("GPAY_CERTIFICATE_KEYS") { + newman_command.args([ + "--env-var", + &format!("certificate_keys={gpay_certificate_keys}"), + ]); + } + + newman_command.arg("--delay-request").arg("5"); + + // Execute the newman command + let output = newman_command.spawn(); + let mut child = match output { + Ok(child) => child, + Err(err) => { + eprintln!("Failed to execute command: {err}"); + exit(1); + } + }; + let status = child.wait(); + + let exit_code = match status { + Ok(exit_status) => { + if exit_status.success() { + println!("Command executed successfully!"); + exit_status.code().unwrap_or(0) + } else { + eprintln!("Command failed with exit code: {:?}", exit_status.code()); + exit_status.code().unwrap_or(1) + } + } + Err(err) => { + eprintln!("Failed to wait for command execution: {}", err); + exit(1); + } + }; + + exit(exit_code); +} diff --git a/scripts/postman_test_automation.sh b/scripts/postman_test_automation.sh deleted file mode 100755 index 575e113bfc..0000000000 --- a/scripts/postman_test_automation.sh +++ /dev/null @@ -1,66 +0,0 @@ -#! /usr/bin/env bash -set -euo pipefail - -# Just by the name of the connector, this function generates the name of the collection -# Example: CONNECTOR_NAME="stripe" -> OUTPUT: postman/stripe.postman_collection.json -path_generation() { - local name="${1}" - local collection_name="postman/${name}.postman_collection.json" - echo "${collection_name}" -} - -# This function gets the api keys from the connector_auth.toml file -# Also determines the type of key (HeaderKey, BodyKey, SignatureKey) for the connector -get_api_keys() { - local input="${1}" - # We get $CONNECTOR_CONFIG_PATH from the GITHUB_ENV - result=$(awk -v name="${input}" -F ' // ' 'BEGIN{ flag=0 } /^\[.*\]/{ if ($1 == "["name"]") { flag=1 } else { flag=0 } } flag==1 && /^[^#]/ { print $0 }' "${CONNECTOR_CONFIG_PATH}") - # OUTPUT of result for `` that has `HeaderKey`: - # [] - # api_key = "HeadKey of " - - # Keys are set as variables since some API Keys for connectors such as ACI - # are Base64 encoded and require "Bearer" to be prefixed such as "Bearer Skst45645gey5r#&$==". - # This effectively stops the shell from interpreting the value of the variable as a command. - API_KEY=$(echo "${result}" | awk -F ' = ' '$1 == "api_key" { gsub(/"/, "", $2); print $2 }') - KEY1=$(echo "${result}" | awk -F ' = ' '$1 == "key1" { gsub(/"/, "", $2); print $2 }') - KEY2=$(echo "${result}" | awk -F ' = ' '$1 == "key2" { gsub(/"/, "", $2); print $2 }') - API_SECRET=$(echo "${result}" | awk -F ' = ' '$1 == "api_secret" { gsub(/"/, "", $2); print $2 }') - - # Determine the type of key - if [[ -n "${API_KEY}" && -z "${KEY1}" && -z "${API_SECRET}" ]]; then - KEY_TYPE="HeaderKey" - elif [[ -n "${API_KEY}" && -n "${KEY1}" && -z "${API_SECRET}" ]]; then - KEY_TYPE="BodyKey" - elif [[ -n "${API_KEY}" && -n "${KEY1}" && -n "${API_SECRET}" ]]; then - KEY_TYPE="SignatureKey" - elif [[ -n "${API_KEY}" && -n "${KEY1}" && -n "${KEY2}" && -n "${API_SECRET}" ]]; then - KEY_TYPE="MultiAuthKey" - else - KEY_TYPE="Invalid" - fi -} - -# [ MAIN ] -CONNECTOR_NAME="${1}" -KEY_TYPE="" - -# Function call -COLLECTION_PATH=$(path_generation "${CONNECTOR_NAME}") -get_api_keys "${CONNECTOR_NAME}" - -# Newman runner -# Depending on the conditions satisfied, variables are added. Since certificates of stripe have already -# been added to the postman collection, those conditions are set to true and collections that have -# variables set up for certificate, will consider those variables and will fail. -newman run "${COLLECTION_PATH}" \ - --env-var "admin_api_key=${ADMIN_API_KEY}" \ - --env-var "baseUrl=${BASE_URL}" \ - --env-var "connector_api_key=${API_KEY}" \ - $(if [[ "${KEY_TYPE}" == BodyKey ]]; then echo --env-var "connector_key1=${KEY1}"; fi) \ - $(if [[ "${KEY_TYPE}" == SignatureKey ]]; then echo --env-var "connector_key1=${KEY1}" --env-var "connector_api_secret=${API_SECRET}"; fi) \ - $(if [[ "${KEY_TYPE}" == MultiAuthKey ]]; then echo --env-var "connector_key1=${KEY1}" --env-var "connector_key2=${KEY2}" --env-var "connector_api_secret=${API_SECRET}"; fi) \ - $(if [[ -n "${GATEWAY_MERCHANT_ID}" ]]; then echo --env-var "gateway_merchant_id=${GATEWAY_MERCHANT_ID}"; fi) \ - $(if [[ -n "${GPAY_CERTIFICATE}" ]]; then echo --env-var "certificate=${GPAY_CERTIFICATE}"; fi) \ - $(if [[ -n "${GPAY_CERTIFICATE_KEYS}" ]]; then echo --env-var "certificate_keys=${GPAY_CERTIFICATE_KEYS}"; fi) \ - --delay-request 5