use crate::common;
use ig_client::utils::logger::setup_logger;
use ig_client::{
application::services::MarketService, application::services::market_service::MarketServiceImpl,
session::interface::IgSession, utils::rate_limiter::RateLimitType,
};
use std::time::Instant;
use tracing::{info, warn};
#[tokio::test]
#[ignore]
async fn test_rate_limiter_integration() -> Result<(), Box<dyn std::error::Error>> {
setup_logger();
let config = common::create_test_config();
let client = common::create_test_client(config.clone());
let _market_service = MarketServiceImpl::new(config.clone(), client);
let normal_session = match common::login_with_account_switch_async().await {
Ok(session) => session,
Err(e) => {
info!("Skipping test due to login failure: {}", e);
return Ok(());
}
};
let rate_limited_session = IgSession::with_rate_limiter(
normal_session.cst.clone(),
normal_session.token.clone(),
normal_session.account_id.clone(),
RateLimitType::NonTradingAccount,
);
let num_requests = 5;
let search_term = "Germany 40";
let market_service_normal =
MarketServiceImpl::new(config.clone(), common::create_test_client(config.clone()));
info!("Testing with normal session (no rate limiter):");
let normal_durations = make_multiple_requests(
&market_service_normal,
&normal_session,
search_term,
num_requests,
)
.await;
let market_service_limited =
MarketServiceImpl::new(config.clone(), common::create_test_client(config.clone()));
info!("Testing with rate-limited session:");
let limited_durations = make_multiple_requests(
&market_service_limited,
&rate_limited_session,
search_term,
num_requests,
)
.await;
let normal_avg = normal_durations.iter().sum::<u128>() as f64 / normal_durations.len() as f64;
let limited_avg =
limited_durations.iter().sum::<u128>() as f64 / limited_durations.len() as f64;
info!("Normal session average request time: {:.2} ms", normal_avg);
info!(
"Rate-limited session average request time: {:.2} ms",
limited_avg
);
let normal_intervals = calculate_intervals(&normal_durations);
let limited_intervals = calculate_intervals(&limited_durations);
info!(
"Normal session intervals between requests (ms): {:?}",
normal_intervals
);
info!(
"Rate-limited session intervals between requests (ms): {:?}",
limited_intervals
);
let normal_avg_interval =
normal_intervals.iter().sum::<u128>() as f64 / normal_intervals.len() as f64;
let limited_avg_interval =
limited_intervals.iter().sum::<u128>() as f64 / limited_intervals.len() as f64;
info!(
"Normal session average interval: {:.2} ms",
normal_avg_interval
);
info!(
"Rate-limited session average interval: {:.2} ms",
limited_avg_interval
);
info!(
"Interval comparison: rate-limited ({:.2} ms) vs normal ({:.2} ms)",
limited_avg_interval, normal_avg_interval
);
if limited_avg_interval > normal_avg_interval {
info!("✓ Rate-limited session has larger intervals as expected");
} else {
info!("⚠ Rate-limited session has smaller intervals than normal session");
info!("This might be due to API limits being reached or external throttling");
}
let min_limited_interval = *limited_intervals.iter().min().unwrap_or(&0);
let request_limit = RateLimitType::NonTradingAccount.request_limit() as u128;
let expected_min_interval = 60000 / request_limit;
info!(
"Minimum interval in rate-limited requests: {} ms",
min_limited_interval
);
info!("Expected minimum interval: {} ms", expected_min_interval);
if min_limited_interval >= expected_min_interval * 8 / 10 {
info!("✓ Minimum interval is close to the expected minimum interval");
} else {
info!("⚠ Minimum interval is less than expected");
info!("This might be due to API limits being reached or external throttling");
}
Ok(())
}
async fn make_multiple_requests<
'a,
T: ig_client::transport::http_client::IgHttpClient + 'static,
>(
market_service: &'a MarketServiceImpl<T>,
session: &'a IgSession,
search_term: &'a str,
num_requests: usize,
) -> Vec<u128> {
let mut durations = Vec::with_capacity(num_requests);
let mut start_times = Vec::with_capacity(num_requests);
for i in 0..num_requests {
info!("Making request {} of {}", i + 1, num_requests);
let start = Instant::now();
start_times.push(start);
match market_service.search_markets(session, search_term).await {
Ok(result) => {
let duration = start.elapsed().as_millis();
durations.push(duration);
info!(
"Request {} completed in {} ms, found {} markets",
i + 1,
duration,
result.markets.len()
);
}
Err(e) => {
warn!("Request {} failed: {:?}", i + 1, e);
durations.push(start.elapsed().as_millis());
}
}
}
durations
}
fn calculate_intervals(durations: &[u128]) -> Vec<u128> {
let mut intervals = Vec::with_capacity(durations.len() - 1);
for &duration in durations.iter().skip(1) {
intervals.push(duration);
}
intervals
}
#[tokio::test]
#[ignore]
async fn test_rate_limiter_concurrent_requests() -> Result<(), Box<dyn std::error::Error>> {
setup_logger();
let config = common::create_test_config();
let client = common::create_test_client(config.clone());
let _market_service = MarketServiceImpl::new(config.clone(), client);
let normal_session = match common::login_with_account_switch_async().await {
Ok(session) => session,
Err(e) => {
info!("Skipping test due to login failure: {}", e);
return Ok(());
}
};
let session = IgSession::with_rate_limiter(
normal_session.cst.clone(),
normal_session.token.clone(),
normal_session.account_id.clone(),
RateLimitType::NonTradingAccount,
);
let num_concurrent = 3;
let search_terms = ["Germany 40", "US 500", "UK 100"];
info!(
"Making {} concurrent requests with rate limiter",
num_concurrent
);
let start = Instant::now();
let mut handles = Vec::with_capacity(num_concurrent);
for i in 0..num_concurrent {
let market_service_clone =
MarketServiceImpl::new(config.clone(), common::create_test_client(config.clone()));
let session_clone = IgSession::with_rate_limiter(
session.cst.clone(),
session.token.clone(),
session.account_id.clone(),
RateLimitType::NonTradingAccount,
);
let search_term = search_terms[i % search_terms.len()].to_string();
let handle = tokio::spawn(async move {
info!("Concurrent request {} starting", i + 1);
let request_start = Instant::now();
let result = market_service_clone
.search_markets(&session_clone, &search_term)
.await;
let duration = request_start.elapsed().as_millis();
match result {
Ok(markets) => {
info!(
"Concurrent request {} completed in {} ms, found {} markets for '{}'",
i + 1,
duration,
markets.markets.len(),
search_term
);
}
Err(e) => {
warn!(
"Concurrent request {} failed after {} ms: {:?}",
i + 1,
duration,
e
);
}
}
duration
});
handles.push(handle);
}
let mut durations = Vec::with_capacity(num_concurrent);
for handle in handles {
match handle.await {
Ok(duration) => durations.push(duration),
Err(e) => warn!("Task join error: {:?}", e),
}
}
let total_duration = start.elapsed().as_millis();
info!("All concurrent requests completed in {} ms", total_duration);
if !durations.is_empty() {
let avg_duration = durations.iter().sum::<u128>() as f64 / durations.len() as f64;
let min_duration = durations.iter().min().unwrap_or(&0);
let max_duration = durations.iter().max().unwrap_or(&0);
info!("Average request duration: {:.2} ms", avg_duration);
info!("Min request duration: {} ms", min_duration);
info!("Max request duration: {} ms", max_duration);
let request_limit = RateLimitType::NonTradingAccount.request_limit() as u128;
let min_interval = 60000 / request_limit;
let expected_min_time = min_interval * (num_concurrent as u128 - 1);
info!(
"Expected minimum time for all requests: {} ms",
expected_min_time
);
if total_duration >= expected_min_time * 8 / 10 {
info!(
"✓ Total duration ({} ms) is close to the expected minimum time ({} ms)",
total_duration, expected_min_time
);
} else {
info!(
"⚠ Total duration ({} ms) is less than expected minimum time ({} ms)",
total_duration, expected_min_time
);
info!("This might be due to API limits being reached or external throttling");
}
}
Ok(())
}