use config_lib::Config;
use std::io::Write;
use tempfile::NamedTempFile;
#[cfg(feature = "schema")]
use config_lib::SchemaBuilder;
#[test]
fn test_conf_parsing() {
let content = r#"
# Basic configuration
app_name = "test-app"
port = 8080
debug = true
version = 1.0
# Section
[database]
host = "localhost"
port = 5432
# Arrays
servers = alpha beta gamma
ports = 8001 8002 8003
"#;
let config = Config::from_string(content, Some("conf")).unwrap();
assert_eq!(
config.get("app_name").unwrap().as_string().unwrap(),
"test-app"
);
assert_eq!(config.get("port").unwrap().as_integer().unwrap(), 8080);
assert!(config.get("debug").unwrap().as_bool().unwrap());
assert_eq!(config.get("version").unwrap().as_float().unwrap(), 1.0);
assert_eq!(
config.get("database.host").unwrap().as_string().unwrap(),
"localhost"
);
assert_eq!(
config.get("database.port").unwrap().as_integer().unwrap(),
5432
);
let servers = config.get("database.servers").unwrap().as_array().unwrap();
assert_eq!(servers.len(), 3);
assert_eq!(servers[0].as_string().unwrap(), "alpha");
let ports = config.get("database.ports").unwrap().as_array().unwrap();
assert_eq!(ports.len(), 3);
assert_eq!(ports[0].as_integer().unwrap(), 8001);
}
#[cfg(feature = "json")]
#[test]
fn test_json_parsing() {
let content = r#"
{
"app_name": "json-app",
"port": 3000,
"debug": false,
"database": {
"host": "localhost",
"port": 5432
},
"servers": ["alpha", "beta", "gamma"]
}
"#;
let config = Config::from_string(content, Some("json")).unwrap();
assert_eq!(
config.get("app_name").unwrap().as_string().unwrap(),
"json-app"
);
assert_eq!(config.get("port").unwrap().as_integer().unwrap(), 3000);
assert!(!config.get("debug").unwrap().as_bool().unwrap());
assert_eq!(
config.get("database.host").unwrap().as_string().unwrap(),
"localhost"
);
let servers = config.get("servers").unwrap().as_array().unwrap();
assert_eq!(servers.len(), 3);
assert_eq!(servers[1].as_string().unwrap(), "beta");
}
#[cfg(feature = "noml")]
#[test]
fn test_noml_parsing() {
use std::env;
env::set_var("TEST_PORT", "9000");
env::set_var("TEST_HOST", "example.com");
let content = r#"
app_name = "noml-app"
port = env("TEST_PORT", 8080)
host = env("TEST_HOST", "localhost")
# Native types
file_size = @size("10MB")
timeout = @duration("30s")
# Simple URL without interpolation for now
api_url = "http://api.example.com"
"#;
let config = Config::from_string(content, Some("noml")).unwrap();
assert_eq!(
config.get("app_name").unwrap().as_string().unwrap(),
"noml-app"
);
assert_eq!(config.get("port").unwrap().as_integer().unwrap(), 9000);
assert_eq!(
config.get("host").unwrap().as_string().unwrap(),
"example.com"
);
assert_eq!(
config.get("file_size").unwrap().as_integer().unwrap(),
10485760
); assert_eq!(config.get("timeout").unwrap().as_float().unwrap(), 30.0);
assert_eq!(
config.get("api_url").unwrap().as_string().unwrap(),
"http://api.example.com"
);
}
#[test]
fn test_format_detection() {
#[cfg(feature = "json")]
{
let json = r#"{"key": "value"}"#;
let config = Config::from_string(json, None).unwrap();
assert_eq!(config.format(), "json");
}
let conf = "key = value";
let config = Config::from_string(conf, None).unwrap();
assert_eq!(config.format(), "conf");
let toml = "[section]\nkey = value";
let config = Config::from_string(toml, Some("conf")).unwrap(); assert_eq!(config.format(), "conf"); }
#[test]
fn test_config_modification() {
let mut config = Config::from_string("key = old_value", Some("conf")).unwrap();
assert!(!config.is_modified());
assert_eq!(config.get("key").unwrap().as_string().unwrap(), "old_value");
config.set("key", "new_value").unwrap();
assert!(config.is_modified());
assert_eq!(config.get("key").unwrap().as_string().unwrap(), "new_value");
config.set("new_key", 42).unwrap();
assert_eq!(config.get("new_key").unwrap().as_integer().unwrap(), 42);
config.set("section.nested", true).unwrap();
assert!(config.get("section.nested").unwrap().as_bool().unwrap());
config.mark_clean();
assert!(!config.is_modified());
}
#[test]
fn test_value_conversions() {
let mut config = Config::new();
config.set("str_int", "42").unwrap();
config.set("str_float", "3.141592653589793").unwrap();
config.set("str_bool", "true").unwrap();
let value = config.get("str_int").unwrap();
assert_eq!(value.as_integer().unwrap(), 42);
assert_eq!(value.as_float().unwrap(), 42.0);
let value = config.get("str_float").unwrap();
assert!((value.as_float().unwrap() - std::f64::consts::PI).abs() < f64::EPSILON);
let value = config.get("str_bool").unwrap();
assert!(value.as_bool().unwrap());
config.set("int_val", 100).unwrap();
let value = config.get("int_val").unwrap();
assert_eq!(value.as_float().unwrap(), 100.0);
}
#[test]
fn test_config_merging() {
let mut base = Config::from_string("a = 1\nb = 2\n[section]\nx = 10", Some("conf")).unwrap();
let override_config =
Config::from_string("b = 20\nc = 3\n[section]\ny = 20", Some("conf")).unwrap();
base.merge(&override_config).unwrap();
assert_eq!(base.get("a").unwrap().as_integer().unwrap(), 1); assert_eq!(base.get("b").unwrap().as_integer().unwrap(), 20); assert_eq!(base.get("c").unwrap().as_integer().unwrap(), 3);
assert_eq!(base.get("section.x").unwrap().as_integer().unwrap(), 10); assert_eq!(base.get("section.y").unwrap().as_integer().unwrap(), 20); }
#[test]
fn test_file_operations() -> Result<(), Box<dyn std::error::Error>> {
let content = "app = file_test\nport = 7000";
let mut temp_file = NamedTempFile::new()?;
write!(temp_file, "{content}")?;
let config = Config::from_file(temp_file.path())?;
assert_eq!(config.get("app").unwrap().as_string()?, "file_test");
assert_eq!(config.get("port").unwrap().as_integer()?, 7000);
assert_eq!(config.file_path(), Some(temp_file.path()));
Ok(())
}
#[cfg(feature = "schema")]
#[test]
fn test_schema_validation() {
let schema = SchemaBuilder::new()
.require_string("name")
.require_integer("port")
.optional_bool("debug")
.build();
let valid_config =
Config::from_string("name = test\nport = 8080\ndebug = true", Some("conf")).unwrap();
assert!(valid_config.validate_schema(&schema).is_ok());
let invalid_config = Config::from_string("name = test\ndebug = true", Some("conf")).unwrap();
assert!(invalid_config.validate_schema(&schema).is_err());
let invalid_config =
Config::from_string("name = test\nport = not_a_number", Some("conf")).unwrap();
assert!(invalid_config.validate_schema(&schema).is_err());
}
#[test]
fn test_serialization_roundtrip() {
let original_content = "key = value\nport = 8080\n[section]\nnested = true";
let config = Config::from_string(original_content, Some("conf")).unwrap();
let serialized = config.serialize().unwrap();
let reparsed = Config::from_string(&serialized, Some("conf")).unwrap();
assert_eq!(reparsed.get("key").unwrap().as_string().unwrap(), "value");
assert_eq!(reparsed.get("port").unwrap().as_integer().unwrap(), 8080);
assert!(reparsed.get("section.nested").unwrap().as_bool().unwrap());
}
#[test]
fn test_error_handling() {
let result = Config::from_string("invalid syntax [[[", Some("conf"));
assert!(result.is_err());
let config = Config::from_string("key = value", Some("conf")).unwrap();
assert!(config.get("nonexistent").is_none());
let config = Config::from_string("port = not_a_number", Some("conf")).unwrap();
let result = config.get("port").unwrap().as_integer();
assert!(result.is_err());
}
#[test]
fn test_conf_arrays() {
let content = r#"
# Space-separated arrays
servers = alpha beta gamma
# Comma-separated arrays
ports = 8001 8002 8003
"#;
let config = Config::from_string(content, Some("conf")).unwrap();
let servers = config.get("servers").unwrap().as_array().unwrap();
assert_eq!(servers.len(), 3);
assert_eq!(servers[0].as_string().unwrap(), "alpha");
let ports = config.get("ports").unwrap().as_array().unwrap();
assert_eq!(ports.len(), 3);
assert_eq!(ports[0].as_integer().unwrap(), 8001);
}
#[test]
fn test_edge_cases() {
let config = Config::from_string("", Some("conf")).unwrap();
assert_eq!(config.keys().unwrap().len(), 0);
let config = Config::from_string("# Just a comment\n# Another comment", Some("conf")).unwrap();
assert_eq!(config.keys().unwrap().len(), 0);
let config = Config::from_string("empty = \nnull_val = null", Some("conf")).unwrap();
assert!(config.get("empty").unwrap().is_null());
assert!(config.get("null_val").unwrap().is_null());
let config = Config::from_string("unicode = \"Hello World\"", Some("conf")).unwrap();
assert_eq!(
config.get("unicode").unwrap().as_string().unwrap(),
"Hello World"
);
}
#[cfg(feature = "async")]
#[tokio::test]
async fn test_async_operations() -> Result<(), Box<dyn std::error::Error>> {
let content = "async_test = true\nport = 8080";
let mut temp_file = NamedTempFile::new()?;
write!(temp_file, "{content}")?;
let config = Config::from_file_async(temp_file.path()).await?;
assert!(config.get("async_test").unwrap().as_bool()?);
assert_eq!(config.get("port").unwrap().as_integer()?, 8080);
Ok(())
}