#![ allow( clippy::std_instead_of_core ) ]
use api_ollama::{ OllamaClient, ChatRequest, ChatMessage, MessageRole, FailoverPolicy };
use std::time::Duration;
#[ tokio::test ]
async fn test_failover_client_creation()
{
let endpoints = vec![
"http://localhost:11434".to_string(),
"http://backup1:11434".to_string(),
"http://backup2:11434".to_string(),
];
let result = OllamaClient::new_with_failover( endpoints, Duration::from_secs( 30 ) );
assert!( result.is_ok() );
let client = result.unwrap();
assert_eq!( client.get_active_endpoint(), "http://localhost:11434" );
assert_eq!( client.get_endpoint_count(), 3 );
}
#[ tokio::test ]
async fn test_failover_configuration_validation()
{
let empty_endpoints : Vec< String > = vec![];
let result = OllamaClient::new_with_failover( empty_endpoints, Duration::from_secs( 30 ) );
assert!( result.is_err() );
let invalid_endpoints = vec![ "not-a-url".to_string() ];
let result = OllamaClient::new_with_failover( invalid_endpoints, Duration::from_secs( 30 ) );
assert!( result.is_err() );
}
#[ tokio::test ]
async fn test_automatic_failover_on_failure()
{
let endpoints = vec![
"http://invalid-endpoint:11434".to_string(), "http://localhost:11434".to_string(), ];
let mut client = OllamaClient::new_with_failover( endpoints, Duration::from_secs( 5 ) ).unwrap();
let request = ChatRequest
{
model : "test-model".to_string(),
messages : vec![ ChatMessage
{
role : MessageRole::User,
content : "Hello".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 initial_endpoint = client.get_active_endpoint();
assert_eq!( initial_endpoint, "http://invalid-endpoint:11434" );
let _ = client.chat( request ).await;
let failover_stats = client.get_failover_stats();
let _ = failover_stats.total_failovers; }
#[ tokio::test ]
async fn test_round_robin_failover_policy()
{
let endpoints = vec![
"http://endpoint1:11434".to_string(),
"http://endpoint2:11434".to_string(),
"http://endpoint3:11434".to_string(),
];
let mut client = OllamaClient::new_with_failover_policy(
endpoints,
Duration::from_secs( 30 ),
FailoverPolicy::RoundRobin
).unwrap();
assert_eq!( client.get_active_endpoint(), "http://endpoint1:11434" );
client.rotate_endpoint();
assert_eq!( client.get_active_endpoint(), "http://endpoint2:11434" );
client.rotate_endpoint();
assert_eq!( client.get_active_endpoint(), "http://endpoint3:11434" );
client.rotate_endpoint();
assert_eq!( client.get_active_endpoint(), "http://endpoint1:11434" ); }
#[ tokio::test ]
async fn test_priority_based_failover_policy()
{
let endpoints = vec![
"http://primary:11434".to_string(), "http://secondary:11434".to_string(), "http://tertiary:11434".to_string(), ];
let mut client = OllamaClient::new_with_failover_policy(
endpoints,
Duration::from_secs( 30 ),
FailoverPolicy::Priority
).unwrap();
assert_eq!( client.get_active_endpoint(), "http://primary:11434" );
client.mark_endpoint_unhealthy( "http://primary:11434" );
assert_eq!( client.get_active_endpoint(), "http://secondary:11434" );
client.mark_endpoint_unhealthy( "http://secondary:11434" );
assert_eq!( client.get_active_endpoint(), "http://tertiary:11434" );
client.mark_endpoint_healthy( "http://primary:11434" );
assert_eq!( client.get_active_endpoint(), "http://primary:11434" );
}
#[ tokio::test ]
async fn test_endpoint_health_tracking()
{
let endpoints = vec![
"http://localhost:11434".to_string(),
"http://backup:11434".to_string(),
];
let mut client = OllamaClient::new_with_failover( endpoints, Duration::from_secs( 30 ) ).unwrap();
assert!( client.is_endpoint_healthy( "http://localhost:11434" ) );
assert!( client.is_endpoint_healthy( "http://backup:11434" ) );
client.mark_endpoint_unhealthy( "http://localhost:11434" );
assert!( !client.is_endpoint_healthy( "http://localhost:11434" ) );
assert!( client.is_endpoint_healthy( "http://backup:11434" ) );
client.mark_endpoint_healthy( "http://localhost:11434" );
assert!( client.is_endpoint_healthy( "http://localhost:11434" ) );
}
#[ tokio::test ]
async fn test_failover_statistics()
{
let endpoints = vec![
"http://localhost:11434".to_string(),
"http://backup:11434".to_string(),
];
let mut client = OllamaClient::new_with_failover( endpoints, Duration::from_secs( 30 ) ).unwrap();
let initial_stats = client.get_failover_stats();
assert_eq!( initial_stats.total_failovers, 0 );
assert_eq!( initial_stats.total_requests, 0 );
client.mark_endpoint_unhealthy( "http://localhost:11434" );
client.mark_endpoint_healthy( "http://localhost:11434" );
let updated_stats = client.get_failover_stats();
let _ = updated_stats.total_requests; }
#[ tokio::test ]
async fn test_graceful_degradation_all_endpoints_fail()
{
let endpoints = vec![
"http://invalid1:11434".to_string(),
"http://invalid2:11434".to_string(),
];
let mut client = OllamaClient::new_with_failover( endpoints, Duration::from_secs( 5 ) ).unwrap();
let request = ChatRequest
{
model : "test-model".to_string(),
messages : vec![ ChatMessage
{
role : MessageRole::User,
content : "Hello".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 result = client.chat( request ).await;
assert!( result.is_err() );
let error_msg = format!( "{:?}", result.unwrap_err() );
assert!( error_msg.contains( "failover endpoints failed" ) || error_msg.contains( "endpoint" ) );
}
#[ tokio::test ]
async fn test_concurrent_requests_with_failover()
{
let endpoints = vec![
"http://localhost:11434".to_string(),
"http://backup:11434".to_string(),
];
let client = OllamaClient::new_with_failover( endpoints, Duration::from_secs( 30 ) ).unwrap();
let result1 = client.get_active_endpoint();
let result2 = client.get_active_endpoint();
assert!( !result1.is_empty() );
assert!( !result2.is_empty() );
}