#[cfg(test)]
mod loader_tests {
use crate::FoxyLoader;
use crate::config::{ConfigError, ConfigProvider};
use serde_json::Value;
use std::collections::HashMap;
#[derive(Debug)]
struct MockConfigProvider {
values: HashMap<String, Value>,
}
impl MockConfigProvider {
fn new() -> Self {
let mut values = HashMap::new();
values.insert("server.port".to_string(), serde_json::json!(8080));
values.insert("server.host".to_string(), serde_json::json!("127.0.0.1"));
Self { values }
}
}
impl ConfigProvider for MockConfigProvider {
fn has(&self, key: &str) -> bool {
self.values.contains_key(key)
}
fn provider_name(&self) -> &str {
"mock"
}
fn get_raw(&self, key: &str) -> Result<Option<Value>, ConfigError> {
Ok(self.values.get(key).cloned())
}
}
#[tokio::test]
async fn test_loader_with_config_file() {
let provider = MockConfigProvider::new();
let loader = FoxyLoader::new().with_provider(provider);
let foxy = loader.build().await.unwrap();
let config = foxy.config();
assert_eq!(config.get::<u64>("server.port").unwrap().unwrap(), 8080);
assert_eq!(
config.get::<String>("server.host").unwrap().unwrap(),
"127.0.0.1"
);
}
#[tokio::test]
async fn test_loader_with_layered_config() {
let provider1 = MockConfigProvider::new();
let mut provider2_values = HashMap::new();
provider2_values.insert("server.port".to_string(), serde_json::json!(9000));
let provider2 = MockConfigProvider {
values: provider2_values,
};
let config = crate::config::Config::builder()
.with_provider(provider1)
.with_provider(provider2)
.build();
assert_eq!(config.get::<u64>("server.port").unwrap().unwrap(), 9000); assert_eq!(
config.get::<String>("server.host").unwrap().unwrap(),
"127.0.0.1"
); }
#[tokio::test]
async fn test_loader_new() {
let loader = FoxyLoader::new();
assert!(loader.config_builder.is_none());
assert!(loader.config_file_path.is_none());
assert!(!loader.use_env_vars);
assert!(loader.env_prefix.is_none());
assert!(loader.custom_filters.is_empty());
}
#[tokio::test]
async fn test_loader_default() {
let loader = FoxyLoader::default();
assert!(loader.config_builder.is_none());
assert!(loader.config_file_path.is_none());
assert!(!loader.use_env_vars);
assert!(loader.env_prefix.is_none());
assert!(loader.custom_filters.is_empty());
}
#[tokio::test]
async fn test_loader_with_config() {
let config = crate::config::Config::builder().build();
let loader = FoxyLoader::new().with_config(config);
assert!(loader.config_builder.is_some());
}
#[tokio::test]
async fn test_loader_with_config_file_path() {
let loader = FoxyLoader::new().with_config_file("config.json");
assert_eq!(loader.config_file_path, Some("config.json".to_string()));
}
#[tokio::test]
async fn test_loader_with_env_vars() {
let loader = FoxyLoader::new().with_env_vars();
assert!(loader.use_env_vars);
}
#[tokio::test]
async fn test_loader_with_env_prefix() {
let loader = FoxyLoader::new().with_env_prefix("MYAPP_");
assert!(loader.use_env_vars);
assert_eq!(loader.env_prefix, Some("MYAPP_".to_string()));
}
#[tokio::test]
async fn test_loader_with_provider() {
let provider = MockConfigProvider::new();
let loader = FoxyLoader::new().with_provider(provider);
assert!(loader.config_builder.is_some());
}
#[tokio::test]
async fn test_loader_with_provider_existing_config() {
let config = crate::config::Config::builder().build();
let provider = MockConfigProvider::new();
let loader = FoxyLoader::new()
.with_config(config)
.with_provider(provider);
assert!(loader.config_builder.is_some());
}
#[derive(Debug)]
struct MockFilter {
name: String,
}
impl MockFilter {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
}
}
}
#[async_trait::async_trait]
impl crate::Filter for MockFilter {
fn name(&self) -> &str {
&self.name
}
fn filter_type(&self) -> crate::FilterType {
crate::FilterType::Pre
}
async fn pre_filter(
&self,
request: crate::ProxyRequest,
) -> Result<crate::ProxyRequest, crate::ProxyError> {
Ok(request)
}
async fn post_filter(
&self,
_request: crate::ProxyRequest,
response: crate::ProxyResponse,
) -> Result<crate::ProxyResponse, crate::ProxyError> {
Ok(response)
}
}
#[tokio::test]
async fn test_loader_with_filter() {
let filter = MockFilter::new("test_filter");
let loader = FoxyLoader::new().with_filter(filter);
assert_eq!(loader.custom_filters.len(), 1);
}
#[tokio::test]
async fn test_loader_with_multiple_filters() {
let filter1 = MockFilter::new("filter1");
let filter2 = MockFilter::new("filter2");
let loader = FoxyLoader::new().with_filter(filter1).with_filter(filter2);
assert_eq!(loader.custom_filters.len(), 2);
}
#[tokio::test]
async fn test_loader_build_with_env_vars() {
unsafe {
std::env::set_var("FOXY_SERVER_HOST", "localhost");
std::env::set_var("FOXY_SERVER_PORT", "9090");
}
let loader = FoxyLoader::new().with_env_vars();
let foxy = loader.build().await.unwrap();
let config = foxy.config();
let host: String = config
.get("server.host")
.unwrap()
.unwrap_or_else(|| "127.0.0.1".to_string());
let port: u16 = config.get("server.port").unwrap().unwrap_or(8080);
unsafe {
std::env::remove_var("FOXY_SERVER_HOST");
std::env::remove_var("FOXY_SERVER_PORT");
}
assert!(!host.is_empty());
assert!(port > 0);
}
#[tokio::test]
async fn test_loader_build_with_custom_env_prefix() {
unsafe {
std::env::set_var("MYAPP_SERVER_HOST", "custom.example.com");
std::env::set_var("MYAPP_SERVER_PORT", "7777");
}
let loader = FoxyLoader::new().with_env_prefix("MYAPP_");
let foxy = loader.build().await.unwrap();
let config = foxy.config();
let host: Option<String> = config.get("server.host").unwrap();
let port: Option<u16> = config.get("server.port").unwrap();
unsafe {
std::env::remove_var("MYAPP_SERVER_HOST");
std::env::remove_var("MYAPP_SERVER_PORT");
}
if let Some(h) = host {
assert!(!h.is_empty());
}
if let Some(p) = port {
assert!(p > 0);
}
}
#[tokio::test]
async fn test_loader_build_with_invalid_config_file() {
let loader = FoxyLoader::new().with_config_file("/nonexistent/path/config.json");
let result = loader.build().await;
assert!(result.is_err());
match result {
Err(crate::loader::LoaderError::ConfigError(_)) => {
}
other => {
panic!("Expected ConfigError, got: {other:?}");
}
}
}
#[tokio::test]
async fn test_foxy_loader_static_method() {
let loader = crate::Foxy::loader();
assert!(loader.config_builder.is_none());
assert!(loader.config_file_path.is_none());
assert!(!loader.use_env_vars);
assert!(loader.env_prefix.is_none());
assert!(loader.custom_filters.is_empty());
}
#[tokio::test]
async fn test_foxy_config_access() {
let provider = MockConfigProvider::new();
let loader = FoxyLoader::new().with_provider(provider);
let foxy = loader.build().await.unwrap();
let config = foxy.config();
assert_eq!(config.get::<u64>("server.port").unwrap().unwrap(), 8080);
assert_eq!(
config.get::<String>("server.host").unwrap().unwrap(),
"127.0.0.1"
);
}
#[tokio::test]
async fn test_loader_error_display() {
let config_error = crate::config::ConfigError::ParseError("test error".to_string());
let loader_error = crate::loader::LoaderError::ConfigError(config_error);
let error_string = format!("{loader_error}");
assert!(error_string.contains("configuration error"));
assert!(error_string.contains("test error"));
}
#[tokio::test]
async fn test_loader_error_from_proxy_error() {
let proxy_error = crate::ProxyError::ConfigError("proxy config error".to_string());
let loader_error = crate::loader::LoaderError::ProxyError(proxy_error);
let error_string = format!("{loader_error}");
assert!(error_string.contains("proxy error"));
}
#[tokio::test]
async fn test_loader_error_from_io_error() {
let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let loader_error = crate::loader::LoaderError::IoError(io_error);
let error_string = format!("{loader_error}");
assert!(error_string.contains("IO error"));
assert!(error_string.contains("file not found"));
}
#[tokio::test]
async fn test_loader_error_other() {
let loader_error = crate::loader::LoaderError::Other("custom error message".to_string());
let error_string = format!("{loader_error}");
assert_eq!(error_string, "custom error message");
}
#[tokio::test]
async fn test_foxy_debug() {
let provider = MockConfigProvider::new();
let loader = FoxyLoader::new().with_provider(provider);
let foxy = loader.build().await.unwrap();
let debug_string = format!("{foxy:?}");
assert!(debug_string.contains("Foxy"));
}
#[tokio::test]
async fn test_foxy_clone() {
let provider = MockConfigProvider::new();
let loader = FoxyLoader::new().with_provider(provider);
let foxy = loader.build().await.unwrap();
let cloned_foxy = foxy.clone();
assert_eq!(
foxy.config().get::<u64>("server.port").unwrap().unwrap(),
cloned_foxy
.config()
.get::<u64>("server.port")
.unwrap()
.unwrap()
);
}
#[cfg(feature = "opentelemetry")]
#[tokio::test]
async fn test_loader_build_with_opentelemetry_config() {
use std::collections::HashMap;
let mut values = HashMap::new();
values.insert("server.port".to_string(), serde_json::json!(8080));
values.insert("server.host".to_string(), serde_json::json!("127.0.0.1"));
let otel_config = serde_json::json!({
"endpoint": "http://localhost:4317",
"service_name": "test-service",
"include_headers": true,
"include_bodies": false,
"max_body_size": 1024
});
values.insert("proxy.opentelemetry".to_string(), otel_config);
let provider = MockConfigProvider { values };
let loader = FoxyLoader::new().with_provider(provider);
let foxy = loader.build().await.unwrap();
let config = foxy.config();
let otel_config: Option<crate::opentelemetry::OpenTelemetryConfig> =
config.get("proxy.opentelemetry").unwrap();
assert!(otel_config.is_some());
let otel_config = otel_config.unwrap();
assert_eq!(otel_config.endpoint, "http://localhost:4317");
assert_eq!(otel_config.service_name, "test-service");
}
#[cfg(feature = "opentelemetry")]
#[tokio::test]
async fn test_loader_build_without_opentelemetry_config() {
let provider = MockConfigProvider::new();
let loader = FoxyLoader::new().with_provider(provider);
let foxy = loader.build().await.unwrap();
let config = foxy.config();
let otel_config: Option<crate::opentelemetry::OpenTelemetryConfig> =
config.get("proxy.opentelemetry").unwrap();
assert!(otel_config.is_none());
}
#[cfg(not(feature = "opentelemetry"))]
#[tokio::test]
async fn test_loader_build_opentelemetry_feature_disabled() {
let provider = MockConfigProvider::new();
let loader = FoxyLoader::new().with_provider(provider);
let foxy = loader.build().await.unwrap();
assert!(foxy.config().get::<u64>("server.port").unwrap().unwrap() > 0);
}
#[tokio::test]
async fn test_loader_build_with_global_filters() {
use std::collections::HashMap;
let mut values = HashMap::new();
values.insert("server.port".to_string(), serde_json::json!(8080));
values.insert("server.host".to_string(), serde_json::json!("127.0.0.1"));
let global_filters = serde_json::json!([
{
"type": "logging",
"config": {
"log_request_headers": true,
"log_request_body": false,
"log_response_headers": true,
"log_response_body": false,
"log_level": "debug",
"max_body_size": 1024
}
}
]);
values.insert("proxy.global_filters".to_string(), global_filters);
let provider = MockConfigProvider { values };
let loader = FoxyLoader::new().with_provider(provider);
let foxy = loader.build().await.unwrap();
let config = foxy.config();
let filters_config: Option<Vec<crate::router::FilterConfig>> =
config.get("proxy.global_filters").unwrap();
assert!(filters_config.is_some());
let filters = filters_config.unwrap();
assert_eq!(filters.len(), 1);
assert_eq!(filters[0].type_, "logging");
}
#[tokio::test]
async fn test_loader_build_with_invalid_global_filter() {
use std::collections::HashMap;
let mut values = HashMap::new();
values.insert("server.port".to_string(), serde_json::json!(8080));
values.insert("server.host".to_string(), serde_json::json!("127.0.0.1"));
let global_filters = serde_json::json!([
{
"type": "nonexistent_filter",
"config": {}
}
]);
values.insert("proxy.global_filters".to_string(), global_filters);
let provider = MockConfigProvider { values };
let loader = FoxyLoader::new().with_provider(provider);
let result = loader.build().await;
assert!(result.is_err());
match result {
Err(crate::loader::LoaderError::ProxyError(_)) => {
}
other => {
panic!("Expected ProxyError, got: {other:?}");
}
}
}
#[tokio::test]
async fn test_loader_build_without_global_filters() {
let provider = MockConfigProvider::new();
let loader = FoxyLoader::new().with_provider(provider);
let foxy = loader.build().await.unwrap();
let config = foxy.config();
let filters_config: Option<Vec<crate::router::FilterConfig>> =
config.get("proxy.global_filters").unwrap();
assert!(filters_config.is_none());
}
#[tokio::test]
async fn test_loader_build_rust_log_env_precedence() {
use std::collections::HashMap;
let mut values = HashMap::new();
values.insert("server.port".to_string(), serde_json::json!(8080));
values.insert("server.host".to_string(), serde_json::json!("127.0.0.1"));
let logging_config = serde_json::json!({
"level": "warn",
"format": "terminal",
"structured": false
});
values.insert("proxy.logging".to_string(), logging_config);
unsafe {
std::env::set_var("RUST_LOG", "debug");
}
let provider = MockConfigProvider { values };
let loader = FoxyLoader::new().with_provider(provider);
let foxy = loader.build().await.unwrap();
unsafe {
std::env::remove_var("RUST_LOG");
}
assert!(foxy.config().get::<u64>("server.port").unwrap().unwrap() > 0);
}
#[tokio::test]
async fn test_loader_build_rust_log_env_not_set() {
use std::collections::HashMap;
unsafe {
std::env::remove_var("RUST_LOG");
}
let mut values = HashMap::new();
values.insert("server.port".to_string(), serde_json::json!(8080));
values.insert("server.host".to_string(), serde_json::json!("127.0.0.1"));
let logging_config = serde_json::json!({
"level": "error",
"format": "terminal",
"structured": false
});
values.insert("proxy.logging".to_string(), logging_config);
let provider = MockConfigProvider { values };
let loader = FoxyLoader::new().with_provider(provider);
let foxy = loader.build().await.unwrap();
let config = foxy.config();
let log_config: Option<crate::logging::config::LoggingConfig> =
config.get("proxy.logging").unwrap();
assert!(log_config.is_some());
let log_config = log_config.unwrap();
assert_eq!(log_config.level, "error"); }
#[tokio::test]
async fn test_loader_build_invalid_rust_log_level() {
use std::collections::HashMap;
let mut values = HashMap::new();
values.insert("server.port".to_string(), serde_json::json!(8080));
values.insert("server.host".to_string(), serde_json::json!("127.0.0.1"));
let logging_config = serde_json::json!({
"level": "warn",
"format": "terminal",
"structured": false
});
values.insert("proxy.logging".to_string(), logging_config);
unsafe {
std::env::set_var("RUST_LOG", "invalid_level");
}
let provider = MockConfigProvider { values };
let loader = FoxyLoader::new().with_provider(provider);
let foxy = loader.build().await.unwrap();
unsafe {
std::env::remove_var("RUST_LOG");
}
assert!(foxy.config().get::<u64>("server.port").unwrap().unwrap() > 0);
}
#[tokio::test]
async fn test_foxy_core_access() {
let provider = MockConfigProvider::new();
let loader = FoxyLoader::new().with_provider(provider);
let foxy = loader.build().await.unwrap();
let core = foxy.core();
assert!(core.config.get::<u64>("server.port").unwrap().unwrap() > 0);
}
#[tokio::test]
async fn test_foxy_core_add_global_filter() {
let provider = MockConfigProvider::new();
let loader = FoxyLoader::new().with_provider(provider);
let foxy = loader.build().await.unwrap();
let core = foxy.core();
let filter = MockFilter::new("test_core_filter");
core.add_global_filter(std::sync::Arc::new(filter)).await;
}
#[tokio::test]
async fn test_foxy_start_method_exists() {
let provider = MockConfigProvider::new();
let loader = FoxyLoader::new().with_provider(provider);
let foxy = loader.build().await.unwrap();
let server_config = foxy.server.config.clone();
assert_eq!(server_config.host, "127.0.0.1");
assert_eq!(server_config.port, 8080);
let core = foxy.server.core();
assert!(core.config.get::<u64>("server.port").unwrap().unwrap() > 0);
}
#[tokio::test]
async fn test_foxy_start_error_mapping() {
let proxy_error = crate::ProxyError::ConfigError("test error".to_string());
let loader_error = crate::loader::LoaderError::ProxyError(proxy_error);
let error_string = format!("{loader_error}");
assert!(error_string.contains("proxy error"));
assert!(error_string.contains("test error"));
}
#[tokio::test]
async fn test_loader_build_missing_logging_config() {
let provider = MockConfigProvider::new();
let loader = FoxyLoader::new().with_provider(provider);
let foxy = loader.build().await.unwrap();
let config = foxy.config();
let log_config: Option<crate::logging::config::LoggingConfig> =
config.get("proxy.logging").unwrap();
assert!(log_config.is_none()); }
#[tokio::test]
async fn test_loader_build_invalid_logging_config_level() {
use std::collections::HashMap;
let mut values = HashMap::new();
values.insert("server.port".to_string(), serde_json::json!(8080));
values.insert("server.host".to_string(), serde_json::json!("127.0.0.1"));
let logging_config = serde_json::json!({
"level": "invalid_log_level",
"format": "terminal",
"structured": false
});
values.insert("proxy.logging".to_string(), logging_config);
let provider = MockConfigProvider { values };
let loader = FoxyLoader::new().with_provider(provider);
let foxy = loader.build().await.unwrap();
let config = foxy.config();
let log_config: Option<crate::logging::config::LoggingConfig> =
config.get("proxy.logging").unwrap();
assert!(log_config.is_some());
let log_config = log_config.unwrap();
assert_eq!(log_config.level, "invalid_log_level"); }
#[tokio::test]
async fn test_loader_build_malformed_logging_config() {
use std::collections::HashMap;
let mut values = HashMap::new();
values.insert("server.port".to_string(), serde_json::json!(8080));
values.insert("server.host".to_string(), serde_json::json!("127.0.0.1"));
values.insert(
"proxy.logging".to_string(),
serde_json::json!("invalid_config"),
);
let provider = MockConfigProvider { values };
let loader = FoxyLoader::new().with_provider(provider);
let result = loader.build().await;
match result {
Ok(foxy) => {
assert!(foxy.config().get::<u64>("server.port").unwrap().unwrap() > 0);
}
Err(e) => {
match e {
crate::loader::LoaderError::ConfigError(_) => {
}
other => {
panic!("Expected ConfigError or success, got: {other:?}");
}
}
}
}
}
#[tokio::test]
async fn test_loader_build_partial_logging_config() {
use std::collections::HashMap;
let mut values = HashMap::new();
values.insert("server.port".to_string(), serde_json::json!(8080));
values.insert("server.host".to_string(), serde_json::json!("127.0.0.1"));
let logging_config = serde_json::json!({
"level": "trace"
});
values.insert("proxy.logging".to_string(), logging_config);
let provider = MockConfigProvider { values };
let loader = FoxyLoader::new().with_provider(provider);
let foxy = loader.build().await.unwrap();
let config = foxy.config();
let log_config: Option<crate::logging::config::LoggingConfig> =
config.get("proxy.logging").unwrap();
assert!(log_config.is_some());
let log_config = log_config.unwrap();
assert_eq!(log_config.level, "trace"); assert_eq!(log_config.format, "terminal"); assert!(!log_config.structured); }
}