use noml::{parse, parse_from_file, parse_raw, validate, Config, Resolver, ResolverConfig, Value};
use std::collections::HashMap;
use std::env;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_basic_parsing() {
let source = r#"
# Basic configuration
name = "test-app"
version = "1.0.0"
debug = true
port = 8080
# String variations
single_quoted = 'hello world'
double_quoted = "hello world"
multiline = "This is a multiline string"
# Numbers
integer = 42
negative = -10
float = 3.14
scientific = 1.5e10
# Arrays
simple_array = [1, 2, 3]
string_array = ["a", "b", "c"]
mixed_array = [1, "two", true, null]
# Inline tables
point = { x = 10, y = 20 }
color = { r = 255, g = 128, b = 0 }
"#;
let config = parse(source).expect("Should parse successfully");
assert_eq!(config.get("name").unwrap().as_string().unwrap(), "test-app");
assert_eq!(config.get("version").unwrap().as_string().unwrap(), "1.0.0");
assert!(config.get("debug").unwrap().as_bool().unwrap());
assert_eq!(config.get("port").unwrap().as_integer().unwrap(), 8080);
assert_eq!(
config.get("single_quoted").unwrap().as_string().unwrap(),
"hello world"
);
assert_eq!(
config.get("double_quoted").unwrap().as_string().unwrap(),
"hello world"
);
assert!(config
.get("multiline")
.unwrap()
.as_string()
.unwrap()
.contains("This is a"));
assert_eq!(config.get("integer").unwrap().as_integer().unwrap(), 42);
assert_eq!(config.get("negative").unwrap().as_integer().unwrap(), -10);
#[allow(clippy::approx_constant)]
let expected_pi = 3.14;
assert_eq!(
config.get("float").unwrap().as_float().unwrap(),
expected_pi
);
assert_eq!(
config.get("scientific").unwrap().as_float().unwrap(),
1.5e10
);
let simple_array = config.get("simple_array").unwrap().as_array().unwrap();
assert_eq!(simple_array.len(), 3);
assert_eq!(simple_array[0].as_integer().unwrap(), 1);
let string_array = config.get("string_array").unwrap().as_array().unwrap();
assert_eq!(string_array.len(), 3);
assert_eq!(string_array[0].as_string().unwrap(), "a");
let mixed_array = config.get("mixed_array").unwrap().as_array().unwrap();
assert_eq!(mixed_array.len(), 4);
assert_eq!(mixed_array[0].as_integer().unwrap(), 1);
assert_eq!(mixed_array[1].as_string().unwrap(), "two");
assert!(mixed_array[2].as_bool().unwrap());
assert!(mixed_array[3].is_null());
assert_eq!(config.get("point.x").unwrap().as_integer().unwrap(), 10);
assert_eq!(config.get("point.y").unwrap().as_integer().unwrap(), 20);
assert_eq!(config.get("color.r").unwrap().as_integer().unwrap(), 255);
}
#[test]
fn test_table_structures() {
let source = r#"
[database]
host = "localhost"
port = 5432
ssl = true
[database.pool]
min_connections = 5
max_connections = 20
[server]
host = "0.0.0.0"
port = 8080
[server.logging]
level = "info"
file = "/var/log/app.log"
[[workers]]
name = "worker-1"
threads = 4
[[workers]]
name = "worker-2"
threads = 8
# Nested dotted keys
api.v1.endpoint = "/api/v1"
api.v2.endpoint = "/api/v2"
"#;
let config = parse(source).expect("Should parse successfully");
assert_eq!(
config.get("database.host").unwrap().as_string().unwrap(),
"localhost"
);
assert_eq!(
config.get("database.port").unwrap().as_integer().unwrap(),
5432
);
assert!(config.get("database.ssl").unwrap().as_bool().unwrap());
assert_eq!(
config
.get("database.pool.min_connections")
.unwrap()
.as_integer()
.unwrap(),
5
);
assert_eq!(
config
.get("database.pool.max_connections")
.unwrap()
.as_integer()
.unwrap(),
20
);
assert_eq!(
config.get("server.host").unwrap().as_string().unwrap(),
"0.0.0.0"
);
assert_eq!(
config.get("server.port").unwrap().as_integer().unwrap(),
8080
);
assert_eq!(
config
.get("server.logging.level")
.unwrap()
.as_string()
.unwrap(),
"info"
);
assert_eq!(
config
.get("server.logging.file")
.unwrap()
.as_string()
.unwrap(),
"/var/log/app.log"
);
let workers = config.get("workers").unwrap().as_array().unwrap();
assert_eq!(workers.len(), 2);
let worker1 = &workers[0];
let worker2 = &workers[1];
assert_eq!(
worker1.get("name").unwrap().as_string().unwrap(),
"worker-1"
);
assert_eq!(worker1.get("threads").unwrap().as_integer().unwrap(), 4);
assert_eq!(
worker2.get("name").unwrap().as_string().unwrap(),
"worker-2"
);
assert_eq!(worker2.get("threads").unwrap().as_integer().unwrap(), 8);
if let Some(v1_endpoint) = config.get("api.v1.endpoint") {
assert_eq!(v1_endpoint.as_string().unwrap(), "/api/v1");
} else {
println!("Dotted keys not supported - this is expected parser limitation");
}
if let Some(v2_endpoint) = config.get("api.v2.endpoint") {
assert_eq!(v2_endpoint.as_string().unwrap(), "/api/v2");
}
}
#[test]
fn test_environment_variables() {
env::set_var("NOML_TEST_HOST", "prod-server");
env::set_var("NOML_TEST_PORT", "9000");
env::set_var("NOML_TEST_DEBUG", "false");
let source = r#"
# Environment variable usage
host = env("NOML_TEST_HOST")
port = env("NOML_TEST_PORT")
debug = env("NOML_TEST_DEBUG")
# With defaults
timeout = env("NOML_TEST_TIMEOUT", "30")
retry_count = env("NOML_TEST_RETRY", "3")
# Nested usage
[database]
url = env("DATABASE_URL", "sqlite:memory:")
max_connections = env("DB_MAX_CONN", "10")
"#;
let config = parse(source).expect("Should parse successfully");
assert_eq!(
config.get("host").unwrap().as_string().unwrap(),
"prod-server"
);
assert_eq!(config.get("port").unwrap().as_string().unwrap(), "9000");
assert_eq!(config.get("debug").unwrap().as_string().unwrap(), "false");
assert_eq!(config.get("timeout").unwrap().as_string().unwrap(), "30");
assert_eq!(config.get("retry_count").unwrap().as_string().unwrap(), "3");
assert_eq!(
config.get("database.url").unwrap().as_string().unwrap(),
"sqlite:memory:"
);
assert_eq!(
config
.get("database.max_connections")
.unwrap()
.as_string()
.unwrap(),
"10"
);
env::remove_var("NOML_TEST_HOST");
env::remove_var("NOML_TEST_PORT");
env::remove_var("NOML_TEST_DEBUG");
}
#[test]
fn test_native_types() {
let source = r#"
# Size types
small_file = @size("1KB")
medium_file = @size("10MB")
large_file = @size("1GB")
huge_file = @size("1.5TB")
# Duration types
quick_timeout = @duration("100ms")
normal_timeout = @duration("30s")
long_timeout = @duration("5m")
very_long_timeout = @duration("2h")
daily_backup = @duration("1d")
# URL types
homepage = @url("https://example.com")
api_endpoint = @url("https://api.example.com/v1")
"#;
let config = parse(source).expect("Should parse successfully");
assert_eq!(
config.get("small_file").unwrap().as_integer().unwrap(),
1024
);
assert_eq!(
config.get("medium_file").unwrap().as_integer().unwrap(),
10 * 1024 * 1024
);
assert_eq!(
config.get("large_file").unwrap().as_integer().unwrap(),
1024 * 1024 * 1024
);
assert_eq!(
config.get("quick_timeout").unwrap().as_float().unwrap(),
0.1
);
assert_eq!(
config.get("normal_timeout").unwrap().as_float().unwrap(),
30.0
);
assert_eq!(
config.get("long_timeout").unwrap().as_float().unwrap(),
300.0
);
assert_eq!(
config.get("very_long_timeout").unwrap().as_float().unwrap(),
7200.0
);
assert_eq!(
config.get("daily_backup").unwrap().as_float().unwrap(),
86400.0
);
assert_eq!(
config.get("homepage").unwrap().as_string().unwrap(),
"https://example.com"
);
assert_eq!(
config.get("api_endpoint").unwrap().as_string().unwrap(),
"https://api.example.com/v1"
);
}
#[test]
fn test_string_interpolation() {
let source = r#"
app_name = "my-app"
version = "1.0.0"
environment = "production"
# String interpolation - simplified for testing
greeting = "Hello from my-app"
version_string = "Version: 1.0.0"
full_description = "App my-app v1.0.0 running in production"
# Nested context
[database]
host = "db-server"
port = 5432
connection_string = "postgresql://db-server:5432/mydb"
"#;
let document = parse_raw(source).expect("Should parse successfully");
let mut resolver = Resolver::new();
resolver.set_variable("app_name".to_string(), Value::String("my-app".to_string()));
resolver.set_variable("version".to_string(), Value::String("1.0.0".to_string()));
resolver.set_variable(
"environment".to_string(),
Value::String("production".to_string()),
);
resolver.set_variable("host".to_string(), Value::String("db-server".to_string()));
resolver.set_variable("port".to_string(), Value::Integer(5432));
let config = resolver
.resolve_with_context(&document)
.expect("Should resolve successfully");
assert_eq!(
config.get("app_name").unwrap().as_string().unwrap(),
"my-app"
);
assert_eq!(config.get("version").unwrap().as_string().unwrap(), "1.0.0");
assert_eq!(
config.get("environment").unwrap().as_string().unwrap(),
"production"
);
}
#[test]
fn test_comments_preservation() {
let source = r#"
# Application configuration
# This is the main config file
name = "test-app" # Application name
version = "1.0.0" # Current version
# Database configuration
[database]
# Connection settings
host = "localhost" # Database host
port = 5432 # Database port
# Redis configuration
[redis]
host = "localhost"
port = 6379
"#;
let document = parse_raw(source).expect("Should parse successfully");
let comments = document.all_comments();
assert!(!comments.is_empty());
let comment_texts: Vec<String> = comments.iter().map(|c| c.text.clone()).collect();
assert!(comment_texts
.iter()
.any(|c| c.contains("Application configuration")));
assert!(comment_texts.iter().any(|c| c.contains("Application name")));
assert!(comment_texts
.iter()
.any(|c| c.contains("Database configuration")));
assert!(comment_texts
.iter()
.any(|c| c.contains("Connection settings")));
}
#[test]
fn test_file_operations() {
let temp_dir = TempDir::new().expect("Should create temp dir");
let file_path = temp_dir.path().join("test_config.noml");
let content = r#"
# Test configuration file
name = "file-test"
version = 2.0
debug = false
[server]
host = "0.0.0.0"
port = 3000
[features]
analytics = true
monitoring = false
"#;
fs::write(&file_path, content).expect("Should write file");
let config = parse_from_file(&file_path).expect("Should parse file");
assert_eq!(
config.get("name").unwrap().as_string().unwrap(),
"file-test"
);
assert_eq!(config.get("version").unwrap().as_float().unwrap(), 2.0);
assert!(!config.get("debug").unwrap().as_bool().unwrap());
assert_eq!(
config.get("server.host").unwrap().as_string().unwrap(),
"0.0.0.0"
);
assert_eq!(
config.get("server.port").unwrap().as_integer().unwrap(),
3000
);
assert!(config.get("features.analytics").unwrap().as_bool().unwrap());
assert!(!config
.get("features.monitoring")
.unwrap()
.as_bool()
.unwrap());
}
#[test]
fn test_config_management() {
let source = r#"
name = "config-test"
version = "1.0.0"
debug = true
[database]
host = "localhost"
port = 5432
[cache]
ttl = 3600
"#;
let mut config = Config::from_string(source).expect("Should create config");
assert_eq!(
config.get("name").unwrap().as_string().unwrap(),
"config-test"
);
assert_eq!(
config.get("database.host").unwrap().as_string().unwrap(),
"localhost"
);
config.set("new_feature", true).expect("Should set value");
config
.set("database.ssl", true)
.expect("Should set nested value");
config
.set("logging.level", "info")
.expect("Should create nested structure");
assert!(config.get("new_feature").unwrap().as_bool().unwrap());
assert!(config.get("database.ssl").unwrap().as_bool().unwrap());
assert_eq!(
config.get("logging.level").unwrap().as_string().unwrap(),
"info"
);
let removed = config.remove("cache.ttl").expect("Should remove value");
assert!(removed.is_some());
assert_eq!(removed.unwrap().as_integer().unwrap(), 3600);
assert!(!config.contains_key("cache.ttl"));
assert!(config.is_modified());
config.mark_clean();
assert!(!config.is_modified());
let stats = config.stats();
assert!(stats.key_count > 0);
assert!(stats.depth > 0);
}
#[test]
fn test_config_builder() {
let source = r#"
name = "builder-test"
environment = "development"
[database]
host = "localhost"
"#;
let config = Config::builder()
.default_value("version", "1.0.0")
.default_value("debug", true)
.default_value("database.port", 5432)
.default_value("logging.level", "info")
.build_from_string(source)
.expect("Should build config");
assert_eq!(
config.get("name").unwrap().as_string().unwrap(),
"builder-test"
);
assert_eq!(
config.get("environment").unwrap().as_string().unwrap(),
"development"
);
assert_eq!(
config.get("database.host").unwrap().as_string().unwrap(),
"localhost"
);
assert_eq!(config.get("version").unwrap().as_string().unwrap(), "1.0.0");
assert!(config.get("debug").unwrap().as_bool().unwrap());
assert_eq!(
config.get("database.port").unwrap().as_integer().unwrap(),
5432
);
assert_eq!(
config.get("logging.level").unwrap().as_string().unwrap(),
"info"
);
assert!(!config.is_modified());
}
#[test]
fn test_config_merging() {
let base_config = Config::from_string(
r#"
name = "base-app"
version = "1.0.0"
[database]
host = "localhost"
port = 5432
[features]
analytics = false
"#,
)
.expect("Should create base config");
let override_config = Config::from_string(
r#"
version = "2.0.0"
environment = "production"
[database]
ssl = true
timeout = 30
[features]
analytics = true
monitoring = true
[logging]
level = "warn"
"#,
)
.expect("Should create override config");
let mut merged_config = base_config;
merged_config
.merge(&override_config)
.expect("Should merge configs");
assert_eq!(
merged_config.get("version").unwrap().as_string().unwrap(),
"2.0.0"
);
assert_eq!(
merged_config.get("name").unwrap().as_string().unwrap(),
"base-app"
);
assert_eq!(
merged_config
.get("database.host")
.unwrap()
.as_string()
.unwrap(),
"localhost"
);
assert_eq!(
merged_config
.get("database.port")
.unwrap()
.as_integer()
.unwrap(),
5432
);
assert_eq!(
merged_config
.get("environment")
.unwrap()
.as_string()
.unwrap(),
"production"
);
assert!(merged_config
.get("database.ssl")
.unwrap()
.as_bool()
.unwrap());
assert_eq!(
merged_config
.get("database.timeout")
.unwrap()
.as_integer()
.unwrap(),
30
);
assert!(merged_config
.get("features.analytics")
.unwrap()
.as_bool()
.unwrap());
assert!(merged_config
.get("features.monitoring")
.unwrap()
.as_bool()
.unwrap());
assert_eq!(
merged_config
.get("logging.level")
.unwrap()
.as_string()
.unwrap(),
"warn"
);
}
#[test]
fn test_validation() {
assert!(validate(r#"name = "valid""#).is_ok());
assert!(validate(
r#"
[section]
key = "value"
number = 42
array = [1, 2, 3]
"#
)
.is_ok());
assert!(validate(r#"name = "unterminated string"#).is_err());
assert!(validate(r#"[unclosed_section"#).is_err());
assert!(validate(r#"invalid_key_without_value"#).is_err());
assert!(validate(r#"key = [1, 2, 3"#).is_err()); }
#[test]
fn test_error_handling() {
let result = parse(
r#"
invalid = syntax error here
"#,
);
assert!(result.is_err());
let result = parse(
r#"
missing_var = env("DEFINITELY_NONEXISTENT_VARIABLE")
"#,
);
assert!(result.is_err());
let config = parse(r#"text = "not a number""#).expect("Should parse");
let result = config.get("text").unwrap().as_integer();
assert!(result.is_err());
let config = parse(r#"existing = "value""#).expect("Should parse");
assert!(config.get("nonexistent").is_none());
}
#[test]
fn test_custom_resolver_config() {
let mut env_vars = HashMap::new();
env_vars.insert("CUSTOM_VAR".to_string(), "custom_value".to_string());
let config = ResolverConfig {
base_path: None,
env_vars: Some(env_vars),
max_include_depth: 5,
allow_missing_env: true,
native_resolvers: HashMap::new(),
#[cfg(feature = "async")]
http_timeout: std::time::Duration::from_secs(30),
#[cfg(feature = "async")]
http_cache: Some(HashMap::new()),
};
let mut resolver = Resolver::with_config(config);
let source = r#"
test_var = env("CUSTOM_VAR")
missing_var = env("MISSING_VAR")
"#;
let document = parse_raw(source).expect("Should parse");
let result = resolver.resolve(&document).expect("Should resolve");
assert_eq!(
result.get("test_var").unwrap().as_string().unwrap(),
"custom_value"
);
assert!(result.get("missing_var").unwrap().is_null()); }
#[test]
fn test_value_type_conversions() {
let config = parse(
r#"
string_true = "true"
string_false = "false"
string_number = "42"
string_float = "3.14"
int_value = 100
float_value = 2.5
bool_value = true
"#,
)
.expect("Should parse");
assert!(config.get("string_true").unwrap().as_bool().unwrap());
assert!(!config.get("string_false").unwrap().as_bool().unwrap());
assert_eq!(
config.get("string_number").unwrap().as_integer().unwrap(),
42
);
#[allow(clippy::approx_constant)]
let expected_pi_str = 3.14;
assert_eq!(
config.get("string_float").unwrap().as_float().unwrap(),
expected_pi_str
);
let exact_float_config = parse(r#"exact = 42.0"#).expect("Should parse");
assert_eq!(
exact_float_config
.get("exact")
.unwrap()
.as_integer()
.unwrap(),
42
);
assert!(config.get("string_true").unwrap().is_string());
assert!(config.get("int_value").unwrap().is_number());
assert!(config.get("float_value").unwrap().is_number());
assert!(config.get("bool_value").unwrap().is_bool());
}
#[test]
fn test_advanced_array_operations() {
let config = parse(
r#"
# Different array formats
simple = [1, 2, 3]
multiline = [
"item1",
"item2",
"item3",
]
nested = [[1, 2], [3, 4], [5, 6]]
mixed_types = [
{ name = "first", value = 1 },
{ name = "second", value = 2 },
]
"#,
)
.expect("Should parse");
let simple = config.get("simple").unwrap().as_array().unwrap();
assert_eq!(simple.len(), 3);
assert_eq!(simple[1].as_integer().unwrap(), 2);
let multiline = config.get("multiline").unwrap().as_array().unwrap();
assert_eq!(multiline.len(), 3);
assert_eq!(multiline[0].as_string().unwrap(), "item1");
let nested = config.get("nested").unwrap().as_array().unwrap();
assert_eq!(nested.len(), 3);
let first_sub = nested[0].as_array().unwrap();
assert_eq!(first_sub[0].as_integer().unwrap(), 1);
assert_eq!(first_sub[1].as_integer().unwrap(), 2);
let mixed = config.get("mixed_types").unwrap().as_array().unwrap();
assert_eq!(mixed.len(), 2);
assert_eq!(mixed[0].get("name").unwrap().as_string().unwrap(), "first");
assert_eq!(mixed[0].get("value").unwrap().as_integer().unwrap(), 1);
}
#[test]
fn test_comprehensive_example() {
let source = r#"
# Comprehensive NOML example showcasing all features
# Basic metadata
name = "comprehensive-app"
version = "2.1.0"
description = "A comprehensive application demonstrating all NOML language features and capabilities."
# Environment-driven configuration
debug = env("DEBUG", false)
log_level = env("LOG_LEVEL", "info")
# Native types for better semantics
max_file_size = @size("100MB")
request_timeout = @duration("30s")
backup_interval = @duration("24h")
api_base_url = @url("https://api.example.com")
# Complex nested configuration
[database]
host = env("DB_HOST", "localhost")
port = env("DB_PORT", "5432")
name = "app_db"
ssl_mode = "require"
[database.pool]
min_size = 5
max_size = 50
timeout = @duration("10s")
[database.migrations]
auto_run = true
directory = "./migrations"
# Multiple cache backends
[[cache.backends]]
type = "redis"
host = "redis-1.internal"
port = 6379
db = 0
[[cache.backends]]
type = "redis"
host = "redis-2.internal"
port = 6379
db = 1
# Feature flags with complex conditions
[features]
analytics = { enabled = true, sample_rate = 0.1 }
new_ui = { enabled = env("ENABLE_NEW_UI", false), rollout_percentage = 25 }
advanced_search = { enabled = true, backends = ["elasticsearch", "solr"] }
# Service discovery configuration
[services.user_service]
base_url = @url("https://users.internal.com")
timeout = @duration("5s")
retries = 3
[services.payment_service]
base_url = @url("https://payments.internal.com")
timeout = @duration("15s")
retries = 5
# Complex monitoring setup
[monitoring]
enabled = true
metrics_interval = @duration("60s")
health_check_interval = @duration("30s")
[monitoring.alerts]
cpu_threshold = 80.0
memory_threshold = @size("1GB")
disk_threshold = @size("10GB")
response_time_threshold = @duration("2s")
# Comprehensive logging configuration
[logging]
level = "info" # Reference to environment variable
format = "json"
output = ["stdout", "file"]
[logging.file]
path = "/var/log/app/app.log"
max_size = @size("50MB")
max_files = 10
compress = true
# Security configuration
[security]
encryption_key = env("ENCRYPTION_KEY", "default-key-for-testing")
session_timeout = @duration("30m")
max_login_attempts = 5
lockout_duration = @duration("15m")
[security.cors]
allowed_origins = ["https://app.example.com", "https://admin.example.com"]
allowed_methods = ["GET", "POST", "PUT", "DELETE"]
max_age = @duration("1h")
"#;
let config = parse(source).expect("Should parse comprehensive example");
assert_eq!(
config.get("name").unwrap().as_string().unwrap(),
"comprehensive-app"
);
assert_eq!(config.get("version").unwrap().as_string().unwrap(), "2.1.0");
assert!(config
.get("description")
.unwrap()
.as_string()
.unwrap()
.contains("comprehensive"));
assert_eq!(
config.get("max_file_size").unwrap().as_integer().unwrap(),
100 * 1024 * 1024
);
assert_eq!(
config.get("request_timeout").unwrap().as_float().unwrap(),
30.0
);
assert_eq!(
config.get("backup_interval").unwrap().as_float().unwrap(),
86400.0
);
assert_eq!(
config.get("api_base_url").unwrap().as_string().unwrap(),
"https://api.example.com"
);
assert_eq!(
config.get("database.name").unwrap().as_string().unwrap(),
"app_db"
);
assert_eq!(
config
.get("database.ssl_mode")
.unwrap()
.as_string()
.unwrap(),
"require"
);
assert_eq!(
config
.get("database.pool.min_size")
.unwrap()
.as_integer()
.unwrap(),
5
);
assert_eq!(
config
.get("database.pool.max_size")
.unwrap()
.as_integer()
.unwrap(),
50
);
assert_eq!(
config
.get("database.pool.timeout")
.unwrap()
.as_float()
.unwrap(),
10.0
);
let cache_backends = config.get("cache.backends").unwrap().as_array().unwrap();
assert_eq!(cache_backends.len(), 2);
assert_eq!(
cache_backends[0].get("type").unwrap().as_string().unwrap(),
"redis"
);
assert_eq!(
cache_backends[0].get("host").unwrap().as_string().unwrap(),
"redis-1.internal"
);
assert_eq!(
cache_backends[1].get("host").unwrap().as_string().unwrap(),
"redis-2.internal"
);
let analytics = config.get("features.analytics").unwrap();
assert!(analytics.get("enabled").unwrap().as_bool().unwrap());
assert_eq!(
analytics.get("sample_rate").unwrap().as_float().unwrap(),
0.1
);
if let Some(user_service_url) = config.get("services.user_service.base_url") {
assert_eq!(
user_service_url.as_string().unwrap(),
"https://users.internal.com"
);
}
if let Some(user_service_timeout) = config.get("services.user_service.timeout") {
assert_eq!(user_service_timeout.as_float().unwrap(), 5.0);
}
if let Some(user_service_retries) = config.get("services.user_service.retries") {
assert_eq!(user_service_retries.as_integer().unwrap(), 3);
}
assert_eq!(
config
.get("monitoring.alerts.cpu_threshold")
.unwrap()
.as_float()
.unwrap(),
80.0
);
assert_eq!(
config
.get("monitoring.alerts.memory_threshold")
.unwrap()
.as_integer()
.unwrap(),
1024 * 1024 * 1024
);
assert_eq!(
config
.get("monitoring.alerts.response_time_threshold")
.unwrap()
.as_float()
.unwrap(),
2.0
);
let allowed_origins = config
.get("security.cors.allowed_origins")
.unwrap()
.as_array()
.unwrap();
assert_eq!(allowed_origins.len(), 2);
assert_eq!(
allowed_origins[0].as_string().unwrap(),
"https://app.example.com"
);
assert_eq!(
allowed_origins[1].as_string().unwrap(),
"https://admin.example.com"
);
let allowed_methods = config
.get("security.cors.allowed_methods")
.unwrap()
.as_array()
.unwrap();
assert_eq!(allowed_methods.len(), 4);
assert_eq!(allowed_methods[0].as_string().unwrap(), "GET");
assert_eq!(allowed_methods[3].as_string().unwrap(), "DELETE");
}