diff --git a/Cargo.toml b/Cargo.toml index 400f2eccb..f35eb25d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,32 +10,25 @@ readme = "./README.md" license = "MIT" [dependencies] -chrono = {version = "0.4", optional = true, default-features = false, features = ["clock", "serde"] } -openssl = {version = "0.10", optional = true} -reqwest = {version = "0.11", features = ["json"]} -serde = {version="1.0", features= ["derive"]} +chrono = { version = "0.4", optional = true, default-features = false, features = [ + "clock", + "serde", +] } +openssl = { version = "0.10", optional = true } +reqwest = { version = "0.11", features = ["json"] } +serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_repr = "0.1" thiserror = "1.0.37" -wiremock = "0.5" + [dev-dependencies] dotenv = "0.15" -tokio = {version = "1", features = ["rt", "rt-multi-thread", "macros"]} +tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } wiremock = "0.5" [features] -default = [ - "account_balance", - "b2b", - "b2c", - "bill_manager", - "c2b_register", - "c2b_simulate", - "express_request", - "transaction_reversal", - "transaction_status" -] +default = ["account_balance", "express_request", "transaction_status"] account_balance = ["dep:openssl"] b2b = ["dep:openssl"] b2c = ["dep:openssl"] @@ -44,4 +37,4 @@ c2b_register = [] c2b_simulate = [] express_request = ["dep:chrono"] transaction_reversal = ["dep:openssl"] -transaction_status= ["dep:openssl"] +transaction_status = ["dep:openssl"] diff --git a/src/client.rs b/src/client.rs index 65c87ee7b..79580d6f7 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,9 +1,6 @@ use crate::environment::ApiEnvironment; use crate::services::{ - AccountBalanceBuilder, B2bBuilder, B2cBuilder, BulkInvoiceBuilder, C2bRegisterBuilder, - C2bSimulateBuilder, CancelInvoiceBuilder, MpesaExpressRequestBuilder, OnboardBuilder, - OnboardModifyBuilder, ReconciliationBuilder, SingleInvoiceBuilder, TransactionReversalBuilder, - TransactionStatusBuilder, + AccountBalanceBuilder, MpesaExpressRequestBuilder, TransactionStatusBuilder, }; use crate::{ApiError, MpesaError}; use openssl::base64; @@ -13,6 +10,24 @@ use reqwest::Client as HttpClient; use serde_json::Value; use std::cell::RefCell; +#[cfg(feature = "b2b")] +pub use crate::services::B2bBuilder; +#[cfg(feature = "b2c")] +pub use crate::services::B2cBuilder; +#[cfg(feature = "bill_manager")] +pub use crate::services::{ + BulkInvoiceBuilder, CancelInvoiceBuilder, OnboardBuilder, OnboardModifyBuilder, + ReconciliationBuilder, SingleInvoiceBuilder, +}; + +#[cfg(feature = "c2b_register")] +pub use crate::services::C2bRegisterBuilder; +#[cfg(feature = "c2b_simulate")] +pub use crate::services::C2bSimulateBuilder; + +#[cfg(feature = "transaction_reversal")] +pub use crate::services::TransactionReversalBuilder; + /// Source: [test credentials](https://developer.safaricom.co.ke/test_credentials) const DEFAULT_INITIATOR_PASSWORD: &str = "Safcom496!"; /// Get current package version from metadata diff --git a/src/lib.rs b/src/lib.rs index 7a9e84138..3cc41f58d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,9 @@ pub mod environment; mod errors; pub mod services; +#[cfg(test)] +mod test_utils; + pub use client::{Mpesa, MpesaResult}; pub use constants::{ CommandId, IdentifierTypes, Invoice, InvoiceItem, ResponseType, SendRemindersTypes, diff --git a/src/services/express_request.rs b/src/services/express_request.rs index a08ce2d16..9bce2bc5d 100644 --- a/src/services/express_request.rs +++ b/src/services/express_request.rs @@ -272,3 +272,144 @@ impl<'mpesa, Env: ApiEnvironment> MpesaExpressRequestBuilder<'mpesa, Env> { Err(MpesaError::MpesaExpressRequestError(value)) } } + +#[cfg(test)] +mod tests { + use crate::get_mpesa_client; + use mpesa::MpesaError; + use serde_json::json; + use wiremock::matchers::{method, path}; + use wiremock::{Mock, ResponseTemplate}; + + #[tokio::test] + async fn stk_push_success_success() { + let (client, server) = get_mpesa_client!(); + let sample_response_body = json!({ + "MerchantRequestID": "16813-1590513-1", + "CheckoutRequestID": "ws_CO_DMZ_12321_23423476", + "ResponseDescription": "Accept the service request successfully.", + "ResponseCode": "0", + "CustomerMessage": "Success. Request accepeted for processing" + }); + Mock::given(method("POST")) + .and(path("/mpesa/stkpush/v1/processrequest")) + .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) + .expect(1) + .mount(&server) + .await; + let response = client + .express_request("174379") + .phone_number("254708374149") + .amount(500) + .callback_url("https://test.example.com/api") + .send() + .await + .unwrap(); + assert_eq!(response.merchant_request_id, "16813-1590513-1"); + assert_eq!(response.checkout_request_id, "ws_CO_DMZ_12321_23423476"); + assert_eq!( + response.response_description, + "Accept the service request successfully." + ); + assert_eq!( + response.customer_message, + "Success. Request accepeted for processing" + ); + } + + #[tokio::test] + async fn stk_push_fails_if_no_amount_is_provided() { + let (client, server) = get_mpesa_client!(expected_auth_requests = 0); + let sample_response_body = json!({ + "MerchantRequestID": "16813-1590513-1", + "CheckoutRequestID": "ws_CO_DMZ_12321_23423476", + "ResponseDescription": "Accept the service request successfully.", + "ResponseCode": "0", + "CustomerMessage": "Success. Request accepeted for processing" + }); + Mock::given(method("POST")) + .and(path("/mpesa/stkpush/v1/processrequest")) + .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) + .expect(0) + .mount(&server) + .await; + if let Err(e) = client + .express_request("174379") + .phone_number("254708374149") + .callback_url("https://test.example.com/api") + .send() + .await + { + let MpesaError::Message(msg) = e else { + panic!("Expected MpesaError::Message, but found {}", e); + }; + assert_eq!(msg, "amount is required") + } else { + panic!("Expected error"); + } + } + + #[tokio::test] + async fn stk_push_fails_if_no_callback_url_is_provided() { + let (client, server) = get_mpesa_client!(expected_auth_requests = 0); + let sample_response_body = json!({ + "MerchantRequestID": "16813-1590513-1", + "CheckoutRequestID": "ws_CO_DMZ_12321_23423476", + "ResponseDescription": "Accept the service request successfully.", + "ResponseCode": "0", + "CustomerMessage": "Success. Request accepeted for processing" + }); + Mock::given(method("POST")) + .and(path("/mpesa/stkpush/v1/processrequest")) + .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) + .expect(0) + .mount(&server) + .await; + if let Err(e) = client + .express_request("174379") + .phone_number("254708374149") + .amount(500) + .send() + .await + { + let MpesaError::Message(msg) = e else { + panic!("Expected MpesaError::Message, but found {}", e); + }; + assert_eq!(msg, "callback_url is required") + } else { + panic!("Expected error"); + } + } + + #[tokio::test] + async fn stk_push_fails_if_no_phone_number_is_provided() { + let (client, server) = get_mpesa_client!(expected_auth_requests = 0); + let sample_response_body = json!({ + "MerchantRequestID": "16813-1590513-1", + "CheckoutRequestID": "ws_CO_DMZ_12321_23423476", + "ResponseDescription": "Accept the service request successfully.", + "ResponseCode": "0", + "CustomerMessage": "Success. Request accepeted for processing" + }); + Mock::given(method("POST")) + .and(path("/mpesa/stkpush/v1/processrequest")) + .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) + .expect(0) + .mount(&server) + .await; + if let Err(e) = client + .express_request("174379") + .amount(500) + .callback_url("https://test.example.com/api") + .send() + .await + { + let MpesaError::Message(msg) = e else { + panic!("Expected MpesaError::Message, but found {}", e); + }; + assert_eq!(msg, "phone_number is required") + } else { + panic!("Expected error"); + } + } +} diff --git a/src/services/mod.rs b/src/services/mod.rs index 3ea340f88..25cf0268a 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -14,14 +14,23 @@ //! 7. [Transaction Reversal](https://developer.safaricom.co.ke/docs#reversal) //! 8. [Bill Manager](https://developer.safaricom.co.ke/APIs/BillManager) +#[cfg(feature = "account_balance")] mod account_balance; +#[cfg(feature = "b2b")] mod b2b; +#[cfg(feature = "b2c")] mod b2c; +#[cfg(feature = "bill_manager")] mod bill_manager; +#[cfg(feature = "c2b_register")] mod c2b_register; +#[cfg(feature = "c2b_simulate")] mod c2b_simulate; +#[cfg(feature = "express_request")] mod express_request; +#[cfg(feature = "transaction_reversal")] mod transaction_reversal; +#[cfg(feature = "transaction_status")] mod transaction_status; #[cfg(feature = "account_balance")] diff --git a/src/services/transaction_reversal.rs b/src/services/transaction_reversal.rs index 5aec81853..6f16e9b4f 100644 --- a/src/services/transaction_reversal.rs +++ b/src/services/transaction_reversal.rs @@ -226,3 +226,205 @@ impl<'mpesa, Env: ApiEnvironment> TransactionReversalBuilder<'mpesa, Env> { Ok(response) } } + +#[cfg(test)] +mod tests { + use crate::get_mpesa_client; + use mpesa::MpesaError; + use serde_json::json; + use wiremock::matchers::{method, path}; + use wiremock::{Mock, ResponseTemplate}; + + #[tokio::test] + async fn transaction_reversal_success() { + let (client, server) = get_mpesa_client!(); + let sample_response_body = json!({ + "OriginatorConversationID": "29464-48063588-1", + "ConversationID": "AG_20230206_201056794190723278ff", + "ResponseDescription": "Accept the service request successfully.", + }); + Mock::given(method("POST")) + .and(path("/mpesa/reversal/v1/request")) + .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) + .expect(1) + .mount(&server) + .await; + let response = client + .transaction_reversal("testapi496") + .result_url("https://testdomain.com/ok") + .timeout_url("https://testdomain.com/err") + .transaction_id("OEI2AK4Q16") + .amount(1.0) + .receiver_party("600111") + .remarks("wrong recipient") + .send() + .await + .unwrap(); + assert_eq!(response.originator_conversation_id, "29464-48063588-1"); + assert_eq!(response.conversation_id, "AG_20230206_201056794190723278ff"); + assert_eq!( + response.response_description, + "Accept the service request successfully." + ); + } + + #[tokio::test] + async fn transaction_reversal_fails_if_no_transaction_id_is_provided() { + let (client, server) = get_mpesa_client!(expected_auth_requests = 0); + let sample_response_body = json!({ + "OriginatorConversationID": "29464-48063588-1", + "ConversationID": "AG_20230206_201056794190723278ff", + "ResponseDescription": "Accept the service request successfully.", + }); + Mock::given(method("POST")) + .and(path("/mpesa/reversal/v1/request")) + .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) + .expect(0) + .mount(&server) + .await; + if let Err(e) = client + .transaction_reversal("testapi496") + .result_url("https://testdomain.com/ok") + .timeout_url("https://testdomain.com/err") + .amount(1.0) + .receiver_party("600111") + .send() + .await + { + let MpesaError::Message(msg) = e else { + panic!("Expected MpesaError::Message, but found {}", e); + }; + assert_eq!(msg, "transaction_id is required"); + } else { + panic!("Expected error"); + } + } + + #[tokio::test] + async fn transaction_reversal_fails_if_no_amount_is_provided() { + let (client, server) = get_mpesa_client!(expected_auth_requests = 0); + let sample_response_body = json!({ + "OriginatorConversationID": "29464-48063588-1", + "ConversationID": "AG_20230206_201056794190723278ff", + "ResponseDescription": "Accept the service request successfully.", + }); + Mock::given(method("POST")) + .and(path("/mpesa/reversal/v1/request")) + .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) + .expect(0) + .mount(&server) + .await; + if let Err(e) = client + .transaction_reversal("testapi496") + .transaction_id("OEI2AK4Q16") + .result_url("https://testdomain.com/ok") + .timeout_url("https://testdomain.com/err") + .receiver_party("600111") + .send() + .await + { + let MpesaError::Message(msg) = e else { + panic!("Expected MpesaError::Message, but found {}", e); + }; + assert_eq!(msg, "amount is required") + } else { + panic!("Expected error"); + } + } + + #[tokio::test] + async fn transaction_reversal_fails_if_no_result_url_is_provided() { + let (client, server) = get_mpesa_client!(expected_auth_requests = 0); + let sample_response_body = json!({ + "OriginatorConversationID": "29464-48063588-1", + "ConversationID": "AG_20230206_201056794190723278ff", + "ResponseDescription": "Accept the service request successfully.", + }); + Mock::given(method("POST")) + .and(path("/mpesa/reversal/v1/request")) + .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) + .expect(0) + .mount(&server) + .await; + if let Err(e) = client + .transaction_reversal("testapi496") + .transaction_id("OEI2AK4Q16") + .amount(1.0) + .result_url("https://testdomain.com/ok") + .receiver_party("600111") + .send() + .await + { + let MpesaError::Message(msg) = e else { + panic!("Expected MpesaError::Message, but found {}", e); + }; + assert_eq!(msg, "timeout_url is required") + } else { + panic!("Expected error"); + } + } + + #[tokio::test] + async fn transaction_reversal_fails_if_no_timeout_url_is_provided() { + let (client, server) = get_mpesa_client!(expected_auth_requests = 0); + let sample_response_body = json!({ + "OriginatorConversationID": "29464-48063588-1", + "ConversationID": "AG_20230206_201056794190723278ff", + "ResponseDescription": "Accept the service request successfully.", + }); + Mock::given(method("POST")) + .and(path("/mpesa/reversal/v1/request")) + .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) + .expect(0) + .mount(&server) + .await; + if let Err(e) = client + .transaction_reversal("testapi496") + .transaction_id("OEI2AK4Q16") + .amount(1.0) + .timeout_url("https://testdomain.com/err") + .receiver_party("600111") + .send() + .await + { + let MpesaError::Message(msg) = e else { + panic!("Expected MpesaError::Message, but found {}", e); + }; + assert_eq!(msg, "result_url is required") + } else { + panic!("Expected error"); + } + } + + #[tokio::test] + async fn transaction_reversal_fails_if_no_receiver_party_is_provided() { + let (client, server) = get_mpesa_client!(expected_auth_requests = 0); + let sample_response_body = json!({ + "OriginatorConversationID": "29464-48063588-1", + "ConversationID": "AG_20230206_201056794190723278ff", + "ResponseDescription": "Accept the service request successfully.", + }); + Mock::given(method("POST")) + .and(path("/mpesa/reversal/v1/request")) + .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) + .expect(0) + .mount(&server) + .await; + if let Err(e) = client + .transaction_reversal("testapi496") + .transaction_id("OEI2AK4Q16") + .amount(1.0) + .result_url("https://testdomain.com/ok") + .timeout_url("https://testdomain.com/err") + .send() + .await + { + let MpesaError::Message(msg) = e else { + panic!("Expected MpesaError::Message, but found {}", e); + }; + assert_eq!(msg, "receiver_party is required") + } else { + panic!("Expected error"); + } + } +} diff --git a/src/services/transaction_status.rs b/src/services/transaction_status.rs index eac9f5552..85ba39af4 100644 --- a/src/services/transaction_status.rs +++ b/src/services/transaction_status.rs @@ -206,3 +206,171 @@ impl<'mpesa, Env: ApiEnvironment> TransactionStatusBuilder<'mpesa, Env> { Ok(response) } } + +#[cfg(test)] +mod tests { + use crate::MpesaError; + + use serde_json::json; + use wiremock::matchers::{method, path}; + use wiremock::{Mock, ResponseTemplate}; + + use crate::get_mpesa_client; + + #[tokio::test] + async fn transaction_status_success() { + let (client, server) = get_mpesa_client!(); + let sample_response_body = json!({ + "OriginatorConversationID": "29464-48063588-1", + "ConversationID": "AG_20230206_201056794190723278ff", + "ResponseDescription": "Accept the service request successfully.", + }); + Mock::given(method("POST")) + .and(path("/mpesa/transactionstatus/v1/query")) + .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) + .expect(1) + .mount(&server) + .await; + let response = client + .transaction_status("testapi496") + .result_url("https://testdomain.com/ok") + .timeout_url("https://testdomain.com/err") + .transaction_id("OEI2AK4Q16") + .party_a("600111") + .remarks("status") + .occasion("work") + .send() + .await + .unwrap(); + assert_eq!(response.originator_conversation_id, "29464-48063588-1"); + assert_eq!(response.conversation_id, "AG_20230206_201056794190723278ff"); + assert_eq!( + response.response_description, + "Accept the service request successfully." + ); + } + + #[tokio::test] + async fn transaction_status_fails_if_transaction_id_is_not_provided() { + let (client, server) = get_mpesa_client!(expected_auth_requests = 0); + let sample_response_body = json!({ + "OriginatorConversationID": "29464-48063588-1", + "ConversationID": "AG_20230206_201056794190723278ff", + "ResponseDescription": "Accept the service request successfully.", + }); + Mock::given(method("POST")) + .and(path("/mpesa/transactionstatus/v1/query")) + .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) + .expect(0) + .mount(&server) + .await; + if let Err(e) = client + .transaction_status("testapi496") + .result_url("https://testdomain.com/ok") + .timeout_url("https://testdomain.com/err") + .party_a("600111") + .send() + .await + { + let MpesaError::Message(msg) = e else { + panic!("Expected MpesaError::Message, but found {}", e); + }; + assert_eq!(msg, "transaction_id is required"); + } else { + panic!("Expected error") + } + } + + #[tokio::test] + async fn transaction_status_fails_if_party_a_is_not_provided() { + let (client, server) = get_mpesa_client!(expected_auth_requests = 0); + let sample_response_body = json!({ + "OriginatorConversationID": "29464-48063588-1", + "ConversationID": "AG_20230206_201056794190723278ff", + "ResponseDescription": "Accept the service request successfully.", + }); + Mock::given(method("POST")) + .and(path("/mpesa/transactionstatus/v1/query")) + .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) + .expect(0) + .mount(&server) + .await; + if let Err(e) = client + .transaction_status("testapi496") + .result_url("https://testdomain.com/ok") + .timeout_url("https://testdomain.com/err") + .transaction_id("OEI2AK4Q16") + .send() + .await + { + let MpesaError::Message(msg) = e else { + panic!("Expected MpesaError::Message, but found {}", e); + }; + assert_eq!(msg, "party_a is required"); + } else { + panic!("Expected error") + } + } + + #[tokio::test] + async fn transaction_status_fails_if_result_url_is_not_provided() { + let (client, server) = get_mpesa_client!(expected_auth_requests = 0); + let sample_response_body = json!({ + "OriginatorConversationID": "29464-48063588-1", + "ConversationID": "AG_20230206_201056794190723278ff", + "ResponseDescription": "Accept the service request successfully.", + }); + Mock::given(method("POST")) + .and(path("/mpesa/transactionstatus/v1/query")) + .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) + .expect(0) + .mount(&server) + .await; + if let Err(e) = client + .transaction_status("testapi496") + .timeout_url("https://testdomain.com/err") + .transaction_id("OEI2AK4Q16") + .party_a("600111") + .send() + .await + { + let MpesaError::Message(msg) = e else { + panic!("Expected MpesaError::Message, but found {}", e); + }; + assert_eq!(msg, "result_url is required"); + } else { + panic!("Expected error") + } + } + + #[tokio::test] + async fn transaction_status_fails_if_timeout_url_is_not_provided() { + let (client, server) = get_mpesa_client!(expected_auth_requests = 0); + let sample_response_body = json!({ + "OriginatorConversationID": "29464-48063588-1", + "ConversationID": "AG_20230206_201056794190723278ff", + "ResponseDescription": "Accept the service request successfully.", + }); + Mock::given(method("POST")) + .and(path("/mpesa/transactionstatus/v1/query")) + .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) + .expect(0) + .mount(&server) + .await; + if let Err(e) = client + .transaction_status("testapi496") + .result_url("https://testdomain.com/ok") + .transaction_id("OEI2AK4Q16") + .party_a("600111") + .send() + .await + { + let MpesaError::Message(msg) = e else { + panic!("Expected MpesaError::Message, but found {}", e); + }; + assert_eq!(msg, "timeout_url is required"); + } else { + panic!("Expected error") + } + } +} diff --git a/src/test_utils.rs b/src/test_utils.rs new file mode 100644 index 000000000..6d8e17d41 --- /dev/null +++ b/src/test_utils.rs @@ -0,0 +1,93 @@ +use crate::ApiEnvironment; +use wiremock::MockServer; + +#[derive(Debug, Clone)] +pub struct TestEnvironment { + pub server_url: String, +} + +impl TestEnvironment { + pub async fn new(server: &MockServer) -> Self { + TestEnvironment { + server_url: server.uri(), + } + } +} + +impl ApiEnvironment for TestEnvironment { + fn base_url(&self) -> &str { + &self.server_url + } + + fn get_certificate(&self) -> &str { + include_str!("./certificates/sandbox") + } +} + +#[macro_export] +macro_rules! get_mpesa_client { + () => {{ + get_mpesa_client!(expected_auth_requests = 1) + }}; + + (expected_auth_requests = $expected_requests: expr) => {{ + use $crate::test_utils::TestEnvironment; + use $crate::Mpesa; + use wiremock::{MockServer, Mock, ResponseTemplate}; + use serde_json::json; + use wiremock::matchers::{path, query_param, method}; + + dotenv::dotenv().ok(); + let server = MockServer::start().await; + let test_environment = TestEnvironment::new(&server).await; + let client = Mpesa::new( + std::env::var("CLIENT_KEY").unwrap(), + std::env::var("CLIENT_SECRET").unwrap(), + test_environment, + ); + Mock::given(method("GET")) + .and(path("/oauth/v1/generate")) + .and(query_param("grant_type", "client_credentials")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "access_token": "dummy_access_token" + }))) + .expect($expected_requests) + .mount(&server) + .await; + (client, server) + }}; + + ($client_key:expr, $client_secret:expr) => {{ + use $crate::{Environment, Mpesa}; + use std::str::FromStr; + dotenv::dotenv().ok(); + let client = Mpesa::new( + $client_key, + $client_secret, + Environment::from_str("sandbox").unwrap(), + ); + client + }}; + + ($client_key:expr, $client_secret:expr, $environment:expr) => {{ + use mpesa::{Environment, Mpesa}; + use std::str::FromStr; + dotenv::dotenv().ok(); + let client = Mpesa::new($client_key, $client_secret, $environment); + client + }}; +} + +#[cfg(test)] +mod tests { + use crate::get_mpesa_client; + + #[tokio::test] + async fn test_client_will_not_authenticate_with_wrong_credentials() { + let client = get_mpesa_client!( + "not a client key".to_string(), + "not a client secret".to_string() + ); + assert!(!client.is_connected().await); + } +} diff --git a/tests/mpesa-rust/transaction_reversal_test.rs b/tests/mpesa-rust/transaction_reversal_test.rs deleted file mode 100644 index a5b39ad9a..000000000 --- a/tests/mpesa-rust/transaction_reversal_test.rs +++ /dev/null @@ -1,198 +0,0 @@ -use crate::get_mpesa_client; -use mpesa::MpesaError; -use serde_json::json; -use wiremock::matchers::{method, path}; -use wiremock::{Mock, ResponseTemplate}; - -#[tokio::test] -async fn transaction_reversal_success() { - let (client, server) = get_mpesa_client!(); - let sample_response_body = json!({ - "OriginatorConversationID": "29464-48063588-1", - "ConversationID": "AG_20230206_201056794190723278ff", - "ResponseDescription": "Accept the service request successfully.", - }); - Mock::given(method("POST")) - .and(path("/mpesa/reversal/v1/request")) - .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) - .expect(1) - .mount(&server) - .await; - let response = client - .transaction_reversal("testapi496") - .result_url("https://testdomain.com/ok") - .timeout_url("https://testdomain.com/err") - .transaction_id("OEI2AK4Q16") - .amount(1.0) - .receiver_party("600111") - .remarks("wrong recipient") - .send() - .await - .unwrap(); - assert_eq!(response.originator_conversation_id, "29464-48063588-1"); - assert_eq!(response.conversation_id, "AG_20230206_201056794190723278ff"); - assert_eq!( - response.response_description, - "Accept the service request successfully." - ); -} - -#[tokio::test] -async fn transaction_reversal_fails_if_no_transaction_id_is_provided() { - let (client, server) = get_mpesa_client!(expected_auth_requests = 0); - let sample_response_body = json!({ - "OriginatorConversationID": "29464-48063588-1", - "ConversationID": "AG_20230206_201056794190723278ff", - "ResponseDescription": "Accept the service request successfully.", - }); - Mock::given(method("POST")) - .and(path("/mpesa/reversal/v1/request")) - .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) - .expect(0) - .mount(&server) - .await; - if let Err(e) = client - .transaction_reversal("testapi496") - .result_url("https://testdomain.com/ok") - .timeout_url("https://testdomain.com/err") - .amount(1.0) - .receiver_party("600111") - .send() - .await - { - let MpesaError::Message(msg) = e else { - panic!("Expected MpesaError::Message, but found {}", e); - }; - assert_eq!(msg, "transaction_id is required"); - } else { - panic!("Expected error"); - } -} - -#[tokio::test] -async fn transaction_reversal_fails_if_no_amount_is_provided() { - let (client, server) = get_mpesa_client!(expected_auth_requests = 0); - let sample_response_body = json!({ - "OriginatorConversationID": "29464-48063588-1", - "ConversationID": "AG_20230206_201056794190723278ff", - "ResponseDescription": "Accept the service request successfully.", - }); - Mock::given(method("POST")) - .and(path("/mpesa/reversal/v1/request")) - .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) - .expect(0) - .mount(&server) - .await; - if let Err(e) = client - .transaction_reversal("testapi496") - .transaction_id("OEI2AK4Q16") - .result_url("https://testdomain.com/ok") - .timeout_url("https://testdomain.com/err") - .receiver_party("600111") - .send() - .await - { - let MpesaError::Message(msg) = e else { - panic!("Expected MpesaError::Message, but found {}", e); - }; - assert_eq!(msg, "amount is required") - } else { - panic!("Expected error"); - } -} - -#[tokio::test] -async fn transaction_reversal_fails_if_no_result_url_is_provided() { - let (client, server) = get_mpesa_client!(expected_auth_requests = 0); - let sample_response_body = json!({ - "OriginatorConversationID": "29464-48063588-1", - "ConversationID": "AG_20230206_201056794190723278ff", - "ResponseDescription": "Accept the service request successfully.", - }); - Mock::given(method("POST")) - .and(path("/mpesa/reversal/v1/request")) - .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) - .expect(0) - .mount(&server) - .await; - if let Err(e) = client - .transaction_reversal("testapi496") - .transaction_id("OEI2AK4Q16") - .amount(1.0) - .result_url("https://testdomain.com/ok") - .receiver_party("600111") - .send() - .await - { - let MpesaError::Message(msg) = e else { - panic!("Expected MpesaError::Message, but found {}", e); - }; - assert_eq!(msg, "timeout_url is required") - } else { - panic!("Expected error"); - } -} - -#[tokio::test] -async fn transaction_reversal_fails_if_no_timeout_url_is_provided() { - let (client, server) = get_mpesa_client!(expected_auth_requests = 0); - let sample_response_body = json!({ - "OriginatorConversationID": "29464-48063588-1", - "ConversationID": "AG_20230206_201056794190723278ff", - "ResponseDescription": "Accept the service request successfully.", - }); - Mock::given(method("POST")) - .and(path("/mpesa/reversal/v1/request")) - .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) - .expect(0) - .mount(&server) - .await; - if let Err(e) = client - .transaction_reversal("testapi496") - .transaction_id("OEI2AK4Q16") - .amount(1.0) - .timeout_url("https://testdomain.com/err") - .receiver_party("600111") - .send() - .await - { - let MpesaError::Message(msg) = e else { - panic!("Expected MpesaError::Message, but found {}", e); - }; - assert_eq!(msg, "result_url is required") - } else { - panic!("Expected error"); - } -} - -#[tokio::test] -async fn transaction_reversal_fails_if_no_receiver_party_is_provided() { - let (client, server) = get_mpesa_client!(expected_auth_requests = 0); - let sample_response_body = json!({ - "OriginatorConversationID": "29464-48063588-1", - "ConversationID": "AG_20230206_201056794190723278ff", - "ResponseDescription": "Accept the service request successfully.", - }); - Mock::given(method("POST")) - .and(path("/mpesa/reversal/v1/request")) - .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) - .expect(0) - .mount(&server) - .await; - if let Err(e) = client - .transaction_reversal("testapi496") - .transaction_id("OEI2AK4Q16") - .amount(1.0) - .result_url("https://testdomain.com/ok") - .timeout_url("https://testdomain.com/err") - .send() - .await - { - let MpesaError::Message(msg) = e else { - panic!("Expected MpesaError::Message, but found {}", e); - }; - assert_eq!(msg, "receiver_party is required") - } else { - panic!("Expected error"); - } -} diff --git a/tests/mpesa-rust/transaction_status_test.rs b/tests/mpesa-rust/transaction_status_test.rs deleted file mode 100644 index b0011c1a2..000000000 --- a/tests/mpesa-rust/transaction_status_test.rs +++ /dev/null @@ -1,164 +0,0 @@ -use mpesa::MpesaError; - -use serde_json::json; -use wiremock::matchers::{method, path}; -use wiremock::{Mock, ResponseTemplate}; - -use crate::get_mpesa_client; - -#[tokio::test] -async fn transaction_status_success() { - let (client, server) = get_mpesa_client!(); - let sample_response_body = json!({ - "OriginatorConversationID": "29464-48063588-1", - "ConversationID": "AG_20230206_201056794190723278ff", - "ResponseDescription": "Accept the service request successfully.", - }); - Mock::given(method("POST")) - .and(path("/mpesa/transactionstatus/v1/query")) - .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) - .expect(1) - .mount(&server) - .await; - let response = client - .transaction_status("testapi496") - .result_url("https://testdomain.com/ok") - .timeout_url("https://testdomain.com/err") - .transaction_id("OEI2AK4Q16") - .party_a("600111") - .remarks("status") - .occasion("work") - .send() - .await - .unwrap(); - assert_eq!(response.originator_conversation_id, "29464-48063588-1"); - assert_eq!(response.conversation_id, "AG_20230206_201056794190723278ff"); - assert_eq!( - response.response_description, - "Accept the service request successfully." - ); -} - -#[tokio::test] -async fn transaction_status_fails_if_transaction_id_is_not_provided() { - let (client, server) = get_mpesa_client!(expected_auth_requests = 0); - let sample_response_body = json!({ - "OriginatorConversationID": "29464-48063588-1", - "ConversationID": "AG_20230206_201056794190723278ff", - "ResponseDescription": "Accept the service request successfully.", - }); - Mock::given(method("POST")) - .and(path("/mpesa/transactionstatus/v1/query")) - .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) - .expect(0) - .mount(&server) - .await; - if let Err(e) = client - .transaction_status("testapi496") - .result_url("https://testdomain.com/ok") - .timeout_url("https://testdomain.com/err") - .party_a("600111") - .send() - .await - { - let MpesaError::Message(msg) = e else { - panic!("Expected MpesaError::Message, but found {}", e); - }; - assert_eq!(msg, "transaction_id is required"); - } else { - panic!("Expected error") - } -} - -#[tokio::test] -async fn transaction_status_fails_if_party_a_is_not_provided() { - let (client, server) = get_mpesa_client!(expected_auth_requests = 0); - let sample_response_body = json!({ - "OriginatorConversationID": "29464-48063588-1", - "ConversationID": "AG_20230206_201056794190723278ff", - "ResponseDescription": "Accept the service request successfully.", - }); - Mock::given(method("POST")) - .and(path("/mpesa/transactionstatus/v1/query")) - .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) - .expect(0) - .mount(&server) - .await; - if let Err(e) = client - .transaction_status("testapi496") - .result_url("https://testdomain.com/ok") - .timeout_url("https://testdomain.com/err") - .transaction_id("OEI2AK4Q16") - .send() - .await - { - let MpesaError::Message(msg) = e else { - panic!("Expected MpesaError::Message, but found {}", e); - }; - assert_eq!(msg, "party_a is required"); - } else { - panic!("Expected error") - } -} - -#[tokio::test] -async fn transaction_status_fails_if_result_url_is_not_provided() { - let (client, server) = get_mpesa_client!(expected_auth_requests = 0); - let sample_response_body = json!({ - "OriginatorConversationID": "29464-48063588-1", - "ConversationID": "AG_20230206_201056794190723278ff", - "ResponseDescription": "Accept the service request successfully.", - }); - Mock::given(method("POST")) - .and(path("/mpesa/transactionstatus/v1/query")) - .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) - .expect(0) - .mount(&server) - .await; - if let Err(e) = client - .transaction_status("testapi496") - .timeout_url("https://testdomain.com/err") - .transaction_id("OEI2AK4Q16") - .party_a("600111") - .send() - .await - { - let MpesaError::Message(msg) = e else { - panic!("Expected MpesaError::Message, but found {}", e); - }; - assert_eq!(msg, "result_url is required"); - } else { - panic!("Expected error") - } -} - -#[tokio::test] -async fn transaction_status_fails_if_timeout_url_is_not_provided() { - let (client, server) = get_mpesa_client!(expected_auth_requests = 0); - let sample_response_body = json!({ - "OriginatorConversationID": "29464-48063588-1", - "ConversationID": "AG_20230206_201056794190723278ff", - "ResponseDescription": "Accept the service request successfully.", - }); - Mock::given(method("POST")) - .and(path("/mpesa/transactionstatus/v1/query")) - .respond_with(ResponseTemplate::new(200).set_body_json(sample_response_body)) - .expect(0) - .mount(&server) - .await; - if let Err(e) = client - .transaction_status("testapi496") - .result_url("https://testdomain.com/ok") - .transaction_id("OEI2AK4Q16") - .party_a("600111") - .send() - .await - { - let MpesaError::Message(msg) = e else { - panic!("Expected MpesaError::Message, but found {}", e); - }; - assert_eq!(msg, "timeout_url is required"); - } else { - panic!("Expected error") - } -}