Skip to content

Commit

Permalink
currency: fix test for http
Browse files Browse the repository at this point in the history
Signed-off-by: Victor Login <batazor111@gmail.com>
  • Loading branch information
batazor committed Sep 28, 2024
1 parent 088ec0b commit 52711ad
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 106 deletions.
1 change: 1 addition & 0 deletions boundaries/billing/currency/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
redis = { version = "0.27.2", features = ["aio", "tokio-comp"] }
deadpool-redis = { version = "0.18.0", features = ["rt_tokio_1"] }
dotenvy = "0.15.7"
thiserror = "1.0.64"
187 changes: 81 additions & 106 deletions boundaries/billing/currency/src/infrastructure/http/test.rs
Original file line number Diff line number Diff line change
@@ -1,171 +1,152 @@
#[cfg(test)]
mod tests {
use super::*;
use crate::usecases::currency_conversion::converter::traits::ICurrencyConversionUseCase;
use crate::usecases::exchange_rate::fetcher::traits::IRateFetcherUseCase;
use crate::usecases::exchange_rate::fetcher::RateFetcherUseCase;
use crate::repository::exchange_rate::in_memory_repository::InMemoryExchangeRateRepository;
use crate::cache::CacheService;
use crate::domain::exchange_rate::entities::{Currency, ExchangeRate};
use rust_decimal_macros::dec;
use serde_json::json;
use crate::infrastructure::http::routes::api;
use warp::http::StatusCode;
use warp::test::request;
use serde_json::json;
use rust_decimal::Decimal;
use std::sync::Arc;
use async_trait::async_trait;
use std::error::Error;
use crate::infrastructure::http::routes::api;
use crate::usecases::currency_conversion::converter::converter::CurrencyConversionUseCase;
use warp::Filter;
use crate::domain::exchange_rate::entities::{Currency, ExchangeRate};

/// Mock implementation of `IRateFetcherUseCase`
struct MockRateFetcherUseCase;

#[async_trait]
#[async_trait::async_trait]
impl IRateFetcherUseCase for MockRateFetcherUseCase {
/// Returns a predefined exchange rate for USD to EUR, else None
async fn fetch_rate(&self, from: &str, to: &str) -> Option<ExchangeRate> {
if from == "USD" && to == "EUR" {
if from.eq_ignore_ascii_case("USD") && to.eq_ignore_ascii_case("EUR") {
Some(ExchangeRate::new(
Currency { code: "USD".to_string(), symbol: "$".to_string() },
Currency { code: "EUR".to_string(), symbol: "€".to_string() },
dec!(0.85),
Decimal::new(85, 2), // 0.85
))
} else {
None
}
}

async fn save_rate(&self, _rate: ExchangeRate) -> Result<(), Box<dyn Error + Send + Sync>> {
/// Mock save_rate does nothing
async fn save_rate(&self, _rate: ExchangeRate) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Ok(())
}
}

/// Mock implementation of `ICurrencyConversionUseCase`
struct MockCurrencyConversionUseCase;

#[async_trait]
#[async_trait::async_trait]
impl ICurrencyConversionUseCase for MockCurrencyConversionUseCase {
/// Returns predefined historical rates for USD to EUR, else None
async fn get_historical_rates(
&self,
_base_currency: &str,
_target_currency: &str,
_start_date: &str,
_end_date: &str,
base_currency: &str,
target_currency: &str,
start_date: &str,
end_date: &str,
) -> Option<Vec<ExchangeRate>> {
Some(vec![
ExchangeRate::new(
Currency { code: "USD".to_string(), symbol: "$".to_string() },
Currency { code: "EUR".to_string(), symbol: "€".to_string() },
dec!(0.84),
),
ExchangeRate::new(
Currency { code: "USD".to_string(), symbol: "$".to_string() },
Currency { code: "EUR".to_string(), symbol: "€".to_string() },
dec!(0.85),
),
])
if base_currency.eq_ignore_ascii_case("USD") && target_currency.eq_ignore_ascii_case("EUR") {
Some(vec![
ExchangeRate::new(
Currency { code: "USD".to_string(), symbol: "$".to_string() },
Currency { code: "EUR".to_string(), symbol: "€".to_string() },
Decimal::new(84, 2), // 0.84
),
ExchangeRate::new(
Currency { code: "USD".to_string(), symbol: "$".to_string() },
Currency { code: "EUR".to_string(), symbol: "€".to_string() },
Decimal::new(85, 2), // 0.85
),
])
} else {
None
}
}
}

/// Helper function to inject `IRateFetcherUseCase` mock
fn with_rate_fetcher(
rate_fetcher: Arc<dyn IRateFetcherUseCase>,
) -> impl warp::Filter<Extract = (Arc<dyn IRateFetcherUseCase>,), Error = std::convert::Infallible> + Clone {
warp::any().map(move || rate_fetcher.clone())
}

/// Helper function to inject `ICurrencyConversionUseCase` mock
fn with_conversion_service(
conversion_service: Arc<dyn ICurrencyConversionUseCase>,
) -> impl warp::Filter<Extract = (Arc<dyn ICurrencyConversionUseCase>,), Error = std::convert::Infallible> + Clone
{
warp::any().map(move || conversion_service.clone())
}

#[tokio::test]
async fn test_get_current_exchange_rate_success() {
// Setup
let rate_fetcher = Arc::new(MockRateFetcherUseCase);
let conversion_service = Arc::new(MockCurrencyConversionUseCase);
// Setup mocks
let mock_rate_fetcher = Arc::new(MockRateFetcherUseCase) as Arc<dyn IRateFetcherUseCase>;
let mock_conversion_service = Arc::new(MockCurrencyConversionUseCase) as Arc<dyn ICurrencyConversionUseCase>;

let api_filter = api(rate_fetcher.clone(), conversion_service.clone());
// Build API filter
let api_filter = api(mock_rate_fetcher.clone(), mock_conversion_service.clone());

// Execute
// Execute request
let resp = request()
.method("GET")
.path("/rates/current?base_currency=USD&target_currency=EUR")
.reply(&api_filter)
.await;

// Verify
// Verify response
assert_eq!(resp.status(), StatusCode::OK);
let expected = json!({
"base_currency": "USD",
"target_currency": "EUR",
"exchange_rate": "0.85",
"timestamp": "2024-09-12T12:00:00Z" // Ensure your handler provides this or adjust accordingly
"timestamp": "2024-09-12T12:00:00Z" // Adjust as needed
});
let body: serde_json::Value = serde_json::from_slice(resp.body()).unwrap();
assert_eq!(body, expected);
}

#[tokio::test]
async fn test_get_current_exchange_rate_not_found() {
// Setup
let rate_fetcher = Arc::new(MockRateFetcherUseCase);
let conversion_service = Arc::new(MockCurrencyConversionUseCase);
// Setup mocks
let mock_rate_fetcher = Arc::new(MockRateFetcherUseCase) as Arc<dyn IRateFetcherUseCase>;
let mock_conversion_service = Arc::new(MockCurrencyConversionUseCase) as Arc<dyn ICurrencyConversionUseCase>;

let api_filter = api(rate_fetcher.clone(), conversion_service.clone());
// Build API filter
let api_filter = api(mock_rate_fetcher.clone(), mock_conversion_service.clone());

// Execute
// Execute request with non-existent currency pair
let resp = request()
.method("GET")
.path("/rates/current?base_currency=GBP&target_currency=JPY")
.reply(&api_filter)
.await;

// Verify
// Verify response
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
}

#[tokio::test]
async fn test_get_historical_exchange_rate_success() {
// Setup
let repository = Arc::new(InMemoryExchangeRateRepository::new());
let cache = Arc::new(CacheService::new());
let rate_fetcher = Arc::new(RateFetcherUseCase::new(
repository.clone(),
cache.clone(),
vec![], // Add mock providers if necessary
3, // max_retries
));
let conversion_service = Arc::new(CurrencyConversionUseCase::new(rate_fetcher.clone()));

// Insert historical rates into the repository
let historical_rates = vec![
ExchangeRate::new(
Currency {
code: "USD".to_string(),
symbol: "$".to_string(),
},
Currency {
code: "EUR".to_string(),
symbol: "€".to_string(),
},
dec!(0.84),
),
ExchangeRate::new(
Currency {
code: "USD".to_string(),
symbol: "$".to_string(),
},
Currency {
code: "EUR".to_string(),
symbol: "€".to_string(),
},
dec!(0.85),
),
];

// Insert into the in-memory repository
for rate in historical_rates.iter() {
rate_fetcher.save_rate(rate.clone()).await.unwrap();
}
// Setup mocks
let mock_rate_fetcher = Arc::new(MockRateFetcherUseCase) as Arc<dyn IRateFetcherUseCase>;
let mock_conversion_service = Arc::new(MockCurrencyConversionUseCase) as Arc<dyn ICurrencyConversionUseCase>;

let api_filter = api(rate_fetcher.clone(), conversion_service.clone());
// Build API filter
let api_filter = api(mock_rate_fetcher.clone(), mock_conversion_service.clone());

// Execute
// Execute request
let resp = request()
.method("GET")
.path("/rates/historical?base_currency=USD&target_currency=EUR&start_date=2024-01-01&end_date=2024-01-02")
.reply(&api_filter)
.await;

// Verify
// Verify response
assert_eq!(resp.status(), StatusCode::OK);
let expected = json!([
{
Expand All @@ -182,28 +163,22 @@ mod tests {
}

#[tokio::test]
async fn test_get_historical_exchange_rate_not_found() {
// Setup
let repository = Arc::new(InMemoryExchangeRateRepository::new());
let cache = Arc::new(CacheService::new());
let rate_fetcher = Arc::new(RateFetcherUseCase::new(
repository.clone(),
cache.clone(),
vec![], // Add mock providers if necessary
3, // max_retries
));
let conversion_service = Arc::new(CurrencyConversionUseCase::new(rate_fetcher.clone()));

let api_filter = api(rate_fetcher.clone(), conversion_service.clone());

// Execute
async fn test_invalid_currency_code() {
// Setup mocks
let mock_rate_fetcher = Arc::new(MockRateFetcherUseCase) as Arc<dyn IRateFetcherUseCase>;
let mock_conversion_service = Arc::new(MockCurrencyConversionUseCase) as Arc<dyn ICurrencyConversionUseCase>;

// Build API filter
let api_filter = api(mock_rate_fetcher.clone(), mock_conversion_service.clone());

// Execute request with invalid currency codes
let resp = request()
.method("GET")
.path("/rates/historical?base_currency=USD&target_currency=EUR&start_date=2024-01-01&end_date=2024-01-02")
.path("/rates/current?base_currency=INVALID&target_currency=EUR")
.reply(&api_filter)
.await;

// Verify
// Verify response
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
}
}

0 comments on commit 52711ad

Please sign in to comment.