399 lines
12 KiB
Rust
399 lines
12 KiB
Rust
//! 集成测试模块
|
||
//!
|
||
//! 提供代理功能的集成测试和端到端测试。
|
||
|
||
use axum::{
|
||
body::Body,
|
||
http::{Method, Request, StatusCode},
|
||
response::Response,
|
||
};
|
||
use std::collections::HashMap;
|
||
use std::time::Duration;
|
||
use tokio::time::sleep;
|
||
|
||
use crate::config::ProxyConfig;
|
||
use crate::service::PortProxyService;
|
||
use crate::server::ProxyServer;
|
||
|
||
/// 创建测试配置
|
||
fn create_test_config() -> ProxyConfig {
|
||
ProxyConfig {
|
||
listen_port: 0, // 使用随机可用端口
|
||
default_backend_port: 3000,
|
||
backend_host: "127.0.0.1".to_string(),
|
||
port_param: "port".to_string(),
|
||
config_file: None,
|
||
verbose: false,
|
||
}
|
||
}
|
||
|
||
/// 创建完整的测试服务器配置
|
||
fn create_test_server_config(listen_port: u16) -> ProxyConfig {
|
||
ProxyConfig {
|
||
listen_port,
|
||
default_backend_port: 3000,
|
||
backend_host: "127.0.0.1".to_string(),
|
||
port_param: "port".to_string(),
|
||
config_file: None,
|
||
verbose: false,
|
||
}
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_config_validation() {
|
||
// 测试有效配置
|
||
let valid_config = ProxyConfig::default();
|
||
assert!(valid_config.validate().is_ok());
|
||
|
||
// 测试无效监听端口
|
||
let mut invalid_config = ProxyConfig::default();
|
||
invalid_config.listen_port = 0;
|
||
assert!(invalid_config.validate().is_err());
|
||
|
||
// 测试无效后端端口
|
||
let mut invalid_config = ProxyConfig::default();
|
||
invalid_config.default_backend_port = 0;
|
||
assert!(invalid_config.validate().is_err());
|
||
|
||
// 测试空主机地址
|
||
let mut invalid_config = ProxyConfig::default();
|
||
invalid_config.backend_host = String::new();
|
||
assert!(invalid_config.validate().is_err());
|
||
|
||
// 测试空端口参数名
|
||
let mut invalid_config = ProxyConfig::default();
|
||
invalid_config.port_param = String::new();
|
||
assert!(invalid_config.validate().is_err());
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_service_backend_management() {
|
||
let config = create_test_config();
|
||
let service = PortProxyService::new(config);
|
||
|
||
// 初始状态应该有默认后端
|
||
assert_eq!(service.backend_count().await, 1);
|
||
assert!(service.has_backend(3000).await);
|
||
assert!(!service.has_backend(3001).await);
|
||
|
||
// 添加后端
|
||
service.add_backend(3001, "localhost".to_string()).await;
|
||
assert_eq!(service.backend_count().await, 2);
|
||
assert!(service.has_backend(3001).await);
|
||
|
||
// 获取后端列表
|
||
let backends = service.list_backends().await;
|
||
assert_eq!(backends.len(), 2);
|
||
assert!(backends.contains_key(&3000));
|
||
assert!(backends.contains_key(&3001));
|
||
|
||
// 移除后端
|
||
service.remove_backend(3001).await;
|
||
assert_eq!(service.backend_count().await, 1);
|
||
assert!(!service.has_backend(3001).await);
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_port_extraction_scenarios() {
|
||
let config = create_test_config();
|
||
let service = PortProxyService::new(config);
|
||
|
||
// 测试场景1: 从查询参数提取端口
|
||
let request = Request::builder()
|
||
.uri("/api/users?port=8080&format=json")
|
||
.body(Body::empty())
|
||
.unwrap();
|
||
let port = service.extract_target_port(&request).unwrap();
|
||
assert_eq!(port, 8080);
|
||
|
||
// 测试场景2: 从路径提取端口
|
||
let request = Request::builder()
|
||
.uri("/proxy/9000/api/v1/data")
|
||
.body(Body::empty())
|
||
.unwrap();
|
||
let port = service.extract_target_port(&request).unwrap();
|
||
assert_eq!(port, 9000);
|
||
|
||
// 测试场景3: 多个查询参数
|
||
let request = Request::builder()
|
||
.uri("/api/search?q=test&port=7000&page=1")
|
||
.body(Body::empty())
|
||
.unwrap();
|
||
let port = service.extract_target_port(&request).unwrap();
|
||
assert_eq!(port, 7000);
|
||
|
||
// 测试场景4: 端口参数格式错误
|
||
let request = Request::builder()
|
||
.uri("/api/data?port=invalid")
|
||
.body(Body::empty())
|
||
.unwrap();
|
||
let port = service.extract_target_port(&request).unwrap();
|
||
assert_eq!(port, 3000); // 应该回退到默认端口
|
||
|
||
// 测试场景5: 路径格式错误
|
||
let request = Request::builder()
|
||
.uri("/proxy/not_a_number/api")
|
||
.body(Body::empty())
|
||
.unwrap();
|
||
let port = service.extract_target_port(&request).unwrap();
|
||
assert_eq!(port, 3000); // 应该回退到默认端口
|
||
|
||
// 测试场景6: 没有端口参数
|
||
let request = Request::builder()
|
||
.uri("/api/data")
|
||
.body(Body::empty())
|
||
.unwrap();
|
||
let port = service.extract_target_port(&request).unwrap();
|
||
assert_eq!(port, 3000); // 应该使用默认端口
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_uri_building() {
|
||
let config = create_test_config();
|
||
let service = PortProxyService::new(config);
|
||
|
||
// 测试普通路径
|
||
let uri = service.build_target_uri("localhost", 3000, "/api/data").unwrap();
|
||
assert_eq!(uri, "http://localhost:3000/api/data");
|
||
|
||
// 测试带查询参数的路径
|
||
let uri = service.build_target_uri("localhost", 3000, "/api/data?format=json").unwrap();
|
||
assert_eq!(uri, "http://localhost:3000/api/data?format=json");
|
||
|
||
// 测试代理路径前缀
|
||
let uri = service.build_target_uri("localhost", 8080, "/proxy/8080/api/data").unwrap();
|
||
assert_eq!(uri, "http://localhost:8080/api/data");
|
||
|
||
// 测试代理路径前缀带查询参数
|
||
let uri = service.build_target_uri("localhost", 8080, "/proxy/8080/api/data?format=json").unwrap();
|
||
assert_eq!(uri, "http://localhost:8080/api/data?format=json");
|
||
|
||
// 测试根路径
|
||
let uri = service.build_target_uri("localhost", 3000, "/").unwrap();
|
||
assert_eq!(uri, "http://localhost:3000/");
|
||
|
||
// 测试代理根路径
|
||
let uri = service.build_target_uri("localhost", 8080, "/proxy/8080/").unwrap();
|
||
assert_eq!(uri, "http://localhost:8080/");
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_server_builder() {
|
||
// 测试默认构建器
|
||
let server = ProxyServerBuilder::new().build();
|
||
assert_eq!(server.listen_port(), 8080);
|
||
assert_eq!(server.default_backend_port(), 3000);
|
||
|
||
// 测试自定义配置
|
||
let server = ProxyServerBuilder::new()
|
||
.listen_port(9000)
|
||
.default_backend_port(4000)
|
||
.backend_host("example.com")
|
||
.port_param("target")
|
||
.verbose(true)
|
||
.build();
|
||
|
||
assert_eq!(server.listen_port(), 9000);
|
||
assert_eq!(server.default_backend_port(), 4000);
|
||
assert_eq!(server.config().backend_host, "example.com");
|
||
assert_eq!(server.config().port_param, "target");
|
||
assert!(server.config().verbose);
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_server_convenience_methods() {
|
||
// 测试默认服务器
|
||
let server = ProxyServer::default();
|
||
assert_eq!(server.listen_port(), 8080);
|
||
assert_eq!(server.default_backend_port(), 3000);
|
||
|
||
// 测试自定义端口
|
||
let server = ProxyServer::with_listen_port(9090);
|
||
assert_eq!(server.listen_port(), 9090);
|
||
assert_eq!(server.default_backend_port(), 3000);
|
||
|
||
// 测试链式配置
|
||
let server = ProxyServer::with_listen_port(8080)
|
||
.with_backend_host("localhost")
|
||
.with_port_param("port")
|
||
.with_default_backend_port(3000);
|
||
|
||
assert_eq!(server.config().backend_host, "localhost");
|
||
assert_eq!(server.config().port_param, "port");
|
||
assert_eq!(server.config().default_backend_port, 3000);
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_server_pre_start_check() {
|
||
let server = ProxyServer::default();
|
||
|
||
// 默认配置应该通过预启动检查
|
||
let result = server.pre_start_check().await;
|
||
assert!(result.is_ok());
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_proxy_headers() {
|
||
let config = create_test_config();
|
||
let service = PortProxyService::new(config);
|
||
|
||
let mut request = Request::builder()
|
||
.uri("/api/data")
|
||
.body(Body::empty())
|
||
.unwrap();
|
||
|
||
// 添加代理头
|
||
service.add_proxy_headers(&mut request).unwrap();
|
||
|
||
// 验证头信息
|
||
assert_eq!(request.headers().get("X-Forwarded-Proto").unwrap(), "http");
|
||
assert_eq!(request.headers().get("X-Port-Proxy").unwrap(), "simple-proxy");
|
||
}
|
||
|
||
// 这个测试需要实际的网络连接,只在集成测试中运行
|
||
#[ignore]
|
||
#[tokio::test]
|
||
async fn test_end_to_end_proxy_request() {
|
||
// 注意:这个测试需要有一个在端口3000运行的实际服务
|
||
// 在实际运行时,需要先启动一个简单的HTTP服务器
|
||
|
||
let config = create_test_server_config(8080);
|
||
let service = PortProxyService::new(config);
|
||
|
||
// 创建测试请求
|
||
let request = Request::builder()
|
||
.method(Method::GET)
|
||
.uri("/?port=3000")
|
||
.body(Body::empty())
|
||
.unwrap();
|
||
|
||
// 尝试代理请求(可能会失败,因为可能没有实际的服务在运行)
|
||
let result = service.proxy_request(request).await;
|
||
|
||
// 检查是否正确解析了端口(即使连接失败)
|
||
// 这主要验证端口提取和URI构建逻辑
|
||
match result {
|
||
Ok(_) => println!("proxyrequestsucceeded"),
|
||
Err(e) => println!("proxyrequestfailed( message ): {}", e),
|
||
}
|
||
}
|
||
|
||
// 性能测试
|
||
#[tokio::test]
|
||
async fn test_concurrent_backend_operations() {
|
||
let config = create_test_config();
|
||
let service = PortProxyService::new(config);
|
||
|
||
// 并发添加多个后端
|
||
let mut handles = Vec::new();
|
||
for i in 1..=10 {
|
||
let service_clone = service.clone();
|
||
let handle = tokio::spawn(async move {
|
||
service_clone.add_backend(3000 + i, format!("host{}", i)).await;
|
||
});
|
||
handles.push(handle);
|
||
}
|
||
|
||
// 等待所有操作完成
|
||
for handle in handles {
|
||
handle.await.unwrap();
|
||
}
|
||
|
||
// 验证所有后端都已添加
|
||
assert_eq!(service.backend_count().await, 11); // 1个默认 + 10个新增
|
||
|
||
// 并发读取后端列表
|
||
let mut handles = Vec::new();
|
||
for _ in 0..5 {
|
||
let service_clone = service.clone();
|
||
let handle = tokio::spawn(async move {
|
||
service_clone.list_backends().await
|
||
});
|
||
handles.push(handle);
|
||
}
|
||
|
||
// 验证并发读取的一致性
|
||
for handle in handles {
|
||
let backends = handle.await.unwrap();
|
||
assert_eq!(backends.len(), 11);
|
||
}
|
||
}
|
||
|
||
// 边界条件测试
|
||
#[tokio::test]
|
||
async fn test_edge_cases() {
|
||
let config = create_test_config();
|
||
let service = PortProxyService::new(config);
|
||
|
||
// 测试添加和移除相同的端口
|
||
service.add_backend(3001, "host1".to_string()).await;
|
||
service.add_backend(3001, "host2".to_string()).await; // 覆盖
|
||
|
||
let backend = service.get_target_backend(3001).await.unwrap();
|
||
assert_eq!(backend, "host2"); // 应该是最后的值
|
||
|
||
// 测试移除不存在的端口
|
||
service.remove_backend(9999).await; // 不应该崩溃
|
||
|
||
// 测试获取不存在的后端
|
||
let result = service.get_target_backend(9999).await;
|
||
assert!(result.is_err());
|
||
|
||
// 测试空配置
|
||
let empty_config = ProxyConfig {
|
||
listen_port: 8080,
|
||
default_backend_port: 0, // 无效端口
|
||
backend_host: "127.0.0.1".to_string(),
|
||
port_param: "port".to_string(),
|
||
config_file: None,
|
||
verbose: false,
|
||
};
|
||
assert!(empty_config.validate().is_err());
|
||
}
|
||
|
||
#[test]
|
||
fn test_config_default_methods() {
|
||
// 测试默认配置
|
||
let default_config = ProxyConfig::default();
|
||
assert_eq!(default_config.listen_port, 8080);
|
||
assert_eq!(default_config.default_backend_port, 3000);
|
||
assert_eq!(default_config.backend_host, "127.0.0.1");
|
||
assert_eq!(default_config.port_param, "port");
|
||
assert!(default_config.config_file.is_none());
|
||
assert!(!default_config.verbose);
|
||
|
||
// 测试便捷方法
|
||
let config = ProxyConfig::new();
|
||
assert_eq!(config.listen_port, 8080);
|
||
|
||
let config = ProxyConfig::with_listen_port(9090);
|
||
assert_eq!(config.listen_port, 9090);
|
||
assert_eq!(config.default_backend_port, 3000); // 其他应该保持默认值
|
||
|
||
let config = ProxyConfig::with_listen_port(8080)
|
||
.with_backend_host("example.com")
|
||
.with_port_param("service_port");
|
||
assert_eq!(config.listen_port, 8080);
|
||
assert_eq!(config.backend_host, "example.com");
|
||
assert_eq!(config.port_param, "service_port");
|
||
}
|
||
|
||
// 测试模块集成
|
||
#[test]
|
||
fn test_module_integration() {
|
||
// 验证各模块之间的集成是否正常
|
||
let config = ProxyConfig::default();
|
||
let service = PortProxyService::new(config.clone());
|
||
let server = ProxyServer::new(config);
|
||
|
||
// 验证配置一致性
|
||
assert_eq!(service.config().default_backend_port, server.default_backend_port());
|
||
assert_eq!(service.config().port_param, server.config().port_param);
|
||
|
||
// 验证服务访问
|
||
let server_service = server.service();
|
||
let service_backends = service.backends();
|
||
|
||
// 两个服务实例应该是独立的
|
||
assert_ne!(service.config() as *const _, server_service.config() as *const _);
|
||
} |