//! Integration tests for Nickel (NCL) config file support //! Tests that actual Nickel config files can be exported and loaded via //! ConfigLoader use std::process::Command; use platform_config::ConfigLoader; /// Test config struct for Nickel integration testing #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)] struct TestNickelConfig { #[serde(default)] pub name: String, #[serde(default)] pub port: u16, #[serde(default)] pub enabled: bool, } impl ConfigLoader for TestNickelConfig { fn service_name() -> &'static str { "test-nickel-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> { 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_nickel_cli_available() { let output = Command::new("nickel") .arg("--version") .output() .expect("Failed to run nickel --version"); assert!( output.status.success(), "nickel CLI not available or failed to run" ); let version_str = String::from_utf8_lossy(&output.stdout); assert!( version_str.contains("nickel"), "nickel version output doesn't contain 'nickel'" ); } #[test] fn test_simple_nickel_export_to_json() { let ncl_code = r#" { name = "test-service", port = 8080, enabled = true, } "#; // Write NCL to temp file let temp_file = tempfile::NamedTempFile::new().expect("Failed to create temp file"); let temp_path = temp_file.path().to_str().unwrap().to_string(); std::fs::write(&temp_path, ncl_code).expect("Failed to write NCL file"); // Run nickel export let output = Command::new("nickel") .arg("export") .arg("--format") .arg("json") .arg(&temp_path) .output() .expect("Failed to run nickel export"); assert!( output.status.success(), "nickel export failed: {}", String::from_utf8_lossy(&output.stderr) ); // Parse JSON output let json_str = String::from_utf8(output.stdout).expect("Failed to read JSON output"); let json_value: serde_json::Value = serde_json::from_str(&json_str).expect("Failed to parse JSON"); // Verify structure assert_eq!(json_value["name"].as_str(), Some("test-service")); assert_eq!(json_value["port"].as_u64(), Some(8080)); assert_eq!(json_value["enabled"].as_bool(), Some(true)); } #[test] fn test_nested_nickel_structure_export() { let ncl_code = r#" { server = { host = "127.0.0.1", port = 8080, workers = 4, }, database = { url = "postgres://localhost", pool_size = 10, }, logging = { level = "info", format = "json", }, } "#; let temp_file = tempfile::NamedTempFile::new().expect("Failed to create temp file"); let temp_path = temp_file.path().to_str().unwrap().to_string(); std::fs::write(&temp_path, ncl_code).expect("Failed to write NCL file"); let output = Command::new("nickel") .arg("export") .arg("--format") .arg("json") .arg(&temp_path) .output() .expect("Failed to run nickel export"); assert!(output.status.success()); let json_str = String::from_utf8(output.stdout).expect("Failed to read JSON output"); let json_value: serde_json::Value = serde_json::from_str(&json_str).expect("Failed to parse JSON"); // Verify nested structure assert_eq!(json_value["server"]["host"].as_str(), Some("127.0.0.1")); assert_eq!(json_value["server"]["port"].as_u64(), Some(8080)); assert_eq!(json_value["database"]["pool_size"].as_u64(), Some(10)); assert_eq!(json_value["logging"]["format"].as_str(), Some("json")); } #[test] fn test_nickel_array_support() { let ncl_code = r#" { endpoints = { resources = "/resources", tools = "/tools", prompts = "/prompts", }, features = { enabled_modules = ["resources", "tools", "prompts"], cache_ttl_seconds = 300, }, } "#; let temp_file = tempfile::NamedTempFile::new().expect("Failed to create temp file"); let temp_path = temp_file.path().to_str().unwrap().to_string(); std::fs::write(&temp_path, ncl_code).expect("Failed to write NCL file"); let output = Command::new("nickel") .arg("export") .arg("--format") .arg("json") .arg(&temp_path) .output() .expect("Failed to run nickel export"); assert!(output.status.success()); let json_str = String::from_utf8(output.stdout).expect("Failed to read JSON output"); let json_value: serde_json::Value = serde_json::from_str(&json_str).expect("Failed to parse JSON"); // Verify array let modules = json_value["features"]["enabled_modules"] .as_array() .expect("enabled_modules should be an array"); assert_eq!(modules.len(), 3); assert_eq!(modules[0].as_str(), Some("resources")); assert_eq!(modules[1].as_str(), Some("tools")); } #[test] fn test_nickel_with_comments() { let ncl_code = r#" # Configuration file with comments # This tests that comments are properly ignored { # Server section server = { # Bind address host = "127.0.0.1", # Port number port = 8080, }, # Enable/disable enabled = true, } "#; let temp_file = tempfile::NamedTempFile::new().expect("Failed to create temp file"); let temp_path = temp_file.path().to_str().unwrap().to_string(); std::fs::write(&temp_path, ncl_code).expect("Failed to write NCL file"); let output = Command::new("nickel") .arg("export") .arg("--format") .arg("json") .arg(&temp_path) .output() .expect("Failed to run nickel export"); assert!( output.status.success(), "nickel export failed: {}", String::from_utf8_lossy(&output.stderr) ); let json_str = String::from_utf8(output.stdout).expect("Failed to read JSON output"); let json_value: serde_json::Value = serde_json::from_str(&json_str).expect("Failed to parse JSON"); assert_eq!(json_value["server"]["host"].as_str(), Some("127.0.0.1")); assert_eq!(json_value["enabled"].as_bool(), Some(true)); } #[test] fn test_nickel_with_null_values() { let ncl_code = r#" { name = "service", description = null, optional_port = null, enabled = true, } "#; let temp_file = tempfile::NamedTempFile::new().expect("Failed to create temp file"); let temp_path = temp_file.path().to_str().unwrap().to_string(); std::fs::write(&temp_path, ncl_code).expect("Failed to write NCL file"); let output = Command::new("nickel") .arg("export") .arg("--format") .arg("json") .arg(&temp_path) .output() .expect("Failed to run nickel export"); assert!(output.status.success()); let json_str = String::from_utf8(output.stdout).expect("Failed to read JSON output"); let json_value: serde_json::Value = serde_json::from_str(&json_str).expect("Failed to parse JSON"); assert!(json_value["description"].is_null()); assert!(json_value["optional_port"].is_null()); assert_eq!(json_value["name"].as_str(), Some("service")); } #[test] fn test_nickel_with_numeric_types() { let ncl_code = r#" { port = 8080, workers = 4, timeout_seconds = 30, cache_ttl_ms = 300000, threshold = 0.95, retries = 3, } "#; let temp_file = tempfile::NamedTempFile::new().expect("Failed to create temp file"); let temp_path = temp_file.path().to_str().unwrap().to_string(); std::fs::write(&temp_path, ncl_code).expect("Failed to write NCL file"); let output = Command::new("nickel") .arg("export") .arg("--format") .arg("json") .arg(&temp_path) .output() .expect("Failed to run nickel export"); assert!(output.status.success()); let json_str = String::from_utf8(output.stdout).expect("Failed to read JSON output"); let json_value: serde_json::Value = serde_json::from_str(&json_str).expect("Failed to parse JSON"); assert_eq!(json_value["port"].as_u64(), Some(8080)); assert_eq!(json_value["workers"].as_u64(), Some(4)); assert_eq!(json_value["timeout_seconds"].as_u64(), Some(30)); assert_eq!(json_value["cache_ttl_ms"].as_u64(), Some(300000)); assert!( (json_value["threshold"].as_f64().unwrap() - 0.95).abs() < 0.001, "threshold should be 0.95" ); } #[test] fn test_nickel_with_multiline_strings() { let ncl_code = r#" { name = "service", description = "This is a multiline description that spans multiple lines in the configuration", version = "1.0.0", } "#; let temp_file = tempfile::NamedTempFile::new().expect("Failed to create temp file"); let temp_path = temp_file.path().to_str().unwrap().to_string(); std::fs::write(&temp_path, ncl_code).expect("Failed to write NCL file"); let output = Command::new("nickel") .arg("export") .arg("--format") .arg("json") .arg(&temp_path) .output() .expect("Failed to run nickel export"); assert!( output.status.success(), "nickel export failed: {}", String::from_utf8_lossy(&output.stderr) ); let json_str = String::from_utf8(output.stdout).expect("Failed to read JSON output"); let _json_value: serde_json::Value = serde_json::from_str(&json_str).expect("Failed to parse JSON"); // Verify multiline strings are handled correctly assert!(json_str.contains("multiline")); } #[test] fn test_nickel_invalid_syntax_error() { let ncl_code = r#" { name = "service", port = invalid_value, # This is invalid syntax } "#; let temp_file = tempfile::NamedTempFile::new().expect("Failed to create temp file"); let temp_path = temp_file.path().to_str().unwrap().to_string(); std::fs::write(&temp_path, ncl_code).expect("Failed to write NCL file"); let output = Command::new("nickel") .arg("export") .arg("--format") .arg("json") .arg(&temp_path) .output() .expect("Failed to run nickel export"); // Should fail due to invalid syntax assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( !stderr.is_empty(), "Expected error message for invalid syntax" ); } #[test] fn test_nickel_via_configloader_from_path() { let ncl_code = r#" { name = "configloader-test", port = 9999, enabled = true, } "#; let temp_file = tempfile::NamedTempFile::new().expect("Failed to create temp file"); let temp_path = temp_file.path().to_path_buf(); // Change extension to .ncl for ConfigLoader to recognize it let ncl_path = temp_path.with_extension("ncl"); std::fs::write(&ncl_path, ncl_code).expect("Failed to write NCL file"); // Use ConfigLoader to load the NCL file let config = TestNickelConfig::from_path(&ncl_path).expect("Failed to load NCL config via ConfigLoader"); assert_eq!(config.name, "configloader-test"); assert_eq!(config.port, 9999); assert!(config.enabled); }