Jesús Pérez 09a97ac8f5
chore: update platform submodule to monorepo crates structure
Platform restructured into crates/, added AI service and detector,
       migrated control-center-ui to Leptos 0.8
2026-01-08 21:32:59 +00:00

421 lines
12 KiB
Rust

//! Integration tests for ConfigLoader system
//! Tests config loading across all platform services
use std::env;
use std::fs;
use std::path::PathBuf;
use platform_config::ConfigLoader;
use tempfile::TempDir;
/// Test config struct for integration testing
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
struct TestConfig {
#[serde(default)]
pub name: String,
#[serde(default)]
pub port: u16,
#[serde(default)]
pub enabled: bool,
}
impl ConfigLoader for TestConfig {
fn service_name() -> &'static str {
"test-service"
}
fn load_from_hierarchy() -> std::result::Result<Self, Box<dyn std::error::Error + Send + Sync>>
{
let service = Self::service_name();
if let Some(path) = platform_config::resolve_config_path(service) {
return Self::from_path(&path);
}
Ok(Self::default())
}
fn apply_env_overrides(
&mut self,
) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
if let Ok(val) = env::var("TEST_SERVICE_NAME") {
self.name = val;
}
if let Ok(val) = env::var("TEST_SERVICE_PORT") {
if let Ok(port) = val.parse() {
self.port = port;
}
}
if let Ok(val) = env::var("TEST_SERVICE_ENABLED") {
self.enabled = val.parse().unwrap_or(self.enabled);
}
Ok(())
}
fn from_path<P: AsRef<std::path::Path>>(
path: P,
) -> std::result::Result<Self, Box<dyn std::error::Error + Send + Sync>> {
let path = path.as_ref();
let json_value = platform_config::format::load_config(path).map_err(|e| {
let err: Box<dyn std::error::Error + Send + Sync> = Box::new(e);
err
})?;
serde_json::from_value(json_value).map_err(|e| {
let err_msg = format!("Failed to deserialize test config from {:?}: {}", path, e);
Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidData,
err_msg,
)) as Box<dyn std::error::Error + Send + Sync>
})
}
}
#[test]
fn test_default_config_loading() {
let config = TestConfig::default();
assert_eq!(config.name, "");
assert_eq!(config.port, 0);
assert!(!config.enabled);
}
#[test]
fn test_config_serialization_toml() {
let config = TestConfig {
name: "test".to_string(),
port: 8080,
enabled: true,
};
let toml_str = toml::to_string(&config).expect("Failed to serialize to TOML");
assert!(toml_str.contains("test"));
assert!(toml_str.contains("8080"));
}
#[test]
fn test_config_serialization_json() {
let config = TestConfig {
name: "test".to_string(),
port: 8080,
enabled: true,
};
// Test that JSON serialization works (even though load_config only supports
// TOML/NCL)
let json_str = serde_json::to_string(&config).expect("Failed to serialize to JSON");
assert!(json_str.contains("test"));
assert!(json_str.contains("8080"));
// Deserialize back from JSON
let deserialized: TestConfig =
serde_json::from_str(&json_str).expect("Failed to deserialize from JSON");
assert_eq!(deserialized.name, "test");
assert_eq!(deserialized.port, 8080);
assert!(deserialized.enabled);
}
#[test]
fn test_toml_file_loading() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let config_path = temp_dir.path().join("test.toml");
let toml_content = r#"
name = "test-service"
port = 9090
enabled = true
"#;
fs::write(&config_path, toml_content).expect("Failed to write TOML file");
let config = TestConfig::from_path(&config_path).expect("Failed to load config from TOML file");
assert_eq!(config.name, "test-service");
assert_eq!(config.port, 9090);
assert!(config.enabled);
}
#[test]
fn test_environment_variable_overrides() {
env::set_var("TEST_SERVICE_NAME", "env-service");
env::set_var("TEST_SERVICE_PORT", "7777");
env::set_var("TEST_SERVICE_ENABLED", "false");
let mut config = TestConfig {
name: "original".to_string(),
port: 8080,
enabled: true,
};
config
.apply_env_overrides()
.expect("Failed to apply env overrides");
assert_eq!(config.name, "env-service");
assert_eq!(config.port, 7777);
assert!(!config.enabled);
// Cleanup
env::remove_var("TEST_SERVICE_NAME");
env::remove_var("TEST_SERVICE_PORT");
env::remove_var("TEST_SERVICE_ENABLED");
}
#[test]
fn test_invalid_toml_file() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let config_path = temp_dir.path().join("invalid.toml");
let invalid_toml = r#"
this is not valid [[ toml
"#;
fs::write(&config_path, invalid_toml).expect("Failed to write invalid TOML file");
let result = TestConfig::from_path(&config_path);
assert!(
result.is_err(),
"Expected error loading invalid TOML, but got success"
);
}
#[test]
fn test_invalid_json_file() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let config_path = temp_dir.path().join("invalid.json");
let invalid_json = r#"{ this is not valid json"#;
fs::write(&config_path, invalid_json).expect("Failed to write invalid JSON file");
let result = TestConfig::from_path(&config_path);
assert!(
result.is_err(),
"Expected error loading invalid JSON, but got success"
);
}
#[test]
fn test_missing_file_error() {
let config_path = PathBuf::from("/nonexistent/path/to/config.toml");
let result = TestConfig::from_path(&config_path);
assert!(
result.is_err(),
"Expected error loading nonexistent file, but got success"
);
}
#[test]
fn test_nested_config_loading() {
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
struct NestedConfig {
pub service: ServiceSection,
pub database: DatabaseSection,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
struct ServiceSection {
pub name: String,
pub port: u16,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
struct DatabaseSection {
pub url: String,
pub pool_size: usize,
}
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let config_path = temp_dir.path().join("nested.toml");
let toml_content = r#"
[service]
name = "test"
port = 8080
[database]
url = "postgres://localhost"
pool_size = 10
"#;
fs::write(&config_path, toml_content).expect("Failed to write TOML file");
let content = fs::read_to_string(&config_path).expect("Failed to read file");
let parsed: NestedConfig = toml::from_str(&content).expect("Failed to parse nested TOML");
assert_eq!(parsed.service.name, "test");
assert_eq!(parsed.service.port, 8080);
assert_eq!(parsed.database.url, "postgres://localhost");
assert_eq!(parsed.database.pool_size, 10);
}
#[test]
fn test_toml_to_json_conversion() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
// Create TOML version
let toml_path = temp_dir.path().join("config1.toml");
let toml_content = r#"
name = "toml-service"
port = 8080
enabled = true
"#;
fs::write(&toml_path, toml_content).expect("Failed to write TOML");
// Create another TOML version with different values
let toml_path2 = temp_dir.path().join("config2.toml");
let toml_content2 = r#"
name = "toml-service-2"
port = 9090
enabled = false
"#;
fs::write(&toml_path2, toml_content2).expect("Failed to write TOML 2");
// Load both via ConfigLoader (which converts TOML to JSON internally)
let config1 = TestConfig::from_path(&toml_path).expect("Failed to load TOML config 1");
let config2 = TestConfig::from_path(&toml_path2).expect("Failed to load TOML config 2");
// Verify they deserialized correctly with different values
assert_eq!(config1.name, "toml-service");
assert_eq!(config2.name, "toml-service-2");
assert_eq!(config1.port, 8080);
assert_eq!(config2.port, 9090);
}
#[test]
fn test_config_service_name() {
assert_eq!(TestConfig::service_name(), "test-service");
}
#[test]
fn test_partial_config_with_defaults() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let config_path = temp_dir.path().join("partial.toml");
// Only specify some fields
let toml_content = r#"
name = "partial-service"
"#;
fs::write(&config_path, toml_content).expect("Failed to write TOML file");
let config = TestConfig::from_path(&config_path).expect("Failed to load partial config");
assert_eq!(config.name, "partial-service");
assert_eq!(config.port, 0); // default value
assert!(!config.enabled); // default value
}
#[test]
fn test_config_roundtrip_toml() {
let original = TestConfig {
name: "roundtrip-test".to_string(),
port: 6789,
enabled: true,
};
// Serialize to TOML
let toml_str = toml::to_string(&original).expect("Failed to serialize to TOML");
// Deserialize back
let deserialized: TestConfig =
toml::from_str(&toml_str).expect("Failed to deserialize from TOML");
assert_eq!(deserialized.name, original.name);
assert_eq!(deserialized.port, original.port);
assert_eq!(deserialized.enabled, original.enabled);
}
#[test]
fn test_config_roundtrip_json() {
let original = TestConfig {
name: "roundtrip-test".to_string(),
port: 6789,
enabled: true,
};
// Serialize to JSON
let json_str = serde_json::to_string(&original).expect("Failed to serialize to JSON");
// Deserialize back
let deserialized: TestConfig =
serde_json::from_str(&json_str).expect("Failed to deserialize from JSON");
assert_eq!(deserialized.name, original.name);
assert_eq!(deserialized.port, original.port);
assert_eq!(deserialized.enabled, original.enabled);
}
#[test]
fn test_empty_config_file_toml() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let config_path = temp_dir.path().join("empty.toml");
// Write empty TOML
fs::write(&config_path, "").expect("Failed to write empty TOML");
let config = TestConfig::from_path(&config_path).expect("Failed to load empty TOML file");
// Should get defaults
assert_eq!(config.name, "");
assert_eq!(config.port, 0);
assert!(!config.enabled);
}
#[test]
fn test_environment_partial_override() {
// Only override one field
env::set_var("TEST_SERVICE_PORT", "5555");
let mut config = TestConfig {
name: "original".to_string(),
port: 8080,
enabled: true,
};
config
.apply_env_overrides()
.expect("Failed to apply env overrides");
assert_eq!(config.name, "original"); // unchanged
assert_eq!(config.port, 5555); // overridden
assert!(config.enabled); // unchanged
env::remove_var("TEST_SERVICE_PORT");
}
#[test]
fn test_invalid_port_environment_variable() {
env::set_var("TEST_SERVICE_PORT", "not_a_number");
let mut config = TestConfig {
name: "original".to_string(),
port: 8080,
enabled: true,
};
// Should not panic, port should remain unchanged
let _ = config.apply_env_overrides();
assert_eq!(config.port, 8080);
env::remove_var("TEST_SERVICE_PORT");
}
#[test]
fn test_enabled_parsing_variants() {
// Rust's str::parse() for bool only accepts lowercase "true" and "false"
let test_cases = vec![("true", true), ("false", false)];
for (input, expected) in test_cases {
env::set_var("TEST_SERVICE_ENABLED", input);
let mut config = TestConfig::default();
config
.apply_env_overrides()
.expect("Failed to apply env overrides");
assert_eq!(config.enabled, expected, "Failed for input: {}", input);
env::remove_var("TEST_SERVICE_ENABLED");
}
}