#[cfg(test)]
mod tests {
use crate::{
HttpMethod, ProxyRequest,
PathPredicate, MethodPredicate, HeaderPredicate, QueryPredicate
};
use crate::router::predicates::{
PathPredicateConfig, MethodPredicateConfig, HeaderPredicateConfig, QueryPredicateConfig
};
use crate::router::Predicate;
use crate::core::RequestContext;
use reqwest::Body;
use std::sync::Arc;
use tokio::sync::RwLock;
use std::collections::HashMap;
fn create_test_request(method: HttpMethod, path: &str, query: Option<&str>, headers: Vec<(&'static str, &'static str)>, target: &str) -> ProxyRequest {
let mut header_map = reqwest::header::HeaderMap::new();
for (name, value) in headers {
header_map.insert(
reqwest::header::HeaderName::from_static(name),
reqwest::header::HeaderValue::from_static(value),
);
}
ProxyRequest {
method,
path: path.to_string(),
query: query.map(|q| q.to_string()),
headers: header_map,
body: Body::from(Vec::new()),
context: Arc::new(RwLock::new(RequestContext::default())),
custom_target: Some(target.to_string()),
}
}
#[tokio::test]
async fn test_path_predicate() {
let config = PathPredicateConfig {
pattern: "/api/*".to_string(),
};
let predicate = PathPredicate::new(config).unwrap();
let request = create_test_request(HttpMethod::Get, "/api/users", None, vec![], "http://test.co.za");
assert!(predicate.matches(&request).await);
let request = create_test_request(HttpMethod::Get, "/api/products", None, vec![], "http://test.co.za");
assert!(predicate.matches(&request).await);
let request = create_test_request(HttpMethod::Get, "/users", None, vec![], "http://test.co.za");
assert!(!predicate.matches(&request).await);
let request = create_test_request(HttpMethod::Get, "/api", None, vec![], "http://test.co.za");
assert!(!predicate.matches(&request).await);
}
#[tokio::test]
async fn test_method_predicate() {
let config = MethodPredicateConfig {
methods: vec![HttpMethod::Get, HttpMethod::Post],
};
let predicate = MethodPredicate::new(config);
let request = create_test_request(HttpMethod::Get, "/api", None, vec![], "http://test.co.za");
assert!(predicate.matches(&request).await);
let request = create_test_request(HttpMethod::Post, "/api", None, vec![], "http://test.co.za");
assert!(predicate.matches(&request).await);
let request = create_test_request(HttpMethod::Put, "/api", None, vec![], "http://test.co.za");
assert!(!predicate.matches(&request).await);
let request = create_test_request(HttpMethod::Delete, "/api", None, vec![], "http://test.co.za");
assert!(!predicate.matches(&request).await);
}
#[tokio::test]
async fn test_header_predicate() {
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "application/json".to_string());
let config = HeaderPredicateConfig {
headers,
exact_match: true,
};
let predicate = HeaderPredicate::new(config);
let request = create_test_request(
HttpMethod::Get,
"/api",
None,
vec![("content-type", "application/json")],
"http://test.co.za",
);
assert!(predicate.matches(&request).await);
let request = create_test_request(
HttpMethod::Get,
"/api",
None,
vec![("content-type", "text/plain")],
"http://test.co.za",
);
assert!(!predicate.matches(&request).await);
let request = create_test_request(HttpMethod::Get, "/api", None, vec![], "http://test.co.za");
assert!(!predicate.matches(&request).await);
}
#[tokio::test]
async fn test_query_predicate() {
let mut params = HashMap::new();
params.insert("version".to_string(), "v1".to_string());
let config = QueryPredicateConfig {
params,
exact_match: true,
};
let predicate = QueryPredicate::new(config);
let request = create_test_request(HttpMethod::Get, "/api", Some("version=v1"), vec![], "http://test.co.za");
assert!(predicate.matches(&request).await);
let request = create_test_request(HttpMethod::Get, "/api", Some("version=v2"), vec![], "http://test.co.za");
assert!(!predicate.matches(&request).await);
let request = create_test_request(HttpMethod::Get, "/api", Some("other=value"), vec![], "http://test.co.za");
assert!(!predicate.matches(&request).await);
let request = create_test_request(HttpMethod::Get, "/api", None, vec![],"http://test.co.za");
assert!(!predicate.matches(&request).await);
}
#[tokio::test]
async fn test_predicate_router_new() {
use crate::config::{Config, ConfigProvider};
use crate::router::PredicateRouter;
use std::sync::Arc;
#[derive(Debug)]
struct MockConfigProvider;
impl ConfigProvider for MockConfigProvider {
fn has(&self, _key: &str) -> bool {
false
}
fn provider_name(&self) -> &str {
"mock"
}
fn get_raw(&self, _key: &str) -> Result<Option<serde_json::Value>, crate::config::ConfigError> {
Ok(None)
}
}
let config = Arc::new(Config::builder().with_provider(MockConfigProvider).build());
let router = PredicateRouter::new(config).await;
assert!(router.is_ok());
}
#[tokio::test]
async fn test_predicate_router_add_route() {
use crate::config::{Config, ConfigProvider};
use crate::router::PredicateRouter;
use crate::core::{Router, Route};
use std::sync::Arc;
#[derive(Debug)]
struct MockConfigProvider;
impl ConfigProvider for MockConfigProvider {
fn has(&self, _key: &str) -> bool {
false
}
fn provider_name(&self) -> &str {
"mock"
}
fn get_raw(&self, _key: &str) -> Result<Option<serde_json::Value>, crate::config::ConfigError> {
Ok(None)
}
}
let config = Arc::new(Config::builder().with_provider(MockConfigProvider).build());
let router = PredicateRouter::new(config).await.unwrap();
let route = Route {
id: "test-route".to_string(),
target_base_url: "http://example.com".to_string(),
path_pattern: "/api/*".to_string(),
filters: None,
};
let result = router.add_route(route.clone()).await;
assert!(result.is_ok());
let routes = router.get_routes().await;
assert_eq!(routes.len(), 1);
assert_eq!(routes[0].id, "test-route");
}
#[tokio::test]
async fn test_predicate_router_remove_route() {
use crate::config::{Config, ConfigProvider};
use crate::router::PredicateRouter;
use crate::core::{Router, Route};
use std::sync::Arc;
#[derive(Debug)]
struct MockConfigProvider;
impl ConfigProvider for MockConfigProvider {
fn has(&self, _key: &str) -> bool {
false
}
fn provider_name(&self) -> &str {
"mock"
}
fn get_raw(&self, _key: &str) -> Result<Option<serde_json::Value>, crate::config::ConfigError> {
Ok(None)
}
}
let config = Arc::new(Config::builder().with_provider(MockConfigProvider).build());
let router = PredicateRouter::new(config).await.unwrap();
let route = Route {
id: "test-route".to_string(),
target_base_url: "http://example.com".to_string(),
path_pattern: "/api/*".to_string(),
filters: None,
};
router.add_route(route).await.unwrap();
assert_eq!(router.get_routes().await.len(), 1);
let result = router.remove_route("test-route").await;
assert!(result.is_ok());
assert_eq!(router.get_routes().await.len(), 0);
let result = router.remove_route("non-existent").await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_predicate_router_route_no_match() {
use crate::config::{Config, ConfigProvider};
use crate::router::PredicateRouter;
use crate::core::Router;
use std::sync::Arc;
#[derive(Debug)]
struct MockConfigProvider;
impl ConfigProvider for MockConfigProvider {
fn has(&self, _key: &str) -> bool {
false
}
fn provider_name(&self) -> &str {
"mock"
}
fn get_raw(&self, _key: &str) -> Result<Option<serde_json::Value>, crate::config::ConfigError> {
Ok(None)
}
}
let config = Arc::new(Config::builder().with_provider(MockConfigProvider).build());
let router = PredicateRouter::new(config).await.unwrap();
let request = create_test_request(HttpMethod::Get, "/api/users", None, vec![], "http://test.co.za");
let result = router.route(&request).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_predicate_factory_create_path_predicate() {
use crate::router::PredicateFactory;
use serde_json::json;
let config = json!({
"pattern": "/api/*"
});
let predicate = PredicateFactory::create_predicate("path", config).unwrap();
assert_eq!(predicate.predicate_type(), "path");
let request = create_test_request(HttpMethod::Get, "/api/users", None, vec![], "http://test.co.za");
assert!(predicate.matches(&request).await);
}
#[tokio::test]
async fn test_predicate_factory_create_method_predicate() {
use crate::router::PredicateFactory;
use serde_json::json;
let config = json!({
"methods": ["GET", "POST"]
});
let predicate = PredicateFactory::create_predicate("method", config).unwrap();
assert_eq!(predicate.predicate_type(), "method");
let request = create_test_request(HttpMethod::Get, "/api", None, vec![], "http://test.co.za");
assert!(predicate.matches(&request).await);
}
#[tokio::test]
async fn test_predicate_factory_create_header_predicate() {
use crate::router::PredicateFactory;
use serde_json::json;
let config = json!({
"headers": {
"content-type": "application/json"
},
"exact_match": true
});
let predicate = PredicateFactory::create_predicate("header", config).unwrap();
assert_eq!(predicate.predicate_type(), "header");
let request = create_test_request(
HttpMethod::Get,
"/api",
None,
vec![("content-type", "application/json")],
"http://test.co.za",
);
assert!(predicate.matches(&request).await);
}
#[tokio::test]
async fn test_predicate_factory_create_query_predicate() {
use crate::router::PredicateFactory;
use serde_json::json;
let config = json!({
"params": {
"version": "v1"
},
"exact_match": true
});
let predicate = PredicateFactory::create_predicate("query", config).unwrap();
assert_eq!(predicate.predicate_type(), "query");
let request = create_test_request(HttpMethod::Get, "/api", Some("version=v1"), vec![], "http://test.co.za");
assert!(predicate.matches(&request).await);
}
#[tokio::test]
async fn test_predicate_factory_unknown_predicate() {
use crate::router::PredicateFactory;
use serde_json::json;
let config = json!({});
let result = PredicateFactory::create_predicate("unknown", config);
assert!(result.is_err());
}
#[tokio::test]
async fn test_predicate_factory_invalid_config() {
use crate::router::PredicateFactory;
use serde_json::json;
let config = json!({
"invalid_field": "value"
});
let result = PredicateFactory::create_predicate("path", config);
assert!(result.is_err());
}
#[tokio::test]
async fn test_path_predicate_invalid_regex() {
let config = PathPredicateConfig {
pattern: "/api/*".to_string(), };
let result = PathPredicate::new(config);
assert!(result.is_ok());
let config = PathPredicateConfig {
pattern: "".to_string(),
};
let result = PathPredicate::new(config);
assert!(result.is_ok()); }
#[tokio::test]
async fn test_path_predicate_complex_patterns() {
let config = PathPredicateConfig {
pattern: "/api/users".to_string(),
};
let predicate = PathPredicate::new(config).unwrap();
let request = create_test_request(HttpMethod::Get, "/api/users", None, vec![], "http://test.co.za");
assert!(predicate.matches(&request).await);
let request = create_test_request(HttpMethod::Get, "/api/users/123", None, vec![], "http://test.co.za");
assert!(!predicate.matches(&request).await);
let config = PathPredicateConfig {
pattern: "/api/*/details".to_string(),
};
let predicate = PathPredicate::new(config).unwrap();
let request = create_test_request(HttpMethod::Get, "/api/users/details", None, vec![], "http://test.co.za");
assert!(predicate.matches(&request).await);
let request = create_test_request(HttpMethod::Get, "/api/products/details", None, vec![], "http://test.co.za");
assert!(predicate.matches(&request).await);
}
#[tokio::test]
async fn test_header_predicate_contains_match() {
let mut headers = HashMap::new();
headers.insert("user-agent".to_string(), "Mozilla".to_string());
let config = HeaderPredicateConfig {
headers,
exact_match: false, };
let predicate = HeaderPredicate::new(config);
let request = create_test_request(
HttpMethod::Get,
"/api",
None,
vec![("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")],
"http://test.co.za",
);
assert!(predicate.matches(&request).await);
let request = create_test_request(
HttpMethod::Get,
"/api",
None,
vec![("user-agent", "Chrome/91.0")],
"http://test.co.za",
);
assert!(!predicate.matches(&request).await);
}
#[tokio::test]
async fn test_header_predicate_multiple_headers() {
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "application/json".to_string());
headers.insert("authorization".to_string(), "Bearer".to_string());
let config = HeaderPredicateConfig {
headers,
exact_match: false,
};
let predicate = HeaderPredicate::new(config);
let request = create_test_request(
HttpMethod::Get,
"/api",
None,
vec![
("content-type", "application/json"),
("authorization", "Bearer token123"),
],
"http://test.co.za",
);
assert!(predicate.matches(&request).await);
let request = create_test_request(
HttpMethod::Get,
"/api",
None,
vec![("content-type", "application/json")],
"http://test.co.za",
);
assert!(!predicate.matches(&request).await);
}
#[tokio::test]
async fn test_query_predicate_contains_match() {
let mut params = HashMap::new();
params.insert("search".to_string(), "user".to_string());
let config = QueryPredicateConfig {
params,
exact_match: false, };
let predicate = QueryPredicate::new(config);
let request = create_test_request(HttpMethod::Get, "/api", Some("search=username"), vec![], "http://test.co.za");
assert!(predicate.matches(&request).await);
let request = create_test_request(HttpMethod::Get, "/api", Some("search=product"), vec![], "http://test.co.za");
assert!(!predicate.matches(&request).await);
}
#[tokio::test]
async fn test_query_predicate_multiple_params() {
let mut params = HashMap::new();
params.insert("version".to_string(), "v1".to_string());
params.insert("format".to_string(), "json".to_string());
let config = QueryPredicateConfig {
params,
exact_match: true,
};
let predicate = QueryPredicate::new(config);
let request = create_test_request(HttpMethod::Get, "/api", Some("version=v1&format=json"), vec![], "http://test.co.za");
assert!(predicate.matches(&request).await);
let request = create_test_request(HttpMethod::Get, "/api", Some("version=v1"), vec![], "http://test.co.za");
assert!(!predicate.matches(&request).await);
let request = create_test_request(HttpMethod::Get, "/api", Some("version=v2&format=json"), vec![], "http://test.co.za");
assert!(!predicate.matches(&request).await);
}
#[tokio::test]
async fn test_query_predicate_empty_params() {
let config = QueryPredicateConfig {
params: HashMap::new(),
exact_match: true,
};
let predicate = QueryPredicate::new(config);
let request = create_test_request(HttpMethod::Get, "/api", None, vec![], "http://test.co.za");
assert!(predicate.matches(&request).await);
let request = create_test_request(HttpMethod::Get, "/api", Some("any=value"), vec![], "http://test.co.za");
assert!(predicate.matches(&request).await);
}
#[tokio::test]
async fn test_register_predicate() {
use crate::router::{register_predicate, PredicateFactory};
use crate::core::ProxyRequest;
use serde_json::json;
use std::sync::Arc;
#[derive(Debug)]
struct CustomPredicate;
#[async_trait::async_trait]
impl crate::router::Predicate for CustomPredicate {
async fn matches(&self, _request: &ProxyRequest) -> bool {
true }
fn predicate_type(&self) -> &str {
"custom"
}
}
register_predicate("custom_test", |_config| {
Ok(Arc::new(CustomPredicate))
});
let config = json!({});
let predicate = PredicateFactory::create_predicate("custom_test", config).unwrap();
assert_eq!(predicate.predicate_type(), "custom");
let request = create_test_request(HttpMethod::Get, "/test", None, vec![], "http://test.co.za");
assert!(predicate.matches(&request).await);
}
#[tokio::test]
async fn test_method_predicate_empty_methods() {
let config = MethodPredicateConfig {
methods: vec![],
};
let predicate = MethodPredicate::new(config);
let request = create_test_request(HttpMethod::Get, "/api", None, vec![], "http://test.co.za");
assert!(!predicate.matches(&request).await);
}
#[tokio::test]
async fn test_method_predicate_all_methods() {
let config = MethodPredicateConfig {
methods: vec![
HttpMethod::Get,
HttpMethod::Post,
HttpMethod::Put,
HttpMethod::Delete,
HttpMethod::Patch,
HttpMethod::Head,
HttpMethod::Options,
],
};
let predicate = MethodPredicate::new(config);
for method in &[
HttpMethod::Get,
HttpMethod::Post,
HttpMethod::Put,
HttpMethod::Delete,
HttpMethod::Patch,
HttpMethod::Head,
HttpMethod::Options,
] {
let request = create_test_request(method.clone(), "/api", None, vec![], "http://test.co.za");
assert!(predicate.matches(&request).await);
}
}
#[tokio::test]
async fn test_header_predicate_invalid_utf8() {
let mut headers = HashMap::new();
headers.insert("custom-header".to_string(), "test".to_string());
let config = HeaderPredicateConfig {
headers,
exact_match: true,
};
let predicate = HeaderPredicate::new(config);
let mut header_map = reqwest::header::HeaderMap::new();
header_map.insert(
"custom-header",
reqwest::header::HeaderValue::from_bytes(&[0xFF, 0xFE]).unwrap(),
);
let request = ProxyRequest {
method: HttpMethod::Get,
path: "/api".to_string(),
query: None,
headers: header_map,
body: reqwest::Body::from(Vec::new()),
context: Arc::new(RwLock::new(RequestContext::default())),
custom_target: Some("http://test.co.za".to_string()),
};
assert!(!predicate.matches(&request).await);
}
}