mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 10:06:32 +08:00 
			
		
		
		
	test(connector): Add support for webhook tests (#1863)
Signed-off-by: chikke srujan <121822803+srujanchikke@users.noreply.github.com>
This commit is contained in:
		
							
								
								
									
										
											BIN
										
									
								
								.github/secrets/connector_auth.toml.gpg
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								.github/secrets/connector_auth.toml.gpg
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										2
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -4749,10 +4749,12 @@ dependencies = [ | |||||||
|  "api_models", |  "api_models", | ||||||
|  "async-trait", |  "async-trait", | ||||||
|  "awc", |  "awc", | ||||||
|  |  "base64 0.21.2", | ||||||
|  "clap", |  "clap", | ||||||
|  "derive_deref", |  "derive_deref", | ||||||
|  "masking", |  "masking", | ||||||
|  "rand 0.8.5", |  "rand 0.8.5", | ||||||
|  |  "reqwest", | ||||||
|  "serde", |  "serde", | ||||||
|  "serde_json", |  "serde_json", | ||||||
|  "serde_path_to_error", |  "serde_path_to_error", | ||||||
|  | |||||||
| @ -15,6 +15,7 @@ payouts = [] | |||||||
| [dependencies] | [dependencies] | ||||||
| async-trait = "0.1.68" | async-trait = "0.1.68" | ||||||
| actix-web = "4.3.1" | actix-web = "4.3.1" | ||||||
|  | base64 = "0.21.2" | ||||||
| clap = { version = "4.3.2", default-features = false, features = ["std", "derive", "help", "usage"] } | clap = { version = "4.3.2", default-features = false, features = ["std", "derive", "help", "usage"] } | ||||||
| serde = { version = "1.0.163", features = ["derive"] } | serde = { version = "1.0.163", features = ["derive"] } | ||||||
| serde_json = "1.0.96" | serde_json = "1.0.96" | ||||||
| @ -26,6 +27,7 @@ actix-http = "3.3.1" | |||||||
| awc = { version = "3.1.1", features = ["rustls"] } | awc = { version = "3.1.1", features = ["rustls"] } | ||||||
| derive_deref = "1.1.1" | derive_deref = "1.1.1" | ||||||
| rand = "0.8.5" | rand = "0.8.5" | ||||||
|  | reqwest = { version = "0.11.18", features = ["native-tls"] } | ||||||
| thirtyfour = "0.31.0" | thirtyfour = "0.31.0" | ||||||
| time = { version = "0.3.21", features = ["macros"] } | time = { version = "0.3.21", features = ["macros"] } | ||||||
| tokio = "1.28.2" | tokio = "1.28.2" | ||||||
|  | |||||||
| @ -269,6 +269,9 @@ impl From<MultiAuthKey> for ConnectorAuthType { | |||||||
| pub struct AutomationConfigs { | pub struct AutomationConfigs { | ||||||
|     pub hs_base_url: Option<String>, |     pub hs_base_url: Option<String>, | ||||||
|     pub hs_api_key: Option<String>, |     pub hs_api_key: Option<String>, | ||||||
|  |     pub hs_api_keys: Option<String>, | ||||||
|  |     pub hs_webhook_url: Option<String>, | ||||||
|  |     pub hs_test_env: Option<String>, | ||||||
|     pub hs_test_browser: Option<String>, |     pub hs_test_browser: Option<String>, | ||||||
|     pub chrome_profile_path: Option<String>, |     pub chrome_profile_path: Option<String>, | ||||||
|     pub firefox_profile_path: Option<String>, |     pub firefox_profile_path: Option<String>, | ||||||
|  | |||||||
							
								
								
									
										37
									
								
								crates/test_utils/tests/connectors/authorizedotnet_wh_ui.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								crates/test_utils/tests/connectors/authorizedotnet_wh_ui.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | |||||||
|  | use rand::Rng; | ||||||
|  | use serial_test::serial; | ||||||
|  | use thirtyfour::{prelude::*, WebDriver}; | ||||||
|  |  | ||||||
|  | use crate::{selenium::*, tester}; | ||||||
|  |  | ||||||
|  | struct AuthorizedotnetSeleniumTest; | ||||||
|  |  | ||||||
|  | impl SeleniumTest for AuthorizedotnetSeleniumTest { | ||||||
|  |     fn get_connector_name(&self) -> String { | ||||||
|  |         "authorizedotnet".to_string() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async fn should_make_webhook(web_driver: WebDriver) -> Result<(), WebDriverError> { | ||||||
|  |     let conn = AuthorizedotnetSeleniumTest {}; | ||||||
|  |     let amount = rand::thread_rng().gen_range(50..1000); //This connector detects it as fradulent payment if the same amount is used for multiple payments so random amount is passed for testing( | ||||||
|  |     conn.make_webhook_test( | ||||||
|  |         web_driver, | ||||||
|  |         &format!("{CHEKOUT_BASE_URL}/saved/227?amount={amount}"), | ||||||
|  |         vec![ | ||||||
|  |             Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))), | ||||||
|  |             Event::Assert(Assert::IsPresent("status")), | ||||||
|  |             Event::Assert(Assert::IsPresent("processing")), | ||||||
|  |         ], | ||||||
|  |         10, | ||||||
|  |         "processing", | ||||||
|  |     ) | ||||||
|  |     .await?; | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | #[serial] | ||||||
|  | fn should_make_webhook_test() { | ||||||
|  |     tester!(should_make_webhook); | ||||||
|  | } | ||||||
							
								
								
									
										34
									
								
								crates/test_utils/tests/connectors/bluesnap_wh_ui.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								crates/test_utils/tests/connectors/bluesnap_wh_ui.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | |||||||
|  | use serial_test::serial; | ||||||
|  | use thirtyfour::{prelude::*, WebDriver}; | ||||||
|  |  | ||||||
|  | use crate::{selenium::*, tester}; | ||||||
|  |  | ||||||
|  | struct BluesnapSeleniumTest; | ||||||
|  |  | ||||||
|  | impl SeleniumTest for BluesnapSeleniumTest { | ||||||
|  |     fn get_connector_name(&self) -> String { | ||||||
|  |         "bluesnap".to_string() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async fn should_make_webhook(web_driver: WebDriver) -> Result<(), WebDriverError> { | ||||||
|  |     let conn = BluesnapSeleniumTest {}; | ||||||
|  |     conn.make_webhook_test( | ||||||
|  |         web_driver, | ||||||
|  |         &format!("{CHEKOUT_BASE_URL}/saved/199"), | ||||||
|  |         vec![ | ||||||
|  |             Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))), | ||||||
|  |             Event::Assert(Assert::IsPresent("succeeded")), | ||||||
|  |         ], | ||||||
|  |         5, | ||||||
|  |         "succeeded", | ||||||
|  |     ) | ||||||
|  |     .await?; | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | #[serial] | ||||||
|  | fn should_make_webhook_test() { | ||||||
|  |     tester!(should_make_webhook); | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								crates/test_utils/tests/connectors/checkout_wh_ui.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								crates/test_utils/tests/connectors/checkout_wh_ui.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | |||||||
|  | use serial_test::serial; | ||||||
|  | use thirtyfour::{prelude::*, WebDriver}; | ||||||
|  |  | ||||||
|  | use crate::{selenium::*, tester}; | ||||||
|  |  | ||||||
|  | struct CheckoutSeleniumTest; | ||||||
|  |  | ||||||
|  | impl SeleniumTest for CheckoutSeleniumTest { | ||||||
|  |     fn get_connector_name(&self) -> String { | ||||||
|  |         "checkout".to_string() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async fn should_make_webhook(web_driver: WebDriver) -> Result<(), WebDriverError> { | ||||||
|  |     let conn = CheckoutSeleniumTest {}; | ||||||
|  |     conn.make_webhook_test( | ||||||
|  |         web_driver, | ||||||
|  |         &format!("{CHEKOUT_BASE_URL}/saved/18"), | ||||||
|  |         vec![ | ||||||
|  |             Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))), | ||||||
|  |             Event::Assert(Assert::IsPresent("status")), | ||||||
|  |             Event::Assert(Assert::IsPresent("succeeded")), | ||||||
|  |         ], | ||||||
|  |         5, | ||||||
|  |         "succeeded", | ||||||
|  |     ) | ||||||
|  |     .await?; | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | #[serial] | ||||||
|  | fn should_make_webhook_test() { | ||||||
|  |     tester!(should_make_webhook); | ||||||
|  | } | ||||||
| @ -8,9 +8,12 @@ mod aci_ui; | |||||||
| mod adyen_uk_ui; | mod adyen_uk_ui; | ||||||
| mod airwallex_ui; | mod airwallex_ui; | ||||||
| mod authorizedotnet_ui; | mod authorizedotnet_ui; | ||||||
|  | mod authorizedotnet_wh_ui; | ||||||
| mod bambora_ui; | mod bambora_ui; | ||||||
| mod bluesnap_ui; | mod bluesnap_ui; | ||||||
|  | mod bluesnap_wh_ui; | ||||||
| mod checkout_ui; | mod checkout_ui; | ||||||
|  | mod checkout_wh_ui; | ||||||
| mod globalpay_ui; | mod globalpay_ui; | ||||||
| mod mollie_ui; | mod mollie_ui; | ||||||
| mod multisafepay_ui; | mod multisafepay_ui; | ||||||
| @ -22,6 +25,7 @@ mod payu_ui; | |||||||
| mod selenium; | mod selenium; | ||||||
| mod shift4_ui; | mod shift4_ui; | ||||||
| mod stripe_ui; | mod stripe_ui; | ||||||
|  | mod stripe_wh_ui; | ||||||
| mod trustpay_3ds_ui; | mod trustpay_3ds_ui; | ||||||
| mod worldline_ui; | mod worldline_ui; | ||||||
| mod zen_ui; | mod zen_ui; | ||||||
|  | |||||||
| @ -14,6 +14,8 @@ use std::{ | |||||||
| }; | }; | ||||||
|  |  | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
|  | use base64::Engine; | ||||||
|  | use serde::{Deserialize, Serialize}; | ||||||
| use serde_json::json; | use serde_json::json; | ||||||
| use test_utils::connector_auth; | use test_utils::connector_auth; | ||||||
| use thirtyfour::{components::SelectElement, prelude::*, WebDriver}; | use thirtyfour::{components::SelectElement, prelude::*, WebDriver}; | ||||||
| @ -247,23 +249,18 @@ pub trait SeleniumTest { | |||||||
|                         let saved_tests = |                         let saved_tests = | ||||||
|                             serde_json::to_string(&self.get_saved_testcases()).unwrap(); |                             serde_json::to_string(&self.get_saved_testcases()).unwrap(); | ||||||
|                         let conf = serde_json::to_string(&self.get_configs()).unwrap(); |                         let conf = serde_json::to_string(&self.get_configs()).unwrap(); | ||||||
|                         let hs_base_url = self |                         let configs = self.get_configs().automation_configs.unwrap(); | ||||||
|                             .get_configs() |                         let hs_base_url = configs | ||||||
|                             .automation_configs |  | ||||||
|                             .unwrap() |  | ||||||
|                             .hs_base_url |                             .hs_base_url | ||||||
|                             .unwrap_or_else(|| "http://localhost:8080".to_string()); |                             .unwrap_or_else(|| "http://localhost:8080".to_string()); | ||||||
|                         let configs_url = self |                         let configs_url = configs.configs_url.unwrap(); | ||||||
|                             .get_configs() |                         let hs_api_keys = configs.hs_api_keys.unwrap(); | ||||||
|                             .automation_configs |                         let test_env = configs.hs_test_env.unwrap(); | ||||||
|                             .unwrap() |  | ||||||
|                             .configs_url |  | ||||||
|                             .unwrap(); |  | ||||||
|                         let script = &[ |                         let script = &[ | ||||||
|                             format!("localStorage.configs='{configs_url}'").as_str(), |                             format!("localStorage.configs='{configs_url}'").as_str(), | ||||||
|                             "localStorage.current_env='local'", |                             format!("localStorage.current_env='{test_env}'").as_str(), | ||||||
|                             "localStorage.hs_api_key=''", |                             "localStorage.hs_api_key=''", | ||||||
|                             "localStorage.hs_api_keys=''", |                             format!("localStorage.hs_api_keys='{hs_api_keys}'").as_str(), | ||||||
|                             format!("localStorage.base_url='{hs_base_url}'").as_str(), |                             format!("localStorage.base_url='{hs_base_url}'").as_str(), | ||||||
|                             format!("localStorage.hs_api_configs='{conf}'").as_str(), |                             format!("localStorage.hs_api_configs='{conf}'").as_str(), | ||||||
|                             format!("localStorage.saved_payments=JSON.stringify({saved_tests})") |                             format!("localStorage.saved_payments=JSON.stringify({saved_tests})") | ||||||
| @ -485,6 +482,60 @@ pub trait SeleniumTest { | |||||||
|         affirm_actions.extend(actions); |         affirm_actions.extend(actions); | ||||||
|         self.complete_actions(&driver, affirm_actions).await |         self.complete_actions(&driver, affirm_actions).await | ||||||
|     } |     } | ||||||
|  |     async fn make_webhook_test( | ||||||
|  |         &self, | ||||||
|  |         web_driver: WebDriver, | ||||||
|  |         payment_url: &str, | ||||||
|  |         actions: Vec<Event<'_>>, | ||||||
|  |         webhook_retry_time: u64, | ||||||
|  |         webhook_status: &str, | ||||||
|  |     ) -> Result<(), WebDriverError> { | ||||||
|  |         self.complete_actions( | ||||||
|  |             &web_driver, | ||||||
|  |             vec![Event::Trigger(Trigger::Goto(payment_url))], | ||||||
|  |         ) | ||||||
|  |         .await?; | ||||||
|  |         self.complete_actions(&web_driver, actions).await?; //additional actions needs to make a payment | ||||||
|  |         self.complete_actions( | ||||||
|  |             &web_driver, | ||||||
|  |             vec![Event::Trigger(Trigger::Goto(&format!( | ||||||
|  |                 "{CHEKOUT_BASE_URL}/events" | ||||||
|  |             )))], | ||||||
|  |         ) | ||||||
|  |         .await?; | ||||||
|  |         let element = web_driver.query(By::Css("h2.last-payment")).first().await?; | ||||||
|  |         let payment_id = element.text().await?; | ||||||
|  |         let retries = 3; // no of retry times | ||||||
|  |         for _i in 0..retries { | ||||||
|  |             let configs = self.get_configs().automation_configs.unwrap(); | ||||||
|  |             let outgoing_webhook_url = configs.hs_webhook_url.unwrap().to_string(); | ||||||
|  |             let client = reqwest::Client::new(); | ||||||
|  |             let response = client.get(outgoing_webhook_url).send().await.unwrap(); // get events from outgoing webhook endpoint | ||||||
|  |             let body_text = response.text().await.unwrap(); | ||||||
|  |             let data: WebhookResponse = serde_json::from_str(&body_text).unwrap(); | ||||||
|  |             let last_three_events = &data.data[data.data.len().saturating_sub(3)..]; // Get the last three elements if available | ||||||
|  |             for last_event in last_three_events { | ||||||
|  |                 let last_event_body = &last_event.step.request.body; | ||||||
|  |                 let decoded_bytes = base64::engine::general_purpose::STANDARD //decode the encoded outgoing webhook event | ||||||
|  |                     .decode(last_event_body) | ||||||
|  |                     .unwrap(); | ||||||
|  |                 let decoded_str = String::from_utf8(decoded_bytes).unwrap(); | ||||||
|  |                 let webhook_response: HsWebhookResponse = | ||||||
|  |                     serde_json::from_str(&decoded_str).unwrap(); | ||||||
|  |                 if payment_id == webhook_response.content.object.payment_id | ||||||
|  |                     && webhook_status == webhook_response.content.object.status | ||||||
|  |                 { | ||||||
|  |                     return Ok(()); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             self.complete_actions( | ||||||
|  |                 &web_driver, | ||||||
|  |                 vec![Event::Trigger(Trigger::Sleep(webhook_retry_time))], | ||||||
|  |             ) | ||||||
|  |             .await?; | ||||||
|  |         } | ||||||
|  |         Err(WebDriverError::CustomError("Webhook Not Found".to_string())) | ||||||
|  |     } | ||||||
|     async fn make_paypal_payment( |     async fn make_paypal_payment( | ||||||
|         &self, |         &self, | ||||||
|         web_driver: WebDriver, |         web_driver: WebDriver, | ||||||
| @ -827,3 +878,40 @@ pub fn handle_test_error( | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Serialize, Deserialize)] | ||||||
|  | pub struct WebhookResponse { | ||||||
|  |     data: Vec<WebhookResponseData>, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Serialize, Deserialize)] | ||||||
|  | pub struct WebhookResponseData { | ||||||
|  |     step: WebhookRequestData, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Serialize, Deserialize)] | ||||||
|  | pub struct WebhookRequestData { | ||||||
|  |     request: WebhookRequest, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Serialize, Deserialize)] | ||||||
|  | pub struct WebhookRequest { | ||||||
|  |     body: String, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Serialize, Deserialize)] | ||||||
|  | pub struct HsWebhookResponse { | ||||||
|  |     content: HsWebhookContent, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Serialize, Deserialize)] | ||||||
|  | pub struct HsWebhookContent { | ||||||
|  |     object: HsWebhookObject, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Serialize, Deserialize)] | ||||||
|  | pub struct HsWebhookObject { | ||||||
|  |     payment_id: String, | ||||||
|  |     status: String, | ||||||
|  |     connector: String, | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										36
									
								
								crates/test_utils/tests/connectors/stripe_wh_ui.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								crates/test_utils/tests/connectors/stripe_wh_ui.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | |||||||
|  | use serial_test::serial; | ||||||
|  | use thirtyfour::{prelude::*, WebDriver}; | ||||||
|  |  | ||||||
|  | use crate::{selenium::*, tester}; | ||||||
|  |  | ||||||
|  | struct StripeSeleniumTest; | ||||||
|  |  | ||||||
|  | impl SeleniumTest for StripeSeleniumTest { | ||||||
|  |     fn get_connector_name(&self) -> String { | ||||||
|  |         "stripe".to_string() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async fn should_make_webhook(web_driver: WebDriver) -> Result<(), WebDriverError> { | ||||||
|  |     let conn = StripeSeleniumTest {}; | ||||||
|  |     conn.make_webhook_test( | ||||||
|  |         web_driver, | ||||||
|  |         &format!("{CHEKOUT_BASE_URL}/saved/16"), | ||||||
|  |         vec![ | ||||||
|  |             Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))), | ||||||
|  |             Event::Assert(Assert::IsPresent("status")), | ||||||
|  |             Event::Assert(Assert::IsPresent("succeeded")), | ||||||
|  |         ], | ||||||
|  |         7, | ||||||
|  |         "succeeded", | ||||||
|  |     ) | ||||||
|  |     .await?; | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | #[serial] | ||||||
|  |  | ||||||
|  | fn should_make_webhook_test() { | ||||||
|  |     tester!(should_make_webhook); | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user
	 chikke srujan
					chikke srujan