use std::time::{Duration, Instant};
use reqwest::Client;
use serde_json::{Value, json};
use tokio::time::timeout;
mod mock_framework;
use mock_framework::{MockLlmServer, MockTestUtils};
#[derive(Debug, Clone)]
pub struct NetworkErrorTestConfig {
pub request_timeout: Duration,
pub retry_attempts: usize,
pub retry_delay: Duration,
pub test_concurrency: bool,
pub concurrent_requests: usize,
}
impl Default for NetworkErrorTestConfig {
fn default() -> Self {
Self {
request_timeout: Duration::from_secs(5),
retry_attempts: 3,
retry_delay: Duration::from_millis(100),
test_concurrency: false,
concurrent_requests: 5,
}
}
}
#[derive(Debug)]
pub struct NetworkErrorTestResults {
pub test_name: String,
pub passed: bool,
pub error_message: Option<String>,
pub duration: Duration,
pub requests_made: usize,
pub expected_errors_caught: usize,
pub unexpected_behaviors: Vec<String>,
}
impl NetworkErrorTestResults {
pub fn new(test_name: &str) -> Self {
Self {
test_name: test_name.to_string(),
passed: false,
error_message: None,
duration: Duration::ZERO,
requests_made: 0,
expected_errors_caught: 0,
unexpected_behaviors: Vec::new(),
}
}
pub fn success(mut self) -> Self {
self.passed = true;
self
}
pub fn failure(mut self, message: &str) -> Self {
self.passed = false;
self.error_message = Some(message.to_string());
self
}
pub fn with_duration(mut self, duration: Duration) -> Self {
self.duration = duration;
self
}
pub fn with_requests(mut self, count: usize) -> Self {
self.requests_made = count;
self
}
pub fn with_expected_errors(mut self, count: usize) -> Self {
self.expected_errors_caught = count;
self
}
}
pub struct NetworkErrorTester {
config: NetworkErrorTestConfig,
}
impl NetworkErrorTester {
pub fn new(config: NetworkErrorTestConfig) -> Self {
Self { config }
}
pub async fn test_connection_timeout(&self) -> NetworkErrorTestResults {
let mut results = NetworkErrorTestResults::new("connection_timeout");
let start_time = Instant::now();
println!("⏱️ Testing connection timeout scenarios...");
let client = Client::builder()
.timeout(Duration::from_millis(500))
.connect_timeout(Duration::from_millis(200))
.build()
.unwrap();
let mut timeout_errors = 0;
let test_urls = [
"http://10.255.255.1:80/test", "http://192.0.2.1:80/test", "http://198.51.100.1:80/test", ];
for (i, url) in test_urls.iter().enumerate() {
println!(" 🔗 Testing timeout with URL {}: {}", i + 1, url);
let request_start = Instant::now();
let result = timeout(
self.config.request_timeout,
client
.post(*url)
.header("authorization", "Bearer test-key")
.json(&json!({
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": "test"}]
}))
.send(),
)
.await;
let request_duration = request_start.elapsed();
results.requests_made += 1;
match result {
Err(_) => {
timeout_errors += 1;
println!(
" ✅ Timeout occurred as expected after {:?}",
request_duration
);
}
Ok(Ok(response)) => {
results.unexpected_behaviors.push(format!(
"Unexpected successful response from {}: {}",
url,
response.status()
));
println!(" ⚠️ Unexpected success from {}", url);
}
Ok(Err(e)) => {
timeout_errors += 1;
println!(" ✅ Network error as expected: {}", e);
}
}
}
results.expected_errors_caught = timeout_errors;
results.duration = start_time.elapsed();
if timeout_errors >= 2 && results.unexpected_behaviors.is_empty() {
results.success()
} else {
let error_msg = format!(
"Expected at least 2 timeout errors, got {}. Unexpected behaviors: {:?}",
timeout_errors, results.unexpected_behaviors
);
results.failure(&error_msg)
}
}
pub async fn test_dns_resolution_failure(&self) -> NetworkErrorTestResults {
let mut results = NetworkErrorTestResults::new("dns_resolution_failure");
let start_time = Instant::now();
println!("🌐 Testing DNS resolution failures...");
let client = Client::builder()
.timeout(self.config.request_timeout)
.build()
.unwrap();
let invalid_domains = [
"http://this-domain-definitely-does-not-exist-12345.com/api",
"http://invalid-tld.invalidtld/api",
"http://nonexistent-subdomain.example.com/api",
];
let mut dns_errors = 0;
for (i, url) in invalid_domains.iter().enumerate() {
println!(" 🔍 Testing DNS failure with domain {}: {}", i + 1, url);
let result = client
.post(*url)
.header("authorization", "Bearer test-key")
.json(&json!({
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": "test"}]
}))
.send()
.await;
results.requests_made += 1;
match result {
Err(e) => {
if e.is_connect()
|| e.to_string().contains("dns")
|| e.to_string().contains("resolve")
|| e.to_string().contains("name")
{
dns_errors += 1;
println!(" ✅ DNS error as expected: {}", e);
} else {
results
.unexpected_behaviors
.push(format!("Unexpected error type from {}: {}", url, e));
println!(" ⚠️ Unexpected error type: {}", e);
}
}
Ok(response) => {
let status = response.status().as_u16();
if status == 502 || status >= 500 {
dns_errors += 1;
println!(" ✅ DNS-related server error as expected: {}", status);
} else {
results.unexpected_behaviors.push(format!(
"Unexpected successful response from {}: {}",
url,
response.status()
));
println!(" ⚠️ Unexpected success from {}", url);
}
}
}
}
results.expected_errors_caught = dns_errors;
results.duration = start_time.elapsed();
if dns_errors >= 2 && results.unexpected_behaviors.is_empty() {
results.success()
} else {
let error_msg = format!(
"Expected at least 2 DNS errors, got {}. Unexpected behaviors: {:?}",
dns_errors, results.unexpected_behaviors
);
results.failure(&error_msg)
}
}
pub async fn test_malformed_response_handling(&self) -> NetworkErrorTestResults {
let mut results = NetworkErrorTestResults::new("malformed_response_handling");
let start_time = Instant::now();
println!("📦 Testing malformed response handling...");
let server = MockLlmServer::new().await;
server.configure(MockTestUtils::malformed_json_config());
server.setup_openai_chat().await;
let client = Client::builder()
.timeout(self.config.request_timeout)
.build()
.unwrap();
let mut parse_errors = 0;
let test_count = 3;
for i in 0..test_count {
println!(" 📄 Testing malformed response {}", i + 1);
let result = client
.post(format!("{}/v1/chat/completions", server.base_url()))
.header("authorization", "Bearer test-key")
.json(&json!({
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": format!("test {}", i)}]
}))
.send()
.await;
results.requests_made += 1;
match result {
Ok(response) => {
let json_result = response.json::<Value>().await;
match json_result {
Err(e) => {
parse_errors += 1;
println!(" ✅ JSON parse error as expected: {}", e);
}
Ok(value) => {
results
.unexpected_behaviors
.push(format!("Unexpected successful JSON parse: {:?}", value));
println!(" ⚠️ Unexpected successful JSON parse");
}
}
}
Err(e) => {
results
.unexpected_behaviors
.push(format!("Unexpected request error: {}", e));
println!(" ⚠️ Unexpected request error: {}", e);
}
}
}
results.expected_errors_caught = parse_errors;
results.duration = start_time.elapsed();
if parse_errors == test_count && results.unexpected_behaviors.is_empty() {
results.success()
} else {
let error_msg = format!(
"Expected {} parse errors, got {}. Unexpected behaviors: {:?}",
test_count, parse_errors, results.unexpected_behaviors
);
results.failure(&error_msg)
}
}
pub async fn test_http_status_errors(&self) -> NetworkErrorTestResults {
let mut results = NetworkErrorTestResults::new("http_status_errors");
let start_time = Instant::now();
println!("🚨 Testing HTTP status code error handling...");
let server = MockLlmServer::new().await;
server.setup_error_scenarios().await;
let client = Client::builder()
.timeout(self.config.request_timeout)
.build()
.unwrap();
let error_scenarios = vec![("server_error", 500, "Server Error")];
let mut status_errors = 0;
for (scenario, expected_status, description) in error_scenarios {
println!(" 🔴 Testing {} scenario", description);
let result = client
.post(format!(
"{}/v1/chat/completions?trigger_error={}",
server.base_url(),
scenario
))
.header("authorization", "Bearer test-key")
.json(&json!({
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": "test"}]
}))
.send()
.await;
results.requests_made += 1;
match result {
Ok(response) => {
let status = response.status().as_u16();
if status == expected_status {
status_errors += 1;
println!(" ✅ Got expected status code: {}", status);
} else if status >= 400 {
status_errors += 1;
println!(
" ✅ Got error status code: {} (expected {})",
status, expected_status
);
} else {
results.unexpected_behaviors.push(format!(
"Unexpected success status {} for scenario {}",
status, scenario
));
println!(" ⚠️ Unexpected success status: {}", status);
}
}
Err(e) => {
results.unexpected_behaviors.push(format!(
"Unexpected request error for scenario {}: {}",
scenario, e
));
println!(" ⚠️ Unexpected request error: {}", e);
}
}
}
results.expected_errors_caught = status_errors;
results.duration = start_time.elapsed();
if status_errors >= 1 && results.unexpected_behaviors.len() <= 1 {
results.success()
} else {
let error_msg = format!(
"Expected at least 1 status error, got {}. Unexpected behaviors: {:?}",
status_errors, results.unexpected_behaviors
);
results.failure(&error_msg)
}
}
pub async fn test_network_instability(&self) -> NetworkErrorTestResults {
let mut results = NetworkErrorTestResults::new("network_instability");
let start_time = Instant::now();
println!("📡 Testing network instability scenarios...");
let server = MockLlmServer::new().await;
server.configure(MockTestUtils::network_failure_config());
server.setup_openai_chat().await;
let client = Client::builder()
.timeout(self.config.request_timeout)
.build()
.unwrap();
let mut network_errors = 0;
let test_count = 3;
for i in 0..test_count {
println!(" 📶 Testing network instability attempt {}", i + 1);
let result = client
.post(format!("{}/v1/chat/completions", server.base_url()))
.header("authorization", "Bearer test-key")
.json(&json!({
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": format!("instability test {}", i)}]
}))
.send()
.await;
results.requests_made += 1;
match result {
Ok(response) => {
let status = response.status().as_u16();
if status >= 500 {
network_errors += 1;
println!(" ✅ Got server error as expected: {}", status);
} else {
results
.unexpected_behaviors
.push(format!("Unexpected success status: {}", status));
println!(" ⚠️ Unexpected success status: {}", status);
}
}
Err(e) => {
network_errors += 1;
println!(" ✅ Network error as expected: {}", e);
}
}
}
results.expected_errors_caught = network_errors;
results.duration = start_time.elapsed();
if network_errors >= 2 && results.unexpected_behaviors.is_empty() {
results.success()
} else {
let error_msg = format!(
"Expected at least 2 network errors, got {}. Unexpected behaviors: {:?}",
network_errors, results.unexpected_behaviors
);
results.failure(&error_msg)
}
}
pub async fn run_all_tests(&self) -> Vec<NetworkErrorTestResults> {
println!("🚀 Running comprehensive network error tests...");
let mut all_results = Vec::new();
all_results.push(self.test_connection_timeout().await);
all_results.push(self.test_dns_resolution_failure().await);
all_results.push(self.test_malformed_response_handling().await);
all_results.push(self.test_http_status_errors().await);
all_results.push(self.test_network_instability().await);
let passed = all_results.iter().filter(|r| r.passed).count();
let total = all_results.len();
println!("\n📊 Network Error Tests Summary:");
println!(" ✅ Passed: {}/{}", passed, total);
println!(" ❌ Failed: {}/{}", total - passed, total);
for result in &all_results {
let status = if result.passed { "✅" } else { "❌" };
println!(" {} {}: {:?}", status, result.test_name, result.duration);
if let Some(error) = &result.error_message {
println!(" Error: {}", error);
}
}
all_results
}
}
#[tokio::test]
async fn test_connection_timeout() {
let config = NetworkErrorTestConfig {
request_timeout: Duration::from_secs(2),
..Default::default()
};
let tester = NetworkErrorTester::new(config);
let result = tester.test_connection_timeout().await;
assert!(
result.passed,
"Connection timeout test failed: {:?}",
result.error_message
);
assert!(result.expected_errors_caught >= 2);
assert!(result.requests_made >= 3);
}
#[tokio::test]
async fn test_dns_resolution_failure() {
let config = NetworkErrorTestConfig {
request_timeout: Duration::from_secs(3),
..Default::default()
};
let tester = NetworkErrorTester::new(config);
let result = tester.test_dns_resolution_failure().await;
assert!(
result.passed,
"DNS resolution failure test failed: {:?}",
result.error_message
);
assert!(result.expected_errors_caught >= 2);
assert!(result.requests_made >= 3);
}
#[tokio::test]
async fn test_malformed_response_handling() {
let config = NetworkErrorTestConfig::default();
let tester = NetworkErrorTester::new(config);
let result = tester.test_malformed_response_handling().await;
assert!(
result.passed,
"Malformed response handling test failed: {:?}",
result.error_message
);
assert!(
result.expected_errors_caught >= 2,
"Should catch at least 2 malformed response errors"
);
assert!(result.requests_made >= 2, "Should make at least 2 requests");
}
#[tokio::test]
async fn test_http_status_errors() {
let config = NetworkErrorTestConfig::default();
let tester = NetworkErrorTester::new(config);
let result = tester.test_http_status_errors().await;
assert!(
result.passed,
"HTTP status errors test failed: {:?}",
result.error_message
);
assert!(result.expected_errors_caught >= 1);
assert!(result.requests_made >= 1);
}
#[tokio::test]
async fn test_network_instability() {
let config = NetworkErrorTestConfig::default();
let tester = NetworkErrorTester::new(config);
let result = tester.test_network_instability().await;
assert!(
result.passed,
"Network instability test failed: {:?}",
result.error_message
);
assert!(
result.expected_errors_caught >= 1,
"Should catch at least 1 network error"
);
assert!(result.requests_made >= 2, "Should make at least 2 requests");
}
#[tokio::test]
async fn test_comprehensive_network_errors() {
let config = NetworkErrorTestConfig {
request_timeout: Duration::from_secs(3),
retry_attempts: 2,
retry_delay: Duration::from_millis(50),
test_concurrency: false, concurrent_requests: 5,
};
let tester = NetworkErrorTester::new(config);
let results = tester.run_all_tests().await;
let passed_count = results.iter().filter(|r| r.passed).count();
let total_count = results.len();
let min_passed = (total_count * 4) / 5;
assert!(
passed_count >= min_passed,
"Too many network error tests failed: {}/{} passed",
passed_count,
total_count
);
assert!(
total_count >= 5,
"Should have run at least 5 different error tests"
);
let total_requests: usize = results.iter().map(|r| r.requests_made).sum();
assert!(
total_requests >= 10,
"Should have made at least 10 test requests"
);
println!(
"🎉 Comprehensive network error tests completed: {}/{} passed",
passed_count, total_count
);
}