251 lines
7.6 KiB
Rust
251 lines
7.6 KiB
Rust
mod common;
|
|
|
|
use common::*;
|
|
use rustproxy::RustProxy;
|
|
use rustproxy_config::RustProxyOptions;
|
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
|
|
#[tokio::test]
|
|
async fn test_start_and_stop() {
|
|
let port = next_port();
|
|
|
|
let options = RustProxyOptions {
|
|
routes: vec![make_test_route(port, None, "127.0.0.1", 8080)],
|
|
..Default::default()
|
|
};
|
|
|
|
let mut proxy = RustProxy::new(options).unwrap();
|
|
|
|
// Not listening before start
|
|
assert!(!wait_for_port(port, 200).await);
|
|
|
|
proxy.start().await.unwrap();
|
|
assert!(wait_for_port(port, 2000).await, "Port should be listening after start");
|
|
|
|
proxy.stop().await.unwrap();
|
|
|
|
// Give the OS a moment to release the port
|
|
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
|
assert!(!wait_for_port(port, 200).await, "Port should not be listening after stop");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_double_start_fails() {
|
|
let port = next_port();
|
|
|
|
let options = RustProxyOptions {
|
|
routes: vec![make_test_route(port, None, "127.0.0.1", 8080)],
|
|
..Default::default()
|
|
};
|
|
|
|
let mut proxy = RustProxy::new(options).unwrap();
|
|
proxy.start().await.unwrap();
|
|
|
|
// Second start should fail
|
|
let result = proxy.start().await;
|
|
assert!(result.is_err());
|
|
assert!(result.unwrap_err().to_string().contains("already started"));
|
|
|
|
proxy.stop().await.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_update_routes_hot_reload() {
|
|
let port = next_port();
|
|
|
|
let options = RustProxyOptions {
|
|
routes: vec![make_test_route(port, Some("old.example.com"), "127.0.0.1", 8080)],
|
|
..Default::default()
|
|
};
|
|
|
|
let mut proxy = RustProxy::new(options).unwrap();
|
|
proxy.start().await.unwrap();
|
|
|
|
// Update routes atomically
|
|
let new_routes = vec![
|
|
make_test_route(port, Some("new.example.com"), "127.0.0.1", 9090),
|
|
];
|
|
let result = proxy.update_routes(new_routes).await;
|
|
assert!(result.is_ok());
|
|
|
|
proxy.stop().await.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_add_remove_listening_port() {
|
|
let port1 = next_port();
|
|
let port2 = next_port();
|
|
|
|
let options = RustProxyOptions {
|
|
routes: vec![make_test_route(port1, None, "127.0.0.1", 8080)],
|
|
..Default::default()
|
|
};
|
|
|
|
let mut proxy = RustProxy::new(options).unwrap();
|
|
proxy.start().await.unwrap();
|
|
assert!(wait_for_port(port1, 2000).await);
|
|
|
|
// Add a new port
|
|
proxy.add_listening_port(port2).await.unwrap();
|
|
assert!(wait_for_port(port2, 2000).await, "New port should be listening");
|
|
|
|
// Remove the port
|
|
proxy.remove_listening_port(port2).await.unwrap();
|
|
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
|
assert!(!wait_for_port(port2, 200).await, "Removed port should not be listening");
|
|
|
|
// Original port should still be listening
|
|
assert!(wait_for_port(port1, 200).await, "Original port should still be listening");
|
|
|
|
proxy.stop().await.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_get_statistics() {
|
|
let port = next_port();
|
|
|
|
let options = RustProxyOptions {
|
|
routes: vec![make_test_route(port, None, "127.0.0.1", 8080)],
|
|
..Default::default()
|
|
};
|
|
|
|
let mut proxy = RustProxy::new(options).unwrap();
|
|
proxy.start().await.unwrap();
|
|
|
|
let stats = proxy.get_statistics();
|
|
assert_eq!(stats.routes_count, 1);
|
|
assert!(stats.listening_ports.contains(&port));
|
|
|
|
proxy.stop().await.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_invalid_routes_rejected() {
|
|
let options = RustProxyOptions {
|
|
routes: vec![{
|
|
let mut route = make_test_route(80, None, "127.0.0.1", 8080);
|
|
route.action.targets = None; // Invalid: forward without targets
|
|
route
|
|
}],
|
|
..Default::default()
|
|
};
|
|
|
|
let result = RustProxy::new(options);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_metrics_track_connections() {
|
|
let backend_port = next_port();
|
|
let proxy_port = next_port();
|
|
|
|
let _backend = start_echo_server(backend_port).await;
|
|
|
|
let options = RustProxyOptions {
|
|
routes: vec![make_test_route(proxy_port, None, "127.0.0.1", backend_port)],
|
|
..Default::default()
|
|
};
|
|
|
|
let mut proxy = RustProxy::new(options).unwrap();
|
|
proxy.start().await.unwrap();
|
|
assert!(wait_for_port(proxy_port, 2000).await);
|
|
|
|
// No connections yet
|
|
let stats = proxy.get_statistics();
|
|
assert_eq!(stats.total_connections, 0);
|
|
|
|
// Make a connection and send data
|
|
{
|
|
let mut stream = tokio::net::TcpStream::connect(format!("127.0.0.1:{}", proxy_port))
|
|
.await
|
|
.unwrap();
|
|
stream.write_all(b"hello").await.unwrap();
|
|
let mut buf = vec![0u8; 16];
|
|
let _ = stream.read(&mut buf).await;
|
|
}
|
|
|
|
// Small delay for metrics to update
|
|
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
|
|
|
let stats = proxy.get_statistics();
|
|
assert!(stats.total_connections > 0, "Expected total_connections > 0, got {}", stats.total_connections);
|
|
|
|
proxy.stop().await.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_metrics_track_bytes() {
|
|
let backend_port = next_port();
|
|
let proxy_port = next_port();
|
|
|
|
let _backend = start_http_echo_backend(backend_port, "metrics-test").await;
|
|
|
|
let options = RustProxyOptions {
|
|
routes: vec![make_test_route(proxy_port, None, "127.0.0.1", backend_port)],
|
|
..Default::default()
|
|
};
|
|
|
|
let mut proxy = RustProxy::new(options).unwrap();
|
|
proxy.start().await.unwrap();
|
|
assert!(wait_for_port(proxy_port, 2000).await);
|
|
|
|
// Send HTTP request through proxy
|
|
{
|
|
let mut stream = tokio::net::TcpStream::connect(format!("127.0.0.1:{}", proxy_port))
|
|
.await
|
|
.unwrap();
|
|
let request = b"GET /test HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n";
|
|
stream.write_all(request).await.unwrap();
|
|
let mut response = Vec::new();
|
|
stream.read_to_end(&mut response).await.unwrap();
|
|
assert!(!response.is_empty(), "Expected non-empty response");
|
|
}
|
|
|
|
// Small delay for metrics to update
|
|
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
|
|
|
let stats = proxy.get_statistics();
|
|
assert!(stats.total_connections > 0,
|
|
"Expected some connections tracked, got {}", stats.total_connections);
|
|
|
|
proxy.stop().await.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_hot_reload_port_changes() {
|
|
let port1 = next_port();
|
|
let port2 = next_port();
|
|
let backend_port = next_port();
|
|
|
|
let _backend = start_echo_server(backend_port).await;
|
|
|
|
// Start with port1
|
|
let options = RustProxyOptions {
|
|
routes: vec![make_test_route(port1, None, "127.0.0.1", backend_port)],
|
|
..Default::default()
|
|
};
|
|
|
|
let mut proxy = RustProxy::new(options).unwrap();
|
|
proxy.start().await.unwrap();
|
|
assert!(wait_for_port(port1, 2000).await);
|
|
assert!(!wait_for_port(port2, 200).await, "port2 should not be listening yet");
|
|
|
|
// Update routes to use port2 instead
|
|
let new_routes = vec![
|
|
make_test_route(port2, None, "127.0.0.1", backend_port),
|
|
];
|
|
proxy.update_routes(new_routes).await.unwrap();
|
|
|
|
// Port2 should now be listening, port1 should be closed
|
|
assert!(wait_for_port(port2, 2000).await, "port2 should be listening after reload");
|
|
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
|
assert!(!wait_for_port(port1, 200).await, "port1 should be closed after reload");
|
|
|
|
// Verify port2 works
|
|
let ports = proxy.get_listening_ports();
|
|
assert!(ports.contains(&port2), "Expected port2 in listening ports: {:?}", ports);
|
|
assert!(!ports.contains(&port1), "port1 should not be in listening ports: {:?}", ports);
|
|
|
|
proxy.stop().await.unwrap();
|
|
}
|