#[cfg(test)]
mod tests {
use bytes::Bytes;
use futures_util::StreamExt;
use http_body_util::BodyExt;
use crate::{
HttpMethod, ProxyRequest,
LoggingFilter, HeaderFilter, TimeoutFilter,
PathRewriteFilter, PathRewriteFilterConfig
};
use crate::filters::{
LoggingFilterConfig, HeaderFilterConfig, TimeoutFilterConfig
};
use crate::core::{RequestContext, Filter};
use reqwest::Body;
use std::sync::Arc;
use tokio::sync::RwLock;
use std::collections::HashMap;
fn create_test_request(method: HttpMethod, path: &str, headers: Vec<(&'static str, &'static str)>, body: Vec<u8>) -> 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: None,
headers: header_map,
body: Body::from(body),
context: Arc::new(RwLock::new(RequestContext::default())),
}
}
#[tokio::test]
async fn test_logging_filter() {
let request = create_test_request(
HttpMethod::Get,
"/test",
vec![("content-type", "application/json")],
b"{\"test\": \"value\"}".to_vec()
);
let config = LoggingFilterConfig {
log_request_body: true,
log_request_headers: true,
log_response_body: false,
log_response_headers: true,
log_level: "debug".to_string(),
max_body_size: 1000,
};
let filter = LoggingFilter::new(config);
let filtered_request = filter.pre_filter(request).await.unwrap();
assert_eq!(filtered_request.path, "/test");
}
#[tokio::test]
async fn test_header_filter() {
let request = create_test_request(
HttpMethod::Get,
"/test",
vec![
("content-type", "application/json"),
("x-remove-me", "should be removed")
],
Vec::new()
);
let mut add_request_headers = HashMap::new();
add_request_headers.insert("x-custom-header".to_string(), "custom-value".to_string());
let config = HeaderFilterConfig {
add_request_headers,
remove_request_headers: vec!["x-remove-me".to_string()],
add_response_headers: HashMap::new(),
remove_response_headers: Vec::new(),
};
let filter = HeaderFilter::new(config);
let filtered_request = filter.pre_filter(request).await.unwrap();
assert!(filtered_request.headers.contains_key("x-custom-header"));
assert!(!filtered_request.headers.contains_key("x-remove-me"));
let custom_header = filtered_request.headers.get("x-custom-header").unwrap();
assert_eq!(custom_header, "custom-value");
}
#[tokio::test]
async fn test_timeout_filter() {
let request = create_test_request(
HttpMethod::Get,
"/test",
vec![],
Vec::new()
);
let config = TimeoutFilterConfig { timeout_ms: 5000 };
let filter = TimeoutFilter::new(config);
let filtered_request = filter.pre_filter(request).await.unwrap();
let context = filtered_request.context.read().await;
let timeout = context.attributes.get("timeout_ms").unwrap();
assert_eq!(timeout, &serde_json::json!(5000));
}
#[tokio::test]
async fn test_path_rewrite_filter() {
let request = create_test_request(
HttpMethod::Get,
"/api/users",
vec![],
Vec::new()
);
let config = PathRewriteFilterConfig {
pattern: "^/api/(.*)$".to_string(),
replacement: "/v2/$1".to_string(),
rewrite_request: true,
rewrite_response: false,
};
let filter = PathRewriteFilter::new(config);
let filtered_request = filter.pre_filter(request).await.unwrap();
assert_eq!(filtered_request.path, "/v2/users");
}
#[tokio::test]
async fn test_path_rewrite_filter_no_match() {
let request = create_test_request(
HttpMethod::Get,
"/other/path",
vec![],
Vec::new()
);
let config = PathRewriteFilterConfig {
pattern: "^/api/(.*)$".to_string(),
replacement: "/v2/$1".to_string(),
rewrite_request: true,
rewrite_response: false,
};
let filter = PathRewriteFilter::new(config);
let filtered_request = filter.pre_filter(request).await.unwrap();
assert_eq!(filtered_request.path, "/other/path");
}
#[tokio::test]
async fn test_tee_body_streaming() {
use crate::filters::tee_body;
use std::time::Duration;
let chunk1 = Bytes::from(vec![b'a'; 500]);
let chunk2 = Bytes::from(vec![b'b'; 500]);
let chunk3 = Bytes::from(vec![b'c'; 500]);
let stream = futures_util::stream::iter(vec![
Ok::<_, std::io::Error>(chunk1),
Ok(chunk2),
Ok(chunk3),
]);
let body = reqwest::Body::wrap_stream(stream);
let (new_body, snippet) = tee_body(body, 800).await.unwrap();
let mut stream = new_body.into_data_stream();
let mut total_bytes = 0;
while let Some(chunk_result) = stream.next().await {
let chunk = chunk_result.unwrap();
total_bytes += chunk.len();
}
assert_eq!(total_bytes, 1500);
assert_eq!(snippet.len(), 800);
assert_eq!(&snippet[0..500], &"a".repeat(500));
assert_eq!(&snippet[500..800], &"b".repeat(300));
}
}