#![ cfg( all( feature = "rate_limiting", feature = "integration_tests" ) ) ]
use api_ollama::
{
OllamaClient,
ChatRequest,
ChatMessage,
MessageRole,
RateLimitingConfig,
RateLimitingAlgorithm,
};
use core::time::Duration;
use std::time::Instant;
#[ tokio::test ]
async fn test_rate_limiting_blocks_http_requests()
{
let config = RateLimitingConfig::new()
.with_max_requests( 2 ) .with_burst_capacity( 2 ) .with_refill_rate( 0.1 ) .with_algorithm( RateLimitingAlgorithm::TokenBucket );
let mut client = OllamaClient::new(
"http://unreachable.test:99999".to_string(), Duration::from_millis( 100 ) ).with_rate_limiter( config );
let request = ChatRequest
{
model : "test-model".to_string(),
messages : vec!
[
ChatMessage
{
role : MessageRole::User,
content : "Test rate limiting HTTP integration".to_string(),
#[ cfg( feature = "vision_support" ) ]
images : None,
#[ cfg( feature = "tool_calling" ) ]
tool_calls : None,
}
],
stream : Some( false ),
options : None,
#[ cfg( feature = "tool_calling" ) ]
tools : None,
#[ cfg( feature = "tool_calling" ) ]
tool_messages : None,
};
assert!( client.has_rate_limiter() );
for i in 1..=2
{
let result = client.chat( request.clone() ).await;
assert!( result.is_err() );
let error_msg = result.unwrap_err().to_string();
assert!( !error_msg.contains( "Rate limit exceeded" ),
"Request {i} should not be rate limited : {error_msg}" );
}
let start_time = Instant::now();
let result = client.chat( request.clone() ).await;
let elapsed = start_time.elapsed();
assert!( result.is_err() );
let error_msg = result.unwrap_err().to_string();
assert!( error_msg.contains( "Rate limit exceeded" ) || error_msg.contains( "rejected" ),
"Request should be rate limited : {error_msg}" );
assert!( elapsed < Duration::from_millis( 50 ),
"Rate limited request took too long : {elapsed:?}" );
println!( "✓ Rate limiting blocks HTTP requests when limit exceeded in {elapsed:?}" );
}
#[ tokio::test ]
async fn test_rate_limiting_token_bucket_integration()
{
let config = RateLimitingConfig::new()
.with_burst_capacity( 2 ) .with_refill_rate( 0.5 ) .with_algorithm( RateLimitingAlgorithm::TokenBucket );
let mut client = OllamaClient::new(
"http://httpbin.org/status/200".to_string(), Duration::from_secs( 5 )
).with_rate_limiter( config );
let request = ChatRequest
{
model : "test".to_string(),
messages : vec!
[
ChatMessage
{
role : MessageRole::User,
content : "Token bucket test".to_string(),
#[ cfg( feature = "vision_support" ) ]
images : None,
#[ cfg( feature = "tool_calling" ) ]
tool_calls : None,
}
],
stream : Some( false ),
options : None,
#[ cfg( feature = "tool_calling" ) ]
tools : None,
#[ cfg( feature = "tool_calling" ) ]
tool_messages : None,
};
assert!( client.has_rate_limiter() );
let rate_config = client.rate_limiter_config().unwrap();
assert_eq!( *rate_config.algorithm(), RateLimitingAlgorithm::TokenBucket );
let mut successful_requests = 0;
let mut rate_limited_requests = 0;
for _i in 0..5
{
let result = client.chat( request.clone() ).await;
match result
{
Ok( _ ) =>
{
successful_requests += 1;
},
Err( error ) =>
{
if error.to_string().contains( "Rate limit exceeded" )
{
rate_limited_requests += 1;
}
else
{
successful_requests += 1;
}
}
}
tokio ::time::sleep( Duration::from_millis( 10 ) ).await; }
println!( "Rate limiting results : {successful_requests} successful, {rate_limited_requests} rate limited (configuration verified)" );
println!( "✓ Token bucket rate limiting : {successful_requests} successful, {rate_limited_requests} rate limited" );
}
#[ tokio::test ]
async fn test_rate_limiting_sliding_window_integration()
{
let config = RateLimitingConfig::new()
.with_max_requests( 1 ) .with_window_duration( 2000 ) .with_algorithm( RateLimitingAlgorithm::SlidingWindow );
let mut client = OllamaClient::new(
"http://httpbin.org/status/200".to_string(),
Duration::from_secs( 5 )
).with_rate_limiter( config );
let request = ChatRequest
{
model : "test".to_string(),
messages : vec!
[
ChatMessage
{
role : MessageRole::User,
content : "Sliding window test".to_string(),
#[ cfg( feature = "vision_support" ) ]
images : None,
#[ cfg( feature = "tool_calling" ) ]
tool_calls : None,
}
],
stream : Some( false ),
options : None,
#[ cfg( feature = "tool_calling" ) ]
tools : None,
#[ cfg( feature = "tool_calling" ) ]
tool_messages : None,
};
let rate_config = client.rate_limiter_config().unwrap();
assert_eq!( *rate_config.algorithm(), RateLimitingAlgorithm::SlidingWindow );
assert_eq!( rate_config.max_requests(), 1 );
let mut successful_requests = 0;
let mut rate_limited_requests = 0;
for _i in 0..4
{
let result = client.chat( request.clone() ).await;
match result
{
Ok( _ ) =>
{
successful_requests += 1;
},
Err( error ) =>
{
if error.to_string().contains( "Rate limit exceeded" )
{
rate_limited_requests += 1;
}
else
{
successful_requests += 1;
}
}
}
}
println!( "Rate limiting results : {successful_requests} successful, {rate_limited_requests} rate limited (configuration verified)" );
println!( "✓ Sliding window rate limiting : {successful_requests} successful, {rate_limited_requests} rate limited" );
}
#[ tokio::test ]
async fn test_rate_limiting_multiple_http_methods()
{
let config = RateLimitingConfig::new()
.with_burst_capacity( 2 )
.with_refill_rate( 0.1 ) .with_algorithm( RateLimitingAlgorithm::TokenBucket );
let mut client = OllamaClient::new(
"http://httpbin.org/status/200".to_string(),
Duration::from_secs( 5 )
).with_rate_limiter( config );
let chat_request = ChatRequest
{
model : "test".to_string(),
messages : vec!
[
ChatMessage
{
role : MessageRole::User,
content : "Multi-method test".to_string(),
#[ cfg( feature = "vision_support" ) ]
images : None,
#[ cfg( feature = "tool_calling" ) ]
tool_calls : None,
}
],
stream : Some( false ),
options : None,
#[ cfg( feature = "tool_calling" ) ]
tools : None,
#[ cfg( feature = "tool_calling" ) ]
tool_messages : None,
};
let _result1 = client.chat( chat_request ).await;
let _result2 = client.list_models().await;
let start_time = Instant::now();
let result3 = client.model_info( "test-model".to_string() ).await;
let elapsed = start_time.elapsed();
if result3.is_err()
{
let error_msg = result3.unwrap_err().to_string();
if error_msg.contains( "Rate limit exceeded" )
{
assert!( elapsed < Duration::from_millis( 100 ) );
println!( "✓ Rate limiting works across different HTTP methods" );
}
else
{
println!( "Note : Rate limiting may not have triggered due to network conditions" );
}
}
}
#[ tokio::test ]
async fn test_rate_limiter_reset_functionality()
{
let config = RateLimitingConfig::new()
.with_burst_capacity( 1 )
.with_refill_rate( 0.1 ) .with_algorithm( RateLimitingAlgorithm::TokenBucket );
let mut client = OllamaClient::new(
"http://unreachable.test:99999".to_string(),
Duration::from_millis( 100 )
).with_rate_limiter( config );
let request = ChatRequest
{
model : "test".to_string(),
messages : vec!
[
ChatMessage
{
role : MessageRole::User,
content : "Reset test".to_string(),
#[ cfg( feature = "vision_support" ) ]
images : None,
#[ cfg( feature = "tool_calling" ) ]
tool_calls : None,
}
],
stream : Some( false ),
options : None,
#[ cfg( feature = "tool_calling" ) ]
tools : None,
#[ cfg( feature = "tool_calling" ) ]
tool_messages : None,
};
let _result1 = client.chat( request.clone() ).await;
let result2 = client.chat( request.clone() ).await;
assert!( result2.is_err() );
if result2.unwrap_err().to_string().contains( "Rate limit exceeded" )
{
client.reset_rate_limiter();
let result3 = client.chat( request.clone() ).await;
assert!( result3.is_err() );
let error_msg = result3.unwrap_err().to_string();
assert!( !error_msg.contains( "Rate limit exceeded" ),
"After reset, should not be rate limited : {error_msg}" );
println!( "✓ Rate limiter reset functionality works" );
}
else
{
println!( "Note : Reset test skipped due to network conditions" );
}
}
#[ tokio::test ]
async fn test_rate_limiting_config_validation_integration()
{
let valid_config = RateLimitingConfig::new()
.with_max_requests( 5 )
.with_burst_capacity( 3 )
.with_refill_rate( 1.0 );
let client = OllamaClient::new(
"http://httpbin.org/status/200".to_string(),
Duration::from_secs( 5 )
).with_rate_limiter( valid_config );
assert!( client.has_rate_limiter() );
println!( "✓ Valid rate limiting configuration accepted" );
}
#[ tokio::test ]
async fn test_rate_limiting_zero_overhead_when_disabled()
{
let client = OllamaClient::new(
"http://localhost:11434".to_string(),
Duration::from_secs( 5 )
);
assert!( !client.has_rate_limiter() );
assert!( client.rate_limiter_config().is_none() );
println!( "✓ Rate limiting integration has zero overhead when not configured" );
}
#[ tokio::test ]
async fn test_rate_limiting_metrics_integration()
{
let config = RateLimitingConfig::new()
.with_burst_capacity( 2 )
.with_refill_rate( 1.0 )
.with_algorithm( RateLimitingAlgorithm::TokenBucket );
let client = OllamaClient::new(
"http://unreachable.test:99999".to_string(),
Duration::from_millis( 100 )
).with_rate_limiter( config.clone() );
let retrieved_config = client.rate_limiter_config().unwrap();
assert_eq!( retrieved_config.burst_capacity(), 2 );
assert!( (retrieved_config.refill_rate() - 1.0).abs() < f64::EPSILON );
assert_eq!( *retrieved_config.algorithm(), RateLimitingAlgorithm::TokenBucket );
println!( "✓ Rate limiting configuration can be inspected" );
client.reset_rate_limiter();
println!( "✓ Rate limiter state can be reset" );
}