//! 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> { 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> { 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>( path: P, ) -> std::result::Result> { let path = path.as_ref(); let json_value = platform_config::format::load_config(path).map_err(|e| { let err: Box = 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 }) } } #[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"); } }