mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-31 01:57:45 +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