#![ cfg( feature = "general_diagnostics" ) ]
#![ allow( clippy::std_instead_of_core ) ]
use api_ollama::{ OllamaClient, DiagnosticsConfig, DiagnosticsCollector };
use api_ollama::{ ChatRequest, ChatMessage, MessageRole, GenerateRequest };
use core::time::Duration;
use std::sync::Arc;
use tokio::time::sleep;
#[ tokio::test ]
async fn test_diagnostics_config_creation_and_configuration()
{
let config = DiagnosticsConfig::default();
assert_eq!(config.max_tracked_requests(), 1000);
assert_eq!(config.metrics_retention_period(), Duration::from_secs(3600)); assert!(config.collect_performance_metrics());
assert!(config.collect_error_analysis());
assert!(config.generate_curl_commands());
let custom_config = DiagnosticsConfig::new()
.with_max_tracked_requests(5000)
.with_metrics_retention_period(Duration::from_secs(7200))
.with_performance_metrics(false)
.with_error_analysis(true)
.with_curl_generation(false);
assert_eq!(custom_config.max_tracked_requests(), 5000);
assert_eq!(custom_config.metrics_retention_period(), Duration::from_secs(7200));
assert!(!custom_config.collect_performance_metrics());
assert!(custom_config.collect_error_analysis());
assert!(!custom_config.generate_curl_commands());
}
#[ tokio::test ]
async fn test_diagnostics_collector_creation_and_initial_state()
{
let config = DiagnosticsConfig::default();
let collector = DiagnosticsCollector::new(config);
assert_eq!(collector.tracked_requests_count(), 0);
assert_eq!(collector.total_requests(), 0);
assert_eq!(collector.successful_requests(), 0);
assert_eq!(collector.failed_requests(), 0);
assert!(collector.is_empty());
let report = collector.generate_report();
assert_eq!(report.total_requests(), 0);
assert_eq!(report.average_response_time(), Duration::ZERO);
assert!((report.error_rate() - 0.0).abs() < f64::EPSILON);
}
#[ tokio::test ]
async fn test_request_lifecycle_tracking()
{
let config = DiagnosticsConfig::new().with_max_tracked_requests(10);
let collector = DiagnosticsCollector::new(config);
let request_id = "test-request-001";
let request = ChatRequest {
model : "llama2".to_string(),
messages : vec![ChatMessage {
role : MessageRole::User,
content : "Test request".to_string(),
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,
};
collector.track_request_start(request_id, &request);
assert_eq!(collector.tracked_requests_count(), 1);
assert_eq!(collector.total_requests(), 1);
sleep(Duration::from_millis(100)).await;
let response_body = "Test response";
collector.track_request_success(request_id, response_body.len());
let metrics = collector.get_request_metrics(request_id).unwrap();
assert_eq!(metrics.request_id(), request_id);
assert!(metrics.response_time() >= Duration::from_millis(100));
assert_eq!(metrics.response_size(), response_body.len());
assert!(metrics.is_successful());
assert_eq!(collector.successful_requests(), 1);
assert_eq!(collector.failed_requests(), 0);
}
#[ tokio::test ]
async fn test_error_tracking_and_analysis()
{
let config = DiagnosticsConfig::default();
let collector = DiagnosticsCollector::new(config);
let request_id = "test-request-error";
let request = GenerateRequest {
model : "invalid-model".to_string(),
prompt : "Test prompt".to_string(),
stream : Some(false),
options : None,
};
collector.track_request_start(request_id, &request);
sleep(Duration::from_millis(50)).await;
let error_message = "Model not found : invalid-model";
let error_code = 404;
collector.track_request_failure(request_id, error_code, error_message);
let metrics = collector.get_request_metrics(request_id).unwrap();
assert!(!metrics.is_successful());
assert_eq!(metrics.error_code(), Some(error_code));
assert!(metrics.error_message().contains("Model not found"));
assert_eq!(collector.successful_requests(), 0);
assert_eq!(collector.failed_requests(), 1);
let error_analysis = collector.analyze_errors();
assert_eq!(error_analysis.total_errors(), 1);
assert!(error_analysis.most_common_error_code() == Some(404));
assert!(error_analysis.error_patterns().contains("Model not found"));
assert!((error_analysis.error_rate() - 1.0).abs() < f64::EPSILON); }
#[ tokio::test ]
async fn test_performance_metrics_collection()
{
let config = DiagnosticsConfig::new().with_performance_metrics(true);
let collector = DiagnosticsCollector::new(config);
for i in 0..5
{
let request_id = format!( "perf-test-{i}" );
let request = ChatRequest {
model : "llama2".to_string(),
messages : vec![ChatMessage {
role : MessageRole::User,
content : format!( "Performance test {i}" ),
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,
};
collector.track_request_start(&request_id, &request);
sleep(Duration::from_millis(50 + i * 20)).await;
collector.track_request_success(&request_id, usize::try_from(100 + i * 50).unwrap_or(100));
}
let performance_report = collector.generate_performance_report();
assert_eq!(performance_report.total_requests(), 5);
assert!(performance_report.average_response_time() > Duration::from_millis(50));
assert!(performance_report.min_response_time() >= Duration::from_millis(50));
assert!(performance_report.max_response_time() >= Duration::from_millis(130));
assert!(performance_report.p95_response_time() > Duration::ZERO);
assert!(performance_report.p99_response_time() > Duration::ZERO);
assert_eq!(performance_report.total_bytes_transferred(), 100 + 150 + 200 + 250 + 300);
}
#[ tokio::test ]
async fn test_curl_command_generation()
{
let config = DiagnosticsConfig::new().with_curl_generation(true);
let collector = DiagnosticsCollector::new(config);
let request_id = "curl-test-001";
let request = ChatRequest {
model : "llama2".to_string(),
messages : vec![ChatMessage {
role : MessageRole::User,
content : "Generate cURL command".to_string(),
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 base_url = "http://localhost:11434";
collector.track_request_start_with_curl(request_id, &request, base_url);
let curl_command = collector.get_curl_command(request_id).unwrap();
assert!(curl_command.contains("curl"));
assert!(curl_command.contains("-X POST"));
assert!(curl_command.contains("http://localhost:11434/api/chat"));
assert!(curl_command.contains("Content-Type : application/json"));
assert!(curl_command.contains("llama2"));
assert!(curl_command.contains("Generate cURL command"));
assert!(collector.validate_curl_command(&curl_command));
}
#[ tokio::test ]
async fn test_metrics_aggregation_and_reporting()
{
let config = DiagnosticsConfig::default();
let collector = DiagnosticsCollector::new(config);
for i in 0..10
{
let request_id = format!( "agg-test-{i}" );
let request = GenerateRequest {
model : if i % 3 == 0 { "invalid".to_string() } else { "llama2".to_string() },
prompt : format!( "Aggregation test {i}" ),
stream : Some(false),
options : None,
};
collector.track_request_start(&request_id, &request);
sleep(Duration::from_millis(10 + i * 5)).await;
if i % 3 == 0
{
collector.track_request_failure(&request_id, 404, "Model not found");
}
else
{
collector.track_request_success(&request_id, usize::try_from(100 + i * 10).unwrap_or(100));
}
}
let comprehensive_report = collector.generate_comprehensive_report();
assert_eq!(comprehensive_report.total_requests(), 10);
assert_eq!(comprehensive_report.successful_requests(), 6); assert_eq!(comprehensive_report.failed_requests(), 4); assert!(comprehensive_report.error_rate() > 0.0);
assert!(comprehensive_report.success_rate() < 1.0);
assert!(comprehensive_report.average_response_time() > Duration::ZERO);
assert!(comprehensive_report.total_bytes_transferred() > 0);
assert!(!comprehensive_report.top_errors().is_empty());
assert!(!comprehensive_report.performance_trends().is_empty());
}
#[ tokio::test ]
async fn test_integration_with_ollama_client()
{
let diagnostics_config = DiagnosticsConfig::new()
.with_max_tracked_requests(100)
.with_performance_metrics(true)
.with_curl_generation(true);
let mut client = OllamaClient::new( "http://test.example:11434".to_string(), OllamaClient::recommended_timeout_fast() )
.with_diagnostics(diagnostics_config);
assert!(client.has_diagnostics());
let initial_report = client.diagnostics_report();
assert_eq!(initial_report.total_requests(), 0);
let request = ChatRequest {
model : "test-model".to_string(),
messages : vec![ChatMessage {
role : MessageRole::User,
content : "Diagnostics integration test".to_string(),
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_with_diagnostics(request.clone()).await;
assert!(result.is_err());
let final_report = client.diagnostics_report();
assert_eq!(final_report.total_requests(), 1);
assert_eq!(final_report.failed_requests(), 1);
assert!((final_report.error_rate() - 1.0).abs() < f64::EPSILON);
}
#[ tokio::test ]
async fn test_diagnostics_memory_management()
{
let config = DiagnosticsConfig::new()
.with_max_tracked_requests(5)
.with_metrics_retention_period(Duration::from_millis(100));
let collector = DiagnosticsCollector::new(config);
for i in 0..10
{
let request_id = format!( "memory-test-{i}" );
let request = GenerateRequest {
model : "test".to_string(),
prompt : format!( "Memory test {i}" ),
stream : Some(false),
options : None,
};
collector.track_request_start(&request_id, &request);
collector.track_request_success(&request_id, 100);
}
assert!(collector.tracked_requests_count() <= 5);
sleep(Duration::from_millis(150)).await;
collector.cleanup_expired_metrics();
assert!(collector.tracked_requests_count() < 5);
}
#[ tokio::test ]
async fn test_diagnostics_concurrent_access()
{
let config = DiagnosticsConfig::new().with_max_tracked_requests(100);
let collector = Arc::new(DiagnosticsCollector::new(config));
let mut handles = vec![];
for thread_id in 0..5
{
let collector = collector.clone();
let handle = tokio::spawn(async move {
for i in 0..10
{
let request_id = format!( "concurrent-{thread_id}-{i}" );
let request = ChatRequest {
model : "llama2".to_string(),
messages : vec![ChatMessage {
role : MessageRole::User,
content : format!( "Concurrent test {thread_id} {i}" ),
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,
};
collector.track_request_start(&request_id, &request);
sleep(Duration::from_millis(1)).await;
collector.track_request_success(&request_id, 100);
}
});
handles.push(handle);
}
for handle in handles
{
handle.await.unwrap();
}
assert_eq!(collector.total_requests(), 50);
assert!(collector.tracked_requests_count() > 0);
assert!(collector.tracked_requests_count() <= 100);
}
#[ tokio::test ]
async fn test_diagnostics_debug_and_display()
{
let config = DiagnosticsConfig::default();
let collector = DiagnosticsCollector::new(config);
let debug_output = format!( "{collector:?}" );
assert!(debug_output.contains("DiagnosticsCollector"));
assert!(debug_output.contains("total_requests: 0"));
let report = collector.generate_report();
let display_output = format!( "{report}" );
assert!(display_output.contains("Diagnostics Report"));
assert!(display_output.contains("Total Requests : 0"));
}