use api_tests::*;
use reqwest::StatusCode;
use serde_json::json;
use std::time::{Duration, Instant};
#[tokio::test]
async fn test_endpoint_response_latency() -> TestResult {
let client = build_test_client();
let mock_server = wiremock::MockServer::start().await;
wiremock::Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/api/v1/fast"))
.respond_with(wiremock::ResponseTemplate::new(200).set_body_json(json!({
"data": "response"
})))
.mount(&mock_server)
.await;
let url = format!("{}/api/v1/fast", mock_server.uri());
let start = Instant::now();
let res = client.get(&url).send().await?;
let duration = start.elapsed();
assert_eq!(res.status(), StatusCode::OK);
assert_latency_acceptable(duration, 100);
Ok(())
}
#[tokio::test]
async fn test_concurrent_requests_performance() -> TestResult {
let client = build_test_client();
let mock_server = wiremock::MockServer::start().await;
wiremock::Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/api/v1/data"))
.respond_with(wiremock::ResponseTemplate::new(200).set_body_json(json!({
"data": "test"
})))
.expect(100)
.mount(&mock_server)
.await;
let url = format!("{}/api/v1/data", mock_server.uri());
let start = Instant::now();
let mut tasks = vec![];
for _ in 0..100 {
let client = client.clone();
let url = url.clone();
tasks.push(tokio::spawn(async move {
client.get(&url).send().await
}));
}
let results = futures::future::join_all(tasks).await;
let duration = start.elapsed();
let success_count = results
.into_iter()
.filter(|r| r.is_ok() && r.as_ref().unwrap().as_ref().unwrap().status() == StatusCode::OK)
.count();
assert_eq!(success_count, 100);
assert_latency_acceptable(duration, 5000);
Ok(())
}
#[tokio::test]
async fn test_large_payload_performance() -> TestResult {
let client = build_test_client();
let mock_server = wiremock::MockServer::start().await;
let large_data = "x".repeat(1024 * 1024);
let large_body = json!({
"data": large_data
});
wiremock::Mock::given(wiremock::matchers::method("POST"))
.and(wiremock::matchers::path("/api/v1/upload"))
.respond_with(wiremock::ResponseTemplate::new(200).set_body_json(json!({
"status": "uploaded",
"size": 1048576
})))
.mount(&mock_server)
.await;
let url = format!("{}/api/v1/upload", mock_server.uri());
let start = Instant::now();
let res = client.post(&url).json(&large_body).send().await?;
let duration = start.elapsed();
assert_eq!(res.status(), StatusCode::OK);
assert_latency_acceptable(duration, 1000);
Ok(())
}
#[tokio::test]
async fn test_pagination_performance() -> TestResult {
let client = build_test_client();
let mock_server = wiremock::MockServer::start().await;
for page in 1..=10 {
wiremock::Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/api/v1/items"))
.and(wiremock::matchers::query_param("page", page.to_string()))
.respond_with(wiremock::ResponseTemplate::new(200).set_body_json(json!({
"data": vec![json!({"id": 1}); 100],
"page": page,
"total_pages": 10
})))
.mount(&mock_server)
.await;
}
let start = Instant::now();
for page in 1..=10 {
let url = format!("{}/api/v1/items?page={}", mock_server.uri(), page);
let res = client.get(&url).send().await?;
assert_eq!(res.status(), StatusCode::OK);
}
let duration = start.elapsed();
assert_latency_acceptable(duration, 2000);
Ok(())
}
#[tokio::test]
async fn test_streaming_response_performance() -> TestResult {
let client = build_test_client();
let mock_server = wiremock::MockServer::start().await;
let streaming_data = (0..1000)
.map(|i| format!("chunk_{}\n", i))
.collect::<String>();
wiremock::Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/api/v1/stream"))
.respond_with(
wiremock::ResponseTemplate::new(200)
.set_body_string(streaming_data)
.insert_header("content-type", "text/event-stream")
)
.mount(&mock_server)
.await;
let url = format!("{}/api/v1/stream", mock_server.uri());
let start = Instant::now();
let res = client.get(&url).send().await?;
let duration = start.elapsed();
assert_eq!(res.status(), StatusCode::OK);
assert_latency_acceptable(duration, 500);
Ok(())
}
#[tokio::test]
async fn test_p50_p95_p99_latency() -> TestResult {
let client = build_test_client();
let mock_server = wiremock::MockServer::start().await;
wiremock::Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/api/v1/latency"))
.respond_with(wiremock::ResponseTemplate::new(200).set_body_json(json!({
"data": "test"
})))
.expect(100)
.mount(&mock_server)
.await;
let url = format!("{}/api/v1/latency", mock_server.uri());
let mut durations = Vec::new();
for _ in 0..100 {
let start = Instant::now();
let res = client.get(&url).send().await?;
let duration = start.elapsed();
assert_eq!(res.status(), StatusCode::OK);
durations.push(duration);
}
let p50 = calculate_percentile(&mut durations, 0.5);
let p95 = calculate_percentile(&mut durations, 0.95);
let p99 = calculate_percentile(&mut durations, 0.99);
println!("Latency p50: {:?}", p50);
println!("Latency p95: {:?}", p95);
println!("Latency p99: {:?}", p99);
assert_latency_acceptable(p50, 50);
assert_latency_acceptable(p95, 100);
assert_latency_acceptable(p99, 200);
Ok(())
}
#[tokio::test]
async fn test_connection_reuse_performance() -> TestResult {
let client = build_test_client();
let mock_server = wiremock::MockServer::start().await;
wiremock::Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/api/v1/keepalive"))
.respond_with(wiremock::ResponseTemplate::new(200).set_body_json(json!({
"data": "test"
})))
.expect(50)
.mount(&mock_server)
.await;
let url = format!("{}/api/v1/keepalive", mock_server.uri());
let start = Instant::now();
client.get(&url).send().await?;
let first_request_duration = start.elapsed();
let mut subsequent_durations = Vec::new();
for _ in 0..49 {
let start = Instant::now();
client.get(&url).send().await?;
subsequent_durations.push(start.elapsed());
}
let avg_subsequent = subsequent_durations.iter().sum::<Duration>() / subsequent_durations.len() as u32;
println!("First request: {:?}", first_request_duration);
println!("Average subsequent: {:?}", avg_subsequent);
assert!(avg_subsequent <= first_request_duration + Duration::from_millis(10));
Ok(())
}
#[tokio::test]
async fn test_timeout_handling() -> TestResult {
let client = reqwest::Client::builder()
.timeout(Duration::from_millis(100))
.build()?;
let mock_server = wiremock::MockServer::start().await;
wiremock::Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/api/v1/slow"))
.respond_with(
wiremock::ResponseTemplate::new(200)
.set_delay(Duration::from_secs(1))
.set_body_json(json!({"data": "slow"}))
)
.mount(&mock_server)
.await;
let url = format!("{}/api/v1/slow", mock_server.uri());
let result = client.get(&url).send().await;
assert!(result.is_err());
Ok(())
}
#[tokio::test]
async fn test_compression_performance() -> TestResult {
let client = build_test_client();
let mock_server = wiremock::MockServer::start().await;
let compressible_data = "a".repeat(100000);
wiremock::Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/api/v1/compressed"))
.and(wiremock::matchers::header("accept-encoding", wiremock::matchers::any()))
.respond_with(
wiremock::ResponseTemplate::new(200)
.set_body_json(json!({
"data": compressible_data
}))
.insert_header("content-encoding", "gzip")
)
.mount(&mock_server)
.await;
let url = format!("{}/api/v1/compressed", mock_server.uri());
let start = Instant::now();
let res = client.get(&url).send().await?;
let duration = start.elapsed();
assert_eq!(res.status(), StatusCode::OK);
assert_latency_acceptable(duration, 500);
Ok(())
}