Files
qiming/qiming-rcoder/crates/rcoder-proxy/src/tests.rs
2026-06-01 13:54:52 +08:00

399 lines
12 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 集成测试模块
//!
//! 提供代理功能的集成测试和端到端测试。
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 _);
}