use crate::core::{ProxyCore, ProxyError, ProxyResponse, ResponseContext};
use crate::server::{ProxyServer, ServerConfig};
use bytes::Bytes;
use http_body_util::Full;
use hyper::{HeaderMap, Method, Request, Response};
use reqwest::Body;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::{RwLock, oneshot};
use tokio::task::Id;
use tokio::task::JoinSet;
#[allow(dead_code)]
fn convert_hyper_response(resp: &Response<Full<Bytes>>) -> ProxyResponse {
let status = resp.status().as_u16();
let headers = resp.headers().clone();
let body = Vec::new();
ProxyResponse {
status,
headers,
body: reqwest::Body::from(body),
context: Arc::new(RwLock::new(ResponseContext::default())),
}
}
fn test_convert_proxy_response(resp: ProxyResponse) -> Result<Response<Body>, ProxyError> {
let mut builder = Response::builder().status(resp.status);
let headers = builder.headers_mut().ok_or_else(|| {
ProxyError::Other("Failed to get mutable headers from response builder".into())
})?;
*headers = resp.headers;
builder
.body(resp.body)
.map_err(|e| ProxyError::Other(e.to_string()))
}
async fn create_mock_proxy_core() -> Arc<ProxyCore> {
use crate::config::Config;
use crate::router::PredicateRouter;
let config = Arc::new(Config::builder().build());
let router = Arc::new(PredicateRouter::new(config.clone()).await.unwrap());
Arc::new(ProxyCore::new(config, router).await.unwrap())
}
fn create_test_hyper_request(method: Method, path: &str) -> Request<http_body_util::Empty<Bytes>> {
Request::builder()
.method(method)
.uri(path)
.header("host", "localhost:8080")
.header("user-agent", "test-agent/1.0")
.body(http_body_util::Empty::<Bytes>::new())
.unwrap()
}
#[cfg(test)]
mod server_tests {
use super::*;
use hyper::StatusCode;
use std::time::Duration;
#[tokio::test]
async fn test_convert_hyper_response() {
let hyper_response = Response::builder()
.status(StatusCode::OK)
.header("content-type", "application/json")
.body(Full::new(Bytes::from(r#"{"result":"success"}"#)))
.unwrap();
let proxy_response = convert_hyper_response(&hyper_response);
assert_eq!(proxy_response.status, 200);
assert!(proxy_response.headers.contains_key("content-type"));
let content_type = proxy_response.headers.get("content-type").unwrap();
assert_eq!(content_type, "application/json");
}
#[test]
fn test_server_config_defaults() {
let config = ServerConfig::default();
assert_eq!(config.host, "127.0.0.1");
assert_eq!(config.port, 8080);
assert_eq!(config.health_port, 8081);
}
#[test]
fn test_server_config_default_functions() {
use crate::server::{default_health_port, default_host, default_port};
assert_eq!(default_host(), "127.0.0.1");
assert_eq!(default_port(), 8080);
assert_eq!(default_health_port(), 8081);
}
#[test]
fn test_server_config_clone_and_debug() {
let config = ServerConfig {
host: "0.0.0.0".to_string(),
port: 9000,
health_port: 9001,
};
let cloned = config.clone();
assert_eq!(config.host, cloned.host);
assert_eq!(config.port, cloned.port);
assert_eq!(config.health_port, cloned.health_port);
let debug_str = format!("{config:?}");
assert!(debug_str.contains("ServerConfig"));
assert!(debug_str.contains("0.0.0.0"));
assert!(debug_str.contains("9000"));
}
#[tokio::test]
async fn test_proxy_server_new() {
let config = ServerConfig::default();
let core = create_mock_proxy_core().await;
let server = ProxyServer::new(config.clone(), core);
assert_eq!(server.config.host, config.host);
assert_eq!(server.config.port, config.port);
assert_eq!(server.config.health_port, config.health_port);
}
#[tokio::test]
async fn test_convert_hyper_request_basic() {
let request = create_test_hyper_request(Method::GET, "/test/path");
assert_eq!(request.method(), Method::GET);
assert_eq!(request.uri().path(), "/test/path");
assert!(request.headers().contains_key("host"));
assert!(request.headers().contains_key("user-agent"));
}
#[tokio::test]
async fn test_convert_hyper_request_with_query() {
let request = Request::builder()
.method(Method::POST)
.uri("/api/users?page=1&limit=10")
.header("content-type", "application/json")
.body(http_body_util::Empty::<Bytes>::new())
.unwrap();
assert_eq!(request.method(), Method::POST);
assert_eq!(request.uri().path(), "/api/users");
assert_eq!(request.uri().query(), Some("page=1&limit=10"));
assert!(request.headers().contains_key("content-type"));
}
#[tokio::test]
async fn test_convert_hyper_request_different_methods() {
let methods = vec![
Method::GET,
Method::POST,
Method::PUT,
Method::DELETE,
Method::PATCH,
Method::HEAD,
Method::OPTIONS,
];
for method in methods {
let request = create_test_hyper_request(method.clone(), "/test");
assert_eq!(request.method(), method);
assert_eq!(request.uri().path(), "/test");
}
}
#[test]
fn test_convert_proxy_response_success() {
let mut headers = HeaderMap::new();
headers.insert("content-type", "application/json".parse().unwrap());
headers.insert("x-custom-header", "test-value".parse().unwrap());
let proxy_resp = ProxyResponse {
status: 200,
headers,
body: Body::from("test response body"),
context: Arc::new(RwLock::new(ResponseContext::default())),
};
let result = test_convert_proxy_response(proxy_resp);
assert!(result.is_ok());
let hyper_resp = result.unwrap();
assert_eq!(hyper_resp.status(), 200);
assert!(hyper_resp.headers().contains_key("content-type"));
assert!(hyper_resp.headers().contains_key("x-custom-header"));
}
#[test]
fn test_convert_proxy_response_different_status_codes() {
let status_codes = vec![200, 201, 400, 401, 403, 404, 500, 502, 503, 504];
for status in status_codes {
let proxy_resp = ProxyResponse {
status,
headers: HeaderMap::new(),
body: Body::from(""),
context: Arc::new(RwLock::new(ResponseContext::default())),
};
let result = test_convert_proxy_response(proxy_resp);
assert!(result.is_ok());
let hyper_resp = result.unwrap();
assert_eq!(hyper_resp.status().as_u16(), status);
}
}
#[tokio::test]
async fn test_server_start_invalid_address() {
let config = ServerConfig {
host: "invalid-host-name-that-does-not-exist".to_string(),
port: 8080,
health_port: 8081,
};
let core = create_mock_proxy_core();
let server = ProxyServer::new(config, core.await);
let result = server.start().await;
assert!(result.is_err());
if let Err(ProxyError::Other(msg)) = result {
assert!(msg.contains("Invalid server address"));
} else {
panic!("Expected ProxyError::Other with address error");
}
}
#[tokio::test]
async fn test_server_start_method_exists() {
let config = ServerConfig {
host: "127.0.0.1".to_string(),
port: 8080,
health_port: 8081,
};
let core = create_mock_proxy_core().await;
let server = ProxyServer::new(config, core);
assert_eq!(server.config.host, "127.0.0.1");
assert_eq!(server.config.port, 8080);
assert_eq!(server.config.health_port, 8081);
let core = server.core();
assert!(core.config.get::<String>("server.host").is_ok());
}
#[tokio::test]
async fn test_handle_request_basic() {
let timeout_error = ProxyError::Timeout(Duration::from_secs(30));
assert!(timeout_error.to_string().contains("timed out"));
let routing_error = ProxyError::RoutingError("No route found".to_string());
assert!(routing_error.to_string().contains("routing error"));
}
#[test]
fn test_proxy_error_variants() {
let timeout_error = ProxyError::Timeout(Duration::from_secs(30));
assert!(timeout_error.to_string().contains("timed out"));
let routing_error = ProxyError::RoutingError("No route found".to_string());
assert!(routing_error.to_string().contains("routing error"));
let security_error = ProxyError::SecurityError("Access denied".to_string());
assert!(security_error.to_string().contains("security error"));
let config_error = ProxyError::ConfigError("Invalid config".to_string());
assert!(config_error.to_string().contains("configuration error"));
let filter_error = ProxyError::FilterError("Filter failed".to_string());
assert!(filter_error.to_string().contains("filter error"));
let other_error = ProxyError::Other("Generic error".to_string());
assert!(other_error.to_string().contains("Generic error"));
}
#[tokio::test]
async fn test_convert_hyper_request_root_path() {
let request = create_test_hyper_request(Method::GET, "/");
assert_eq!(request.method(), Method::GET);
assert_eq!(request.uri().path(), "/");
assert_eq!(request.uri().query(), None);
}
#[tokio::test]
async fn test_convert_hyper_request_no_headers() {
let request = Request::builder()
.method(Method::GET)
.uri("/test")
.body(http_body_util::Empty::<Bytes>::new())
.unwrap();
assert_eq!(request.method(), Method::GET);
assert_eq!(request.uri().path(), "/test");
assert!(request.headers().is_empty() || !request.headers().is_empty()); }
#[test]
fn test_convert_proxy_response_empty_headers() {
let proxy_resp = ProxyResponse {
status: 204, headers: HeaderMap::new(),
body: Body::from(""),
context: Arc::new(RwLock::new(ResponseContext::default())),
};
let result = test_convert_proxy_response(proxy_resp);
assert!(result.is_ok());
let hyper_resp = result.unwrap();
assert_eq!(hyper_resp.status(), 204);
assert!(hyper_resp.headers().is_empty());
}
#[test]
fn test_server_config_serialization() {
let config = ServerConfig {
host: "0.0.0.0".to_string(),
port: 3000,
health_port: 3001,
};
let json = serde_json::to_string(&config).unwrap();
assert!(json.contains("0.0.0.0"));
assert!(json.contains("3000"));
assert!(json.contains("3001"));
let deserialized: ServerConfig = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.host, config.host);
assert_eq!(deserialized.port, config.port);
assert_eq!(deserialized.health_port, config.health_port);
}
#[test]
fn test_server_config_with_defaults() {
let json = r#"{"port": 9000}"#;
let config: ServerConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.host, "127.0.0.1"); assert_eq!(config.port, 9000);
assert_eq!(config.health_port, 8081); }
#[test]
fn test_server_config_empty_json() {
let json = r"{}";
let config: ServerConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.host, "127.0.0.1");
assert_eq!(config.port, 8080);
assert_eq!(config.health_port, 8081);
}
#[cfg(unix)]
#[tokio::test]
async fn test_unix_signal_handling() {
use tokio::signal::unix::{SignalKind, signal};
let result = signal(SignalKind::terminate());
assert!(result.is_ok());
}
#[cfg(not(unix))]
#[test]
fn test_non_unix_signal_handling() {
let sigterm = std::future::pending::<()>();
assert!(
std::future::Future::poll(
std::pin::Pin::new(&mut Box::pin(sigterm)),
&mut std::task::Context::from_waker(std::task::Waker::noop())
)
.is_pending()
);
}
#[cfg(feature = "opentelemetry")]
#[test]
fn test_opentelemetry_imports() {
use opentelemetry::trace::{TraceContextExt, Tracer};
use opentelemetry::{Context, KeyValue, global};
let tracer = global::tracer("test");
let span = tracer.start("test-span");
let _context = Context::current().with_span(span);
let kv = KeyValue::new("test-key", "test-value");
assert_eq!(kv.key.as_str(), "test-key");
}
#[cfg(feature = "swagger-ui")]
#[test]
fn test_swagger_ui_imports() {
use crate::server::swagger::SwaggerUIConfig;
let config = SwaggerUIConfig {
enabled: true,
path: "/swagger".to_string(),
sources: vec![],
};
assert!(config.enabled);
assert_eq!(config.path, "/swagger");
}
#[tokio::test]
async fn test_proxy_server_debug_implementation() {
let config = ServerConfig::default();
let core = create_mock_proxy_core().await;
let server = ProxyServer::new(config, core);
let debug_str = format!("{server:?}");
assert!(debug_str.contains("ProxyServer"));
assert!(debug_str.contains("config"));
assert!(debug_str.contains("core"));
}
#[tokio::test]
async fn test_proxy_server_clone() {
let config = ServerConfig::default();
let core = create_mock_proxy_core().await;
let server = ProxyServer::new(config, core);
let cloned_server = server.clone();
assert_eq!(server.config.host, cloned_server.config.host);
assert_eq!(server.config.port, cloned_server.config.port);
assert_eq!(server.config.health_port, cloned_server.config.health_port);
}
#[tokio::test]
async fn test_convert_hyper_request_with_custom_target() {
let request = create_test_hyper_request(Method::GET, "/test");
assert_eq!(request.method(), Method::GET);
assert_eq!(request.uri().path(), "/test");
}
#[test]
fn test_convert_proxy_response_with_large_headers() {
let mut headers = HeaderMap::new();
for i in 0..50 {
let header_name = format!("x-custom-header-{i}");
let header_value = format!("value-{i}");
headers.insert(
header_name.parse::<hyper::header::HeaderName>().unwrap(),
header_value.parse().unwrap(),
);
}
let proxy_resp = ProxyResponse {
status: 200,
headers,
body: Body::from("test"),
context: Arc::new(RwLock::new(ResponseContext::default())),
};
let result = test_convert_proxy_response(proxy_resp);
assert!(result.is_ok());
let hyper_resp = result.unwrap();
assert_eq!(hyper_resp.status(), 200);
assert!(hyper_resp.headers().len() >= 50);
}
#[tokio::test]
async fn test_setup_listener() {
let config = ServerConfig {
host: "127.0.0.1".to_string(),
port: 0, health_port: 0,
};
let core = create_mock_proxy_core().await;
let server = ProxyServer::new(config, core);
let result = server.setup_listener().await;
assert!(result.is_ok());
let listener = result.unwrap();
let addr = listener.local_addr().unwrap();
assert_eq!(addr.ip().to_string(), "127.0.0.1");
assert!(addr.port() > 0); }
#[tokio::test]
async fn test_setup_listener_invalid_address() {
let config = ServerConfig {
host: "invalid.address".to_string(),
port: 8080,
health_port: 8081,
};
let core = create_mock_proxy_core().await;
let server = ProxyServer::new(config, core);
let result = server.setup_listener().await;
assert!(result.is_err());
if let Err(ProxyError::Other(msg)) = result {
assert!(msg.contains("Invalid server address") || msg.contains("Failed to bind"));
} else {
panic!("Expected ProxyError::Other");
}
}
#[test]
fn test_handle_connection_result_success() {
let result: Result<(), Box<dyn std::error::Error + Send + Sync>> = Ok(());
ProxyServer::handle_connection_result(result);
}
#[test]
fn test_handle_connection_result_error() {
let error = Box::new(std::io::Error::other("test error"));
let result: Result<(), Box<dyn std::error::Error + Send + Sync>> = Err(error);
ProxyServer::handle_connection_result(result);
}
#[test]
fn test_handle_connection_result_graceful_close() {
let error = Box::new(std::io::Error::other("connection closed"));
let result: Result<(), Box<dyn std::error::Error + Send + Sync>> = Err(error);
ProxyServer::handle_connection_result(result);
}
#[cfg(unix)]
#[tokio::test]
async fn test_setup_signal_handlers_unix() {
let result = ProxyServer::setup_signal_handlers();
assert!(result.is_ok());
let (ctrl_c, _term_stream) = result.unwrap();
assert!(std::mem::size_of_val(&ctrl_c) > 0);
}
#[cfg(not(unix))]
#[tokio::test]
async fn test_setup_signal_handlers_windows() {
let result = ProxyServer::setup_signal_handlers();
assert!(result.is_ok());
let ctrl_c = result.unwrap();
assert!(std::mem::size_of_val(&ctrl_c) > 0);
}
#[tokio::test]
async fn test_graceful_shutdown_empty_joinset() {
let config = ServerConfig::default();
let core = create_mock_proxy_core().await;
let server = ProxyServer::new(config, core);
let join_set = JoinSet::new();
let shutdown_senders = Arc::new(RwLock::new(HashMap::<Id, oneshot::Sender<()>>::new()));
let result = server.graceful_shutdown(join_set, shutdown_senders).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_graceful_shutdown_with_senders() {
let config = ServerConfig::default();
let core = create_mock_proxy_core().await;
let server = ProxyServer::new(config, core);
let join_set = JoinSet::new();
let shutdown_senders = Arc::new(RwLock::new(HashMap::<Id, oneshot::Sender<()>>::new()));
let handle1 = tokio::spawn(async { "dummy1" });
let handle2 = tokio::spawn(async { "dummy2" });
let (tx1, _rx1) = oneshot::channel();
let (tx2, _rx2) = oneshot::channel();
{
let mut senders = shutdown_senders.write().await;
senders.insert(handle1.id(), tx1);
senders.insert(handle2.id(), tx2);
}
handle1.abort();
handle2.abort();
let result = server
.graceful_shutdown(join_set, shutdown_senders.clone())
.await;
assert!(result.is_ok());
let senders = shutdown_senders.read().await;
assert!(senders.is_empty());
}
}