#[cfg(test)]
mod tests {
use crate::config::Config; use once_cell::sync::Lazy;
use std::env;
use std::sync::Mutex;
static ENV_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
fn clear_test_env_vars() {
env::remove_var("MAIL_LASER_TARGET_EMAILS");
env::remove_var("MAIL_LASER_WEBHOOK_URL");
env::remove_var("MAIL_LASER_BIND_ADDRESS");
env::remove_var("MAIL_LASER_PORT");
env::remove_var("MAIL_LASER_HEALTH_BIND_ADDRESS");
env::remove_var("MAIL_LASER_HEALTH_PORT");
env::remove_var("MAIL_LASER_HEADER_PREFIX");
env::remove_var("MAIL_LASER_WEBHOOK_TIMEOUT");
env::remove_var("MAIL_LASER_WEBHOOK_MAX_RETRIES");
env::remove_var("MAIL_LASER_CIRCUIT_BREAKER_THRESHOLD");
env::remove_var("MAIL_LASER_CIRCUIT_BREAKER_RESET");
}
#[tokio::test]
async fn test_config_from_env_all_set() {
let _lock = ENV_LOCK.lock().unwrap(); clear_test_env_vars();
env::set_var(
"MAIL_LASER_TARGET_EMAILS",
"test1@example.com, test2@example.com",
); env::set_var(
"MAIL_LASER_WEBHOOK_URL",
"https://webhook.example.com/endpoint",
);
env::set_var("MAIL_LASER_BIND_ADDRESS", "127.0.0.1");
env::set_var("MAIL_LASER_PORT", "3000"); env::set_var("MAIL_LASER_HEALTH_BIND_ADDRESS", "192.168.1.1"); env::set_var("MAIL_LASER_HEALTH_PORT", "9090"); env::set_var("MAIL_LASER_HEADER_PREFIX", "X-Custom, X-My-App");
env::set_var("MAIL_LASER_WEBHOOK_TIMEOUT", "15");
env::set_var("MAIL_LASER_WEBHOOK_MAX_RETRIES", "5");
env::set_var("MAIL_LASER_CIRCUIT_BREAKER_THRESHOLD", "10");
env::set_var("MAIL_LASER_CIRCUIT_BREAKER_RESET", "120");
let config = Config::from_env().expect("Config loading failed when all vars were set");
assert_eq!(
config.target_emails,
vec![
"test1@example.com".to_string(),
"test2@example.com".to_string()
]
);
assert_eq!(config.webhook_url, "https://webhook.example.com/endpoint");
assert_eq!(config.smtp_bind_address, "127.0.0.1");
assert_eq!(config.smtp_port, 3000);
assert_eq!(config.health_check_bind_address, "192.168.1.1");
assert_eq!(config.health_check_port, 9090);
assert_eq!(
config.header_prefixes,
vec!["X-Custom".to_string(), "X-My-App".to_string()]
);
assert_eq!(config.webhook_timeout_secs, 15);
assert_eq!(config.webhook_max_retries, 5);
assert_eq!(config.circuit_breaker_threshold, 10);
assert_eq!(config.circuit_breaker_reset_secs, 120);
}
#[tokio::test]
async fn test_config_default_values() {
let _lock = ENV_LOCK.lock().unwrap(); clear_test_env_vars();
env::set_var("MAIL_LASER_TARGET_EMAILS", "required@example.com"); env::set_var(
"MAIL_LASER_WEBHOOK_URL",
"https://required.example.com/hook",
);
let config = Config::from_env().expect("Config loading failed with only required vars set");
assert_eq!(
config.target_emails,
vec!["required@example.com".to_string()]
); assert_eq!(config.webhook_url, "https://required.example.com/hook");
assert_eq!(
config.smtp_bind_address, "0.0.0.0",
"Default SMTP bind address mismatch"
);
assert_eq!(config.smtp_port, 2525, "Default SMTP port mismatch"); assert_eq!(
config.health_check_bind_address, "0.0.0.0",
"Default health bind address mismatch"
);
assert_eq!(
config.health_check_port, 8080,
"Default health port mismatch"
);
assert!(
config.header_prefixes.is_empty(),
"Default header_prefixes should be empty"
);
assert_eq!(
config.webhook_timeout_secs, 30,
"Default webhook timeout mismatch"
);
assert_eq!(
config.webhook_max_retries, 3,
"Default webhook max retries mismatch"
);
assert_eq!(
config.circuit_breaker_threshold, 5,
"Default circuit breaker threshold mismatch"
);
assert_eq!(
config.circuit_breaker_reset_secs, 60,
"Default circuit breaker reset mismatch"
);
}
#[tokio::test]
async fn test_config_missing_required_vars() {
let _lock = ENV_LOCK.lock().unwrap(); clear_test_env_vars();
let result_missing_target = Config::from_env();
assert!(
result_missing_target.is_err(),
"Expected error when TARGET_EMAILS is missing"
);
assert!(
result_missing_target
.unwrap_err()
.to_string()
.contains("MAIL_LASER_TARGET_EMAILS"),
"Error message should mention TARGET_EMAILS"
);
env::set_var("MAIL_LASER_TARGET_EMAILS", "test@example.com");
let result_missing_webhook = Config::from_env();
assert!(
result_missing_webhook.is_err(),
"Expected error when WEBHOOK_URL is missing"
);
assert!(
result_missing_webhook
.unwrap_err()
.to_string()
.contains("MAIL_LASER_WEBHOOK_URL"),
"Error message should mention WEBHOOK_URL"
);
}
#[tokio::test]
async fn test_config_invalid_port_values() {
let _lock = ENV_LOCK.lock().unwrap(); clear_test_env_vars();
env::set_var("MAIL_LASER_TARGET_EMAILS", "test@example.com");
env::set_var("MAIL_LASER_WEBHOOK_URL", "https://webhook.example.com");
env::set_var("MAIL_LASER_PORT", "not-a-number");
let result_invalid_smtp = Config::from_env();
assert!(
result_invalid_smtp.is_err(),
"Expected error for invalid SMTP port"
);
assert!(
result_invalid_smtp
.unwrap_err()
.to_string()
.contains("MAIL_LASER_PORT"),
"Error message should mention MAIL_LASER_PORT"
);
env::set_var("MAIL_LASER_PORT", "2525");
env::set_var("MAIL_LASER_HEALTH_PORT", "also-invalid");
let result_invalid_health = Config::from_env();
assert!(
result_invalid_health.is_err(),
"Expected error for invalid health port"
);
assert!(
result_invalid_health
.unwrap_err()
.to_string()
.contains("MAIL_LASER_HEALTH_PORT"),
"Error message should mention MAIL_LASER_HEALTH_PORT"
);
}
#[tokio::test]
async fn test_config_target_emails_parsing() {
let _lock = ENV_LOCK.lock().unwrap(); clear_test_env_vars();
env::set_var("MAIL_LASER_WEBHOOK_URL", "https://webhook.example.com");
env::set_var("MAIL_LASER_TARGET_EMAILS", "single@example.com");
let config1 = Config::from_env().expect("Config loading failed for single email");
assert_eq!(
config1.target_emails,
vec!["single@example.com".to_string()]
);
env::set_var(
"MAIL_LASER_TARGET_EMAILS",
" spaced1@example.com , spaced2@example.com ,third@here.net",
);
let config2 = Config::from_env().expect("Config loading failed for emails with whitespace");
assert_eq!(
config2.target_emails,
vec![
"spaced1@example.com".to_string(),
"spaced2@example.com".to_string(),
"third@here.net".to_string(),
]
);
env::set_var("MAIL_LASER_TARGET_EMAILS", "");
let result_empty = Config::from_env();
assert!(
result_empty.is_err(),
"Expected error for empty MAIL_LASER_TARGET_EMAILS"
);
assert!(
result_empty
.unwrap_err()
.to_string()
.contains("MAIL_LASER_TARGET_EMAILS cannot be empty"),
"Error message should indicate non-empty requirement"
);
env::set_var("MAIL_LASER_TARGET_EMAILS", " ,, , ");
let result_whitespace = Config::from_env();
assert!(
result_whitespace.is_err(),
"Expected error for whitespace/comma only MAIL_LASER_TARGET_EMAILS"
);
assert!(
result_whitespace
.unwrap_err()
.to_string()
.contains("MAIL_LASER_TARGET_EMAILS must contain at least one valid email"),
"Error message should indicate need for valid emails"
);
}
#[tokio::test]
async fn test_config_header_prefix_parsing() {
let _lock = ENV_LOCK.lock().unwrap();
clear_test_env_vars();
env::set_var("MAIL_LASER_TARGET_EMAILS", "test@example.com");
env::set_var("MAIL_LASER_WEBHOOK_URL", "https://webhook.example.com");
env::set_var("MAIL_LASER_HEADER_PREFIX", "X-Custom");
let config1 = Config::from_env().expect("Config loading failed for single prefix");
assert_eq!(config1.header_prefixes, vec!["X-Custom".to_string()]);
env::set_var(
"MAIL_LASER_HEADER_PREFIX",
" X-Custom , X-My-App , X-Third ",
);
let config2 = Config::from_env().expect("Config loading failed for multiple prefixes");
assert_eq!(
config2.header_prefixes,
vec![
"X-Custom".to_string(),
"X-My-App".to_string(),
"X-Third".to_string(),
]
);
env::set_var("MAIL_LASER_HEADER_PREFIX", "");
let config3 = Config::from_env().expect("Config loading failed for empty prefix string");
assert!(
config3.header_prefixes.is_empty(),
"Empty string should produce empty vec"
);
env::set_var("MAIL_LASER_HEADER_PREFIX", " ,, , ");
let config4 =
Config::from_env().expect("Config loading failed for whitespace/comma prefix");
assert!(
config4.header_prefixes.is_empty(),
"Whitespace/commas should produce empty vec"
);
env::remove_var("MAIL_LASER_HEADER_PREFIX");
let config5 = Config::from_env().expect("Config loading failed when prefix not set");
assert!(
config5.header_prefixes.is_empty(),
"Unset var should produce empty vec"
);
}
#[tokio::test]
async fn test_config_resilience_fields() {
let _lock = ENV_LOCK.lock().unwrap();
clear_test_env_vars();
env::set_var("MAIL_LASER_TARGET_EMAILS", "test@example.com");
env::set_var("MAIL_LASER_WEBHOOK_URL", "https://webhook.example.com");
env::set_var("MAIL_LASER_WEBHOOK_TIMEOUT", "10");
env::set_var("MAIL_LASER_WEBHOOK_MAX_RETRIES", "7");
env::set_var("MAIL_LASER_CIRCUIT_BREAKER_THRESHOLD", "3");
env::set_var("MAIL_LASER_CIRCUIT_BREAKER_RESET", "90");
let config = Config::from_env().expect("Config loading failed for resilience fields");
assert_eq!(config.webhook_timeout_secs, 10);
assert_eq!(config.webhook_max_retries, 7);
assert_eq!(config.circuit_breaker_threshold, 3);
assert_eq!(config.circuit_breaker_reset_secs, 90);
env::set_var("MAIL_LASER_WEBHOOK_TIMEOUT", "not-a-number");
let result = Config::from_env();
assert!(
result.is_err(),
"Expected error for invalid WEBHOOK_TIMEOUT"
);
assert!(result
.unwrap_err()
.to_string()
.contains("MAIL_LASER_WEBHOOK_TIMEOUT"));
env::set_var("MAIL_LASER_WEBHOOK_TIMEOUT", "30"); env::set_var("MAIL_LASER_WEBHOOK_MAX_RETRIES", "abc");
let result = Config::from_env();
assert!(
result.is_err(),
"Expected error for invalid WEBHOOK_MAX_RETRIES"
);
assert!(result
.unwrap_err()
.to_string()
.contains("MAIL_LASER_WEBHOOK_MAX_RETRIES"));
}
}