#![cfg(not(miri))]
#[cfg(test)]
mod tests {
use rlg::{
config::{
Config, ConfigError, LogRotation, LoggingDestination,
},
log_level::LogLevel,
};
use serde::Deserialize;
use std::{
collections::HashMap, env, num::NonZeroU64, path::PathBuf,
str::FromStr,
};
use tempfile::tempdir;
#[cfg(feature = "tokio")]
use tokio::{fs, io::AsyncWriteExt};
#[test]
fn test_log_level_from_str_basic() {
assert_eq!(LogLevel::from_str("INFO").unwrap(), LogLevel::INFO);
assert_eq!(
LogLevel::from_str("debug").unwrap(),
LogLevel::DEBUG
);
assert_eq!(LogLevel::from_str("NONE").unwrap(), LogLevel::NONE);
assert_eq!(LogLevel::from_str("warn").unwrap(), LogLevel::WARN);
assert_eq!(
LogLevel::from_str("ERROR").unwrap(),
LogLevel::ERROR
);
assert!(LogLevel::from_str("INVALID").is_err());
}
#[test]
fn test_config_log_file_path_display() {
let config = Config {
version: "1.0".to_string(),
profile: "test".to_string(),
log_file_path: PathBuf::from("RLG.log"),
log_level: LogLevel::INFO,
log_rotation: None,
log_format: "%level - %message".to_string(),
logging_destinations: vec![],
env_vars: HashMap::new(),
};
assert_eq!(
config.log_file_path.display().to_string(),
"RLG.log",
"Log file path should be 'RLG.log'"
);
assert!(
config.log_file_path.is_relative(),
"The log file path should be a relative path"
);
assert_eq!(
config.log_file_path.to_str().unwrap(),
"RLG.log",
"The string representation of the path should be 'RLG.log'"
);
}
#[cfg(feature = "tokio")]
#[tokio::test]
#[allow(unsafe_code)]
async fn test_config_load_with_invalid_values() {
let temp_dir =
tempdir().expect("Failed to create temp directory");
let log_file_path = temp_dir.path().join("RLG.log");
let mut log_file = fs::File::create(&log_file_path)
.await
.expect("Failed to create log file");
log_file
.write_all(b"This is a test log file")
.await
.expect("Failed to write to log file");
let config_content = r#"
version = "1.0"
log_file_path = "RLG.log"
log_format = "%level - %message"
"#;
let config_file_path = temp_dir.path().join("config.toml");
fs::write(&config_file_path, config_content)
.await
.expect("Failed to write config file");
unsafe { env::set_var("LOG_LEVEL", "INVALID_LOG_LEVEL") };
unsafe { env::set_var("LOG_ROTATION", "INVALID_LOG_ROTATION") };
let config_result =
Config::load_async(Some(&config_file_path)).await;
assert!(config_result.is_ok(), "Config load should not fail");
let log_level_env =
env::var("LOG_LEVEL").expect("LOG_LEVEL not set");
let log_rotation_env =
env::var("LOG_ROTATION").expect("LOG_ROTATION not set");
assert!(
log_level_env.parse::<LogLevel>().is_err(),
"Expected LOG_LEVEL to be invalid"
);
assert!(
log_rotation_env.parse::<LogRotation>().is_err(),
"Expected LOG_ROTATION to be invalid"
);
unsafe { env::remove_var("LOG_LEVEL") };
unsafe { env::remove_var("LOG_ROTATION") };
fs::remove_file(log_file_path)
.await
.expect("Failed to remove log file");
}
#[test]
fn test_log_rotation_clone_and_copy() {
let size = NonZeroU64::new(1024 * 1024)
.expect("Failed to create NonZeroU64 instance");
let rotation1 = LogRotation::Size(size);
let rotation2 = rotation1;
assert_eq!(rotation1, rotation2);
}
#[test]
fn test_config_error() {
let env_var_error = ConfigError::EnvVarParseError(
envy::Error::MissingValue("Test error"),
);
assert!(
format!("{}", env_var_error)
.contains("Environment variable parse error"),
"Unexpected error message for EnvVarParseError: {}",
env_var_error
);
}
#[test]
fn test_logging_destination() {
let file_dest =
LoggingDestination::File(PathBuf::from("test.log"));
let stdout_dest = LoggingDestination::Stdout;
let network_dest =
LoggingDestination::Network("127.0.0.1:514".to_string());
assert!(matches!(file_dest, LoggingDestination::File(_)));
assert!(matches!(stdout_dest, LoggingDestination::Stdout));
assert!(matches!(network_dest, LoggingDestination::Network(_)));
}
#[test]
fn test_log_level_from_str_comprehensive() {
let test_cases = [
("ALL", Ok(LogLevel::ALL)),
("DEBUG", Ok(LogLevel::DEBUG)),
("INFO", Ok(LogLevel::INFO)),
("WARN", Ok(LogLevel::WARN)),
("ERROR", Ok(LogLevel::ERROR)),
("FATAL", Ok(LogLevel::FATAL)),
("TRACE", Ok(LogLevel::TRACE)),
("VERBOSE", Ok(LogLevel::VERBOSE)),
("NONE", Ok(LogLevel::NONE)),
("DISABLED", Ok(LogLevel::DISABLED)),
("CRITICAL", Ok(LogLevel::CRITICAL)),
("invalid", Err(())),
];
for (input, expected) in test_cases.iter() {
let result = LogLevel::from_str(input);
match (result, expected) {
(Ok(level), Ok(expected_level)) => assert_eq!(
level, *expected_level,
"Failed for input: {}",
input
),
(Err(_), Err(())) => {} _ => panic!("Unexpected result for input: {}", input),
}
}
assert!(matches!(
LogLevel::from_str("info"),
Ok(LogLevel::INFO)
));
assert!(matches!(
LogLevel::from_str("ErRoR"),
Ok(LogLevel::ERROR)
));
}
#[test]
fn test_config_validate() {
let temp_dir = env::temp_dir();
let log_file_path = temp_dir.join("test_validate_RLG.log");
std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&log_file_path)
.unwrap();
let mut config = Config {
log_file_path,
..Default::default()
};
assert!(
config.validate().is_ok(),
"Validation should pass with valid config"
);
config.log_file_path = PathBuf::new();
assert!(
config.validate().is_err(),
"Validation should fail with empty log file path"
);
}
#[test]
#[allow(unsafe_code)]
fn test_config_expand_env_vars() {
unsafe {
env::set_var("RLG_LOG_PATH", "/tmp/env_test_RLG.log")
};
let mut config = Config::default();
config.env_vars.insert(
"RLG_LOG_PATH".to_string(),
"${RLG_LOG_PATH}".to_string(),
);
let expanded_config = config.expand_env_vars();
assert_eq!(
expanded_config.env_vars.get("RLG_LOG_PATH").unwrap(),
"/tmp/env_test_RLG.log"
);
unsafe { env::remove_var("RLG_LOG_PATH") };
}
#[test]
fn test_config_diff() {
let config1 = Config::default();
let mut env_vars = HashMap::new();
env_vars.insert("DEBUG".to_string(), "true".to_string());
let config2 = Config {
version: "2.0".to_string(),
profile: "production".to_string(),
log_file_path: PathBuf::from("prod.log"),
log_level: LogLevel::ERROR,
log_rotation: Some(LogRotation::Count(10)),
log_format: "JSON".to_string(),
logging_destinations: vec![LoggingDestination::Network(
"localhost:8080".to_string(),
)],
env_vars,
};
let differences = Config::diff(&config1, &config2);
assert_eq!(
differences.get("version"),
Some(&"1.0 -> 2.0".to_string())
);
assert_eq!(
differences.get("profile"),
Some(&"default -> production".to_string())
);
assert!(differences.contains_key("log_file_path"));
assert!(differences.contains_key("log_level"));
assert!(differences.contains_key("log_rotation"));
assert!(differences.contains_key("log_format"));
assert!(differences.contains_key("logging_destinations"));
assert!(differences.contains_key("env_vars"));
}
#[test]
fn test_config_override_with() {
let mut env_vars1 = HashMap::new();
env_vars1.insert("K1".to_string(), "V1".to_string());
let config1 = Config {
env_vars: env_vars1,
..Config::default()
};
let mut env_vars2 = HashMap::new();
env_vars2.insert("K2".to_string(), "V2".to_string());
let config2 = Config {
profile: "test_profile".to_string(),
log_format: "%level - %message".to_string(),
env_vars: env_vars2,
..Config::default()
};
let merged_config = config1.override_with(&config2);
assert_eq!(merged_config.profile, "test_profile");
assert_eq!(merged_config.log_format, "%level - %message");
assert!(merged_config.env_vars.contains_key("K1"));
assert!(merged_config.env_vars.contains_key("K2"));
}
#[test]
fn test_config_error_enum() {
#[allow(dead_code)]
#[derive(Deserialize, Debug)]
struct EnvConfig {
required_field: String,
}
let env_var_error = ConfigError::EnvVarParseError(
envy::from_env::<EnvConfig>().unwrap_err(),
);
let config_parse_error = ConfigError::ConfigParseError(
config::ConfigError::Message("test error".to_string()),
);
let invalid_file_path_error =
ConfigError::InvalidFilePath("invalid path".to_string());
let env_var_error_message = format!("{}", env_var_error);
println!("Env var error message: {}", env_var_error_message);
assert!(
env_var_error_message.contains("field")
|| env_var_error_message.contains("parse"),
"Env var error should contain 'field' or 'parse' but was: {}",
env_var_error_message
);
assert_eq!(
format!("{}", config_parse_error),
"Configuration parsing error: test error"
);
assert_eq!(
format!("{}", invalid_file_path_error),
"Invalid file path: invalid path"
);
}
#[test]
fn test_config_set_all_fields() {
let mut config = Config::default();
assert!(config.set("version", "2.0").is_ok());
assert_eq!(config.version, "2.0");
assert!(config.set("profile", "new_profile").is_ok());
assert_eq!(config.profile, "new_profile");
assert!(
config
.set("log_file_path", PathBuf::from("new.log"))
.is_ok()
);
assert_eq!(config.log_file_path, PathBuf::from("new.log"));
assert!(config.set("log_level", LogLevel::DEBUG).is_ok());
assert_eq!(config.log_level, LogLevel::DEBUG);
let new_rotation = Some(LogRotation::Count(5));
assert!(config.set("log_rotation", new_rotation).is_ok());
assert_eq!(config.log_rotation, new_rotation);
assert!(config.set("log_format", "[%level] %message").is_ok());
assert_eq!(config.log_format, "[%level] %message");
let new_dest = vec![LoggingDestination::Stdout];
assert!(
config
.set("logging_destinations", new_dest.clone())
.is_ok()
);
assert_eq!(config.logging_destinations, new_dest);
let mut new_env = HashMap::new();
new_env.insert("K".to_string(), "V".to_string());
assert!(config.set("env_vars", &new_env).is_ok());
assert_eq!(config.env_vars, new_env);
assert!(config.set("non_existent", "value").is_err());
}
#[test]
fn test_config_save_to_file() {
let temp_dir =
tempdir().expect("Failed to create temp directory");
let config_path = temp_dir.path().join("test_config.json");
let config = Config::default();
assert!(config.save_to_file(&config_path).is_ok());
assert!(
config_path.exists(),
"Config file should have been created"
);
}
}