#![allow(clippy::pedantic, clippy::nursery, clippy::all)]
mod common;
use axum::http::{Method, StatusCode};
use serde_json::json;
use std::time::Duration;
use crate::common::test_builders::{HandlerBuilder, RequestBuilder, assert_status, parse_json_body};
#[tokio::test]
async fn test_compression_applies_gzip_for_large_response() {
let large_data = vec!["x".repeat(200); 10];
let large_body = json!({
"data": large_data,
"message": "This is a large response that should be compressed"
});
let handler = HandlerBuilder::new().status(200).json_body(large_body).build();
let (request, request_data) = RequestBuilder::new()
.method(Method::GET)
.path("/large-data")
.header("Accept-Encoding", "gzip")
.build();
let response = handler.call(request, request_data).await.unwrap();
assert_status(&response, StatusCode::OK);
}
#[tokio::test]
async fn test_compression_skipped_for_small_response() {
let small_body = json!({
"status": "ok",
"message": "small"
});
let handler = HandlerBuilder::new().status(200).json_body(small_body.clone()).build();
let (request, request_data) = RequestBuilder::new()
.method(Method::GET)
.path("/small-data")
.header("Accept-Encoding", "gzip")
.build();
let response = handler.call(request, request_data).await.unwrap();
assert_status(&response, StatusCode::OK);
}
#[tokio::test]
async fn test_compression_respects_accept_encoding() {
let large_data = vec!["x".repeat(200); 10];
let large_body = json!({"data": large_data});
let handler = HandlerBuilder::new().status(200).json_body(large_body).build();
let (request, request_data) = RequestBuilder::new().method(Method::GET).path("/data").build();
let response = handler.call(request, request_data).await.unwrap();
assert_status(&response, StatusCode::OK);
}
#[tokio::test]
async fn test_compression_preserves_content_type() {
let body = json!({
"data": vec!["x".repeat(200); 10],
"message": "test"
});
let handler = HandlerBuilder::new().status(200).json_body(body).build();
let (request, request_data) = RequestBuilder::new()
.method(Method::GET)
.path("/api/data")
.header("Accept-Encoding", "gzip")
.build();
let response = handler.call(request, request_data).await.unwrap();
assert_status(&response, StatusCode::OK);
}
#[tokio::test]
async fn test_rate_limit_allows_requests_below_threshold() {
let handler = HandlerBuilder::new().status(200).json_body(json!({"count": 1})).build();
let mut handles = vec![];
for i in 0..10 {
let handler_clone = handler.clone();
let handle = tokio::spawn(async move {
let (request, request_data) = RequestBuilder::new()
.method(Method::GET)
.path(&format!("/request-{}", i))
.build();
let response = handler_clone.call(request, request_data).await.unwrap();
response.status()
});
handles.push(handle);
}
for handle in handles {
let status = handle.await.unwrap();
assert_eq!(status, StatusCode::OK);
}
}
#[tokio::test]
async fn test_rate_limit_blocks_requests_above_threshold() {
let handler = HandlerBuilder::new().status(200).json_body(json!({"ok": true})).build();
let (request, request_data) = RequestBuilder::new().method(Method::GET).path("/api/endpoint").build();
let response = handler.call(request, request_data).await.unwrap();
assert_status(&response, StatusCode::OK);
}
#[tokio::test]
async fn test_rate_limit_per_ip() {
let handler = HandlerBuilder::new()
.status(200)
.json_body(json!({"ip": "test"}))
.build();
let (request1, request_data1) = RequestBuilder::new()
.method(Method::GET)
.path("/api/endpoint")
.header("X-Forwarded-For", "192.168.1.1")
.build();
let (request2, request_data2) = RequestBuilder::new()
.method(Method::GET)
.path("/api/endpoint")
.header("X-Forwarded-For", "192.168.1.2")
.build();
let response1 = handler.call(request1, request_data1).await.unwrap();
assert_status(&response1, StatusCode::OK);
let response2 = handler.call(request2, request_data2).await.unwrap();
assert_status(&response2, StatusCode::OK);
}
#[tokio::test]
async fn test_timeout_allows_fast_handler() {
let handler = HandlerBuilder::new()
.status(200)
.json_body(json!({"result": "success"}))
.delay(Duration::from_millis(50))
.build();
let (request, request_data) = RequestBuilder::new().method(Method::GET).path("/fast-endpoint").build();
let start = std::time::Instant::now();
let response = handler.call(request, request_data).await.unwrap();
let elapsed = start.elapsed();
assert_status(&response, StatusCode::OK);
assert!(elapsed >= Duration::from_millis(50));
}
#[tokio::test]
async fn test_timeout_cancels_slow_handler() {
let handler = HandlerBuilder::new()
.status(200)
.json_body(json!({"result": "ok"}))
.delay(Duration::from_secs(2))
.build();
let (request, request_data) = RequestBuilder::new().method(Method::GET).path("/slow-endpoint").build();
let start = std::time::Instant::now();
let result = tokio::time::timeout(Duration::from_secs(1), handler.call(request, request_data)).await;
let elapsed = start.elapsed();
assert!(result.is_err(), "Expected timeout to occur");
assert!(elapsed >= Duration::from_secs(1));
assert!(elapsed < Duration::from_secs(2));
}
#[tokio::test]
async fn test_timeout_error_message() {
let timeout_handler = HandlerBuilder::new()
.status(408)
.json_body(json!({
"error": "Request timeout",
"code": "REQUEST_TIMEOUT",
"details": "Handler did not complete within the configured timeout"
}))
.build();
let (request, request_data) = RequestBuilder::new().method(Method::GET).path("/endpoint").build();
let response = timeout_handler.call(request, request_data).await.unwrap();
assert_status(&response, StatusCode::REQUEST_TIMEOUT);
let mut response_mut = response;
let body = parse_json_body(&mut response_mut).await.unwrap();
assert_eq!(body["error"], "Request timeout");
assert_eq!(body["code"], "REQUEST_TIMEOUT");
assert!(body["details"].as_str().unwrap().contains("timeout"));
}
#[tokio::test]
async fn test_request_id_generates_when_missing() {
let handler = HandlerBuilder::new()
.status(200)
.json_body(json!({"message": "ok"}))
.build();
let (request, request_data) = RequestBuilder::new().method(Method::GET).path("/api/resource").build();
let response = handler.call(request, request_data).await.unwrap();
assert_status(&response, StatusCode::OK);
}
#[tokio::test]
async fn test_request_id_preserves_when_present() {
let request_id = "abc-123-def-456";
let handler = HandlerBuilder::new()
.status(200)
.json_body(json!({"message": "ok"}))
.build();
let (request, request_data) = RequestBuilder::new()
.method(Method::GET)
.path("/api/resource")
.header("X-Request-ID", request_id)
.build();
let response = handler.call(request, request_data).await.unwrap();
assert_status(&response, StatusCode::OK);
}
#[tokio::test]
async fn test_request_id_propagation_to_handler() {
let handler = HandlerBuilder::new()
.status(200)
.json_body(json!({
"message": "handler executed with request id",
"trace": "request-id-123"
}))
.build();
let (request, request_data) = RequestBuilder::new()
.method(Method::GET)
.path("/api/trace")
.header("X-Request-ID", "request-id-123")
.build();
let response = handler.call(request, request_data).await.unwrap();
assert_status(&response, StatusCode::OK);
let mut response_mut = response;
let body = parse_json_body(&mut response_mut).await.unwrap();
assert_eq!(body["message"], "handler executed with request id");
assert_eq!(body["trace"], "request-id-123");
}
#[tokio::test]
async fn test_middleware_composition_all_pass() {
let handler = HandlerBuilder::new()
.status(200)
.json_body(json!({
"request_id": "req-001",
"status": "success"
}))
.delay(Duration::from_millis(10))
.build();
let (request, request_data) = RequestBuilder::new()
.method(Method::GET)
.path("/api/combined")
.header("X-Request-ID", "req-001")
.header("Accept-Encoding", "gzip")
.build();
let response = handler.call(request, request_data).await.unwrap();
assert_status(&response, StatusCode::OK);
let mut response_mut = response;
let body = parse_json_body(&mut response_mut).await.unwrap();
assert_eq!(body["status"], "success");
}
#[tokio::test]
async fn test_timeout_precedence_over_rate_limit() {
let handler = HandlerBuilder::new()
.status(200)
.json_body(json!({"message": "slow"}))
.delay(Duration::from_secs(2))
.build();
let (request, request_data) = RequestBuilder::new()
.method(Method::GET)
.path("/api/slow")
.header("X-Request-ID", "req-slow")
.build();
let result = tokio::time::timeout(Duration::from_secs(1), handler.call(request, request_data)).await;
assert!(result.is_err(), "Expected timeout");
}