prvng_platform/crates/platform-config/tests/nickel_integration_tests.rs
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

422 lines
12 KiB
Rust

//! 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<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>> {
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_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);
}