feat: add comprehensive test coverage for config module

This commit is contained in:
overtrue
2025-05-27 23:11:29 +08:00
parent 08a6cacdc2
commit dbe573513e
4 changed files with 701 additions and 1 deletions
+171
View File
@@ -21,3 +21,174 @@ impl Default for RustFsConfig {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rustfs_config_new() {
let config = RustFsConfig::new();
// Verify that observability config is properly initialized
assert!(!config.observability.sinks.is_empty(), "Observability sinks should not be empty");
assert!(config.observability.logger.is_some(), "Logger config should be present");
// Verify that event config is properly initialized
assert!(!config.event.store_path.is_empty(), "Event store path should not be empty");
assert!(config.event.channel_capacity > 0, "Channel capacity should be positive");
assert!(!config.event.adapters.is_empty(), "Event adapters should not be empty");
}
#[test]
fn test_rustfs_config_default() {
let config = RustFsConfig::default();
// Default should be equivalent to new()
let new_config = RustFsConfig::new();
// Compare observability config
assert_eq!(config.observability.sinks.len(), new_config.observability.sinks.len());
assert_eq!(config.observability.logger.is_some(), new_config.observability.logger.is_some());
// Compare event config
assert_eq!(config.event.store_path, new_config.event.store_path);
assert_eq!(config.event.channel_capacity, new_config.event.channel_capacity);
assert_eq!(config.event.adapters.len(), new_config.event.adapters.len());
}
#[test]
fn test_rustfs_config_components_independence() {
let mut config = RustFsConfig::new();
// Modify observability config
config.observability.sinks.clear();
// Event config should remain unchanged
assert!(!config.event.adapters.is_empty(), "Event adapters should remain unchanged");
assert!(config.event.channel_capacity > 0, "Channel capacity should remain unchanged");
// Create new config to verify independence
let new_config = RustFsConfig::new();
assert!(!new_config.observability.sinks.is_empty(), "New config should have default sinks");
}
#[test]
fn test_rustfs_config_observability_integration() {
let config = RustFsConfig::new();
// Test observability config properties
assert!(config.observability.otel.endpoint.is_empty() || !config.observability.otel.endpoint.is_empty());
assert!(config.observability.otel.use_stdout.is_some());
assert!(config.observability.otel.sample_ratio.is_some());
assert!(config.observability.otel.meter_interval.is_some());
assert!(config.observability.otel.service_name.is_some());
assert!(config.observability.otel.service_version.is_some());
assert!(config.observability.otel.environment.is_some());
assert!(config.observability.otel.logger_level.is_some());
}
#[test]
fn test_rustfs_config_event_integration() {
let config = RustFsConfig::new();
// Test event config properties
assert!(!config.event.store_path.is_empty(), "Store path should not be empty");
assert!(config.event.channel_capacity >= 1000, "Channel capacity should be reasonable for production");
// Test that store path is a valid path format
let store_path = &config.event.store_path;
assert!(!store_path.contains('\0'), "Store path should not contain null characters");
// Test adapters configuration
for adapter in &config.event.adapters {
// Each adapter should have a valid configuration
match adapter {
crate::event::adapters::AdapterConfig::Webhook(_) => {
// Webhook adapter should be properly configured
},
crate::event::adapters::AdapterConfig::Kafka(_) => {
// Kafka adapter should be properly configured
},
crate::event::adapters::AdapterConfig::Mqtt(_) => {
// MQTT adapter should be properly configured
},
}
}
}
#[test]
fn test_rustfs_config_memory_usage() {
// Test that config doesn't use excessive memory
let config = RustFsConfig::new();
// Basic memory usage checks
assert!(std::mem::size_of_val(&config) < 10000, "Config should not use excessive memory");
// Test that strings are not excessively long
assert!(config.event.store_path.len() < 1000, "Store path should not be excessively long");
// Test that collections are reasonably sized
assert!(config.observability.sinks.len() < 100, "Sinks collection should be reasonably sized");
assert!(config.event.adapters.len() < 100, "Adapters collection should be reasonably sized");
}
#[test]
fn test_rustfs_config_serialization_compatibility() {
let config = RustFsConfig::new();
// Test that observability config can be serialized (it has Serialize trait)
let observability_json = serde_json::to_string(&config.observability);
assert!(observability_json.is_ok(), "Observability config should be serializable");
// Test that event config can be serialized (it has Serialize trait)
let event_json = serde_json::to_string(&config.event);
assert!(event_json.is_ok(), "Event config should be serializable");
}
#[test]
fn test_rustfs_config_debug_format() {
let config = RustFsConfig::new();
// Test that observability config has Debug trait
let observability_debug = format!("{:?}", config.observability);
assert!(!observability_debug.is_empty(), "Observability config should have debug output");
assert!(observability_debug.contains("ObservabilityConfig"), "Debug output should contain type name");
// Test that event config has Debug trait
let event_debug = format!("{:?}", config.event);
assert!(!event_debug.is_empty(), "Event config should have debug output");
assert!(event_debug.contains("NotifierConfig"), "Debug output should contain type name");
}
#[test]
fn test_rustfs_config_clone_behavior() {
let config = RustFsConfig::new();
// Test that observability config can be cloned
let observability_clone = config.observability.clone();
assert_eq!(observability_clone.sinks.len(), config.observability.sinks.len());
// Test that event config can be cloned
let event_clone = config.event.clone();
assert_eq!(event_clone.store_path, config.event.store_path);
assert_eq!(event_clone.channel_capacity, config.event.channel_capacity);
}
#[test]
fn test_rustfs_config_environment_independence() {
// Test that config creation doesn't depend on specific environment variables
// This test ensures the config can be created in any environment
let config1 = RustFsConfig::new();
let config2 = RustFsConfig::new();
// Both configs should have the same structure
assert_eq!(config1.observability.sinks.len(), config2.observability.sinks.len());
assert_eq!(config1.event.adapters.len(), config2.event.adapters.len());
// Store paths should be consistent
assert_eq!(config1.event.store_path, config2.event.store_path);
assert_eq!(config1.event.channel_capacity, config2.event.channel_capacity);
}
}
+1 -1
View File
@@ -117,7 +117,7 @@ mod tests {
"Log level should be a valid tracing level"
);
assert_eq!(USE_STDOUT, true);
assert_eq!(USE_STDOUT, false);
assert_eq!(SAMPLE_RATIO, 1.0);
assert!(SAMPLE_RATIO >= 0.0 && SAMPLE_RATIO <= 1.0, "Sample ratio should be between 0.0 and 1.0");
+281
View File
@@ -41,3 +41,284 @@ fn default_store_path() -> String {
fn default_channel_capacity() -> usize {
10000 // Reasonable default values for high concurrency systems
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn test_notifier_config_new() {
let config = NotifierConfig::new();
// Verify store path is set
assert!(!config.store_path.is_empty(), "Store path should not be empty");
assert!(config.store_path.contains("event-notification"), "Store path should contain event-notification");
// Verify channel capacity is reasonable
assert_eq!(config.channel_capacity, 10000, "Channel capacity should be 10000");
assert!(config.channel_capacity > 0, "Channel capacity should be positive");
// Verify adapters are initialized
assert!(!config.adapters.is_empty(), "Adapters should not be empty");
assert_eq!(config.adapters.len(), 1, "Should have exactly one default adapter");
}
#[test]
fn test_notifier_config_default() {
let config = NotifierConfig::default();
let new_config = NotifierConfig::new();
// Default should be equivalent to new()
assert_eq!(config.store_path, new_config.store_path);
assert_eq!(config.channel_capacity, new_config.channel_capacity);
assert_eq!(config.adapters.len(), new_config.adapters.len());
}
#[test]
fn test_default_store_path() {
let store_path = default_store_path();
// Verify store path properties
assert!(!store_path.is_empty(), "Store path should not be empty");
assert!(store_path.contains("event-notification"), "Store path should contain event-notification");
// Verify it's a valid path format
let path = Path::new(&store_path);
assert!(path.is_absolute() || path.is_relative(), "Store path should be a valid path");
// Verify it doesn't contain invalid characters
assert!(!store_path.contains('\0'), "Store path should not contain null characters");
// Verify it's based on temp directory
let temp_dir = env::temp_dir();
let expected_path = temp_dir.join("event-notification");
assert_eq!(store_path, expected_path.to_string_lossy().to_string());
}
#[test]
fn test_default_channel_capacity() {
let capacity = default_channel_capacity();
// Verify capacity is reasonable
assert_eq!(capacity, 10000, "Default capacity should be 10000");
assert!(capacity > 0, "Capacity should be positive");
assert!(capacity >= 1000, "Capacity should be at least 1000 for production use");
assert!(capacity <= 1_000_000, "Capacity should not be excessively large");
}
#[test]
fn test_notifier_config_serialization() {
let config = NotifierConfig::new();
// Test serialization to JSON
let json_result = serde_json::to_string(&config);
assert!(json_result.is_ok(), "Config should be serializable to JSON");
let json_str = json_result.unwrap();
assert!(!json_str.is_empty(), "Serialized JSON should not be empty");
assert!(json_str.contains("store_path"), "JSON should contain store_path");
assert!(json_str.contains("channel_capacity"), "JSON should contain channel_capacity");
assert!(json_str.contains("adapters"), "JSON should contain adapters");
// Test deserialization from JSON
let deserialized_result: Result<NotifierConfig, _> = serde_json::from_str(&json_str);
assert!(deserialized_result.is_ok(), "Config should be deserializable from JSON");
let deserialized_config = deserialized_result.unwrap();
assert_eq!(deserialized_config.store_path, config.store_path);
assert_eq!(deserialized_config.channel_capacity, config.channel_capacity);
assert_eq!(deserialized_config.adapters.len(), config.adapters.len());
}
#[test]
fn test_notifier_config_serialization_with_defaults() {
// Test serialization with minimal JSON (using serde defaults)
let minimal_json = r#"{"adapters": []}"#;
let deserialized_result: Result<NotifierConfig, _> = serde_json::from_str(minimal_json);
assert!(deserialized_result.is_ok(), "Config should deserialize with defaults");
let config = deserialized_result.unwrap();
assert_eq!(config.store_path, default_store_path(), "Should use default store path");
assert_eq!(config.channel_capacity, default_channel_capacity(), "Should use default channel capacity");
assert!(config.adapters.is_empty(), "Should have empty adapters as specified");
}
#[test]
fn test_notifier_config_debug_format() {
let config = NotifierConfig::new();
let debug_str = format!("{:?}", config);
assert!(!debug_str.is_empty(), "Debug output should not be empty");
assert!(debug_str.contains("NotifierConfig"), "Debug output should contain struct name");
assert!(debug_str.contains("store_path"), "Debug output should contain store_path field");
assert!(debug_str.contains("channel_capacity"), "Debug output should contain channel_capacity field");
assert!(debug_str.contains("adapters"), "Debug output should contain adapters field");
}
#[test]
fn test_notifier_config_clone() {
let config = NotifierConfig::new();
let cloned_config = config.clone();
// Test that clone creates an independent copy
assert_eq!(cloned_config.store_path, config.store_path);
assert_eq!(cloned_config.channel_capacity, config.channel_capacity);
assert_eq!(cloned_config.adapters.len(), config.adapters.len());
// Verify they are independent (modifying one doesn't affect the other)
let mut modified_config = config.clone();
modified_config.channel_capacity = 5000;
assert_ne!(modified_config.channel_capacity, config.channel_capacity);
assert_eq!(cloned_config.channel_capacity, config.channel_capacity);
}
#[test]
fn test_notifier_config_modification() {
let mut config = NotifierConfig::new();
// Test modifying store path
let original_store_path = config.store_path.clone();
config.store_path = "/custom/path".to_string();
assert_ne!(config.store_path, original_store_path);
assert_eq!(config.store_path, "/custom/path");
// Test modifying channel capacity
let original_capacity = config.channel_capacity;
config.channel_capacity = 5000;
assert_ne!(config.channel_capacity, original_capacity);
assert_eq!(config.channel_capacity, 5000);
// Test modifying adapters
let original_adapters_len = config.adapters.len();
config.adapters.push(AdapterConfig::new());
assert_eq!(config.adapters.len(), original_adapters_len + 1);
// Test clearing adapters
config.adapters.clear();
assert!(config.adapters.is_empty());
}
#[test]
fn test_notifier_config_adapters() {
let config = NotifierConfig::new();
// Test default adapter configuration
assert_eq!(config.adapters.len(), 1, "Should have exactly one default adapter");
// Test that we can add more adapters
let mut config_mut = config.clone();
config_mut.adapters.push(AdapterConfig::new());
assert_eq!(config_mut.adapters.len(), 2, "Should be able to add more adapters");
// Test adapter types
for adapter in &config.adapters {
match adapter {
AdapterConfig::Webhook(_) => {
// Webhook adapter should be properly configured
},
AdapterConfig::Kafka(_) => {
// Kafka adapter should be properly configured
},
AdapterConfig::Mqtt(_) => {
// MQTT adapter should be properly configured
},
}
}
}
#[test]
fn test_notifier_config_edge_cases() {
// Test with empty adapters
let mut config = NotifierConfig::new();
config.adapters.clear();
assert!(config.adapters.is_empty(), "Adapters should be empty after clearing");
// Test serialization with empty adapters
let json_result = serde_json::to_string(&config);
assert!(json_result.is_ok(), "Config with empty adapters should be serializable");
// Test with very large channel capacity
config.channel_capacity = 1_000_000;
assert_eq!(config.channel_capacity, 1_000_000);
// Test with minimum channel capacity
config.channel_capacity = 1;
assert_eq!(config.channel_capacity, 1);
// Test with empty store path
config.store_path = String::new();
assert!(config.store_path.is_empty());
}
#[test]
fn test_notifier_config_memory_efficiency() {
let config = NotifierConfig::new();
// Test that config doesn't use excessive memory
let config_size = std::mem::size_of_val(&config);
assert!(config_size < 5000, "Config should not use excessive memory");
// Test that store path is not excessively long
assert!(config.store_path.len() < 1000, "Store path should not be excessively long");
// Test that adapters collection is reasonably sized
assert!(config.adapters.len() < 100, "Adapters collection should be reasonably sized");
}
#[test]
fn test_notifier_config_consistency() {
// Create multiple configs and ensure they're consistent
let config1 = NotifierConfig::new();
let config2 = NotifierConfig::new();
// Both configs should have the same default values
assert_eq!(config1.store_path, config2.store_path);
assert_eq!(config1.channel_capacity, config2.channel_capacity);
assert_eq!(config1.adapters.len(), config2.adapters.len());
}
#[test]
fn test_notifier_config_path_validation() {
let config = NotifierConfig::new();
// Test that store path is a valid path
let path = Path::new(&config.store_path);
// Path should be valid
assert!(path.components().count() > 0, "Path should have components");
// Path should not contain invalid characters for most filesystems
assert!(!config.store_path.contains('\0'), "Path should not contain null characters");
assert!(!config.store_path.contains('\x01'), "Path should not contain control characters");
// Path should be reasonable length
assert!(config.store_path.len() < 260, "Path should be shorter than Windows MAX_PATH");
}
#[test]
fn test_notifier_config_production_readiness() {
let config = NotifierConfig::new();
// Test production readiness criteria
assert!(config.channel_capacity >= 1000, "Channel capacity should be sufficient for production");
assert!(!config.store_path.is_empty(), "Store path should be configured");
assert!(!config.adapters.is_empty(), "At least one adapter should be configured");
// Test that configuration is reasonable for high-load scenarios
assert!(config.channel_capacity <= 10_000_000, "Channel capacity should not be excessive");
// Test that store path is in a reasonable location (temp directory)
assert!(config.store_path.contains("event-notification"), "Store path should be identifiable");
}
#[test]
fn test_default_config_file_constant() {
// Test that the constant is properly defined
assert_eq!(DEFAULT_CONFIG_FILE, "event");
assert!(!DEFAULT_CONFIG_FILE.is_empty(), "Config file name should not be empty");
assert!(!DEFAULT_CONFIG_FILE.contains('/'), "Config file name should not contain path separators");
assert!(!DEFAULT_CONFIG_FILE.contains('\\'), "Config file name should not contain Windows path separators");
}
}
+248
View File
@@ -26,3 +26,251 @@ impl Default for ObservabilityConfig {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_observability_config_new() {
let config = ObservabilityConfig::new();
// Verify OTEL config is initialized
assert!(config.otel.use_stdout.is_some(), "OTEL use_stdout should be configured");
assert!(config.otel.sample_ratio.is_some(), "OTEL sample_ratio should be configured");
assert!(config.otel.meter_interval.is_some(), "OTEL meter_interval should be configured");
assert!(config.otel.service_name.is_some(), "OTEL service_name should be configured");
assert!(config.otel.service_version.is_some(), "OTEL service_version should be configured");
assert!(config.otel.environment.is_some(), "OTEL environment should be configured");
assert!(config.otel.logger_level.is_some(), "OTEL logger_level should be configured");
// Verify sinks are initialized
assert!(!config.sinks.is_empty(), "Sinks should not be empty");
assert_eq!(config.sinks.len(), 1, "Should have exactly one default sink");
// Verify logger is initialized
assert!(config.logger.is_some(), "Logger should be configured");
}
#[test]
fn test_observability_config_default() {
let config = ObservabilityConfig::default();
let new_config = ObservabilityConfig::new();
// Default should be equivalent to new()
assert_eq!(config.sinks.len(), new_config.sinks.len());
assert_eq!(config.logger.is_some(), new_config.logger.is_some());
// OTEL configs should be equivalent
assert_eq!(config.otel.use_stdout, new_config.otel.use_stdout);
assert_eq!(config.otel.sample_ratio, new_config.otel.sample_ratio);
assert_eq!(config.otel.meter_interval, new_config.otel.meter_interval);
assert_eq!(config.otel.service_name, new_config.otel.service_name);
assert_eq!(config.otel.service_version, new_config.otel.service_version);
assert_eq!(config.otel.environment, new_config.otel.environment);
assert_eq!(config.otel.logger_level, new_config.otel.logger_level);
}
#[test]
fn test_observability_config_otel_defaults() {
let config = ObservabilityConfig::new();
// Test OTEL default values
if let Some(use_stdout) = config.otel.use_stdout {
assert!(use_stdout == true || use_stdout == false, "use_stdout should be a valid boolean");
}
if let Some(sample_ratio) = config.otel.sample_ratio {
assert!(sample_ratio >= 0.0 && sample_ratio <= 1.0, "Sample ratio should be between 0.0 and 1.0");
}
if let Some(meter_interval) = config.otel.meter_interval {
assert!(meter_interval > 0, "Meter interval should be positive");
assert!(meter_interval <= 3600, "Meter interval should be reasonable (≤ 1 hour)");
}
if let Some(service_name) = &config.otel.service_name {
assert!(!service_name.is_empty(), "Service name should not be empty");
assert!(!service_name.contains(' '), "Service name should not contain spaces");
}
if let Some(service_version) = &config.otel.service_version {
assert!(!service_version.is_empty(), "Service version should not be empty");
}
if let Some(environment) = &config.otel.environment {
assert!(!environment.is_empty(), "Environment should not be empty");
assert!(
["development", "staging", "production", "test"].contains(&environment.as_str()),
"Environment should be a standard environment name"
);
}
if let Some(logger_level) = &config.otel.logger_level {
assert!(
["trace", "debug", "info", "warn", "error"].contains(&logger_level.as_str()),
"Logger level should be a valid tracing level"
);
}
}
#[test]
fn test_observability_config_sinks() {
let config = ObservabilityConfig::new();
// Test default sink configuration
assert_eq!(config.sinks.len(), 1, "Should have exactly one default sink");
let _default_sink = &config.sinks[0];
// Test that the sink has valid configuration
// Note: We can't test specific values without knowing SinkConfig implementation
// but we can test that it's properly initialized
// Test that we can add more sinks
let mut config_mut = config.clone();
config_mut.sinks.push(SinkConfig::new());
assert_eq!(config_mut.sinks.len(), 2, "Should be able to add more sinks");
}
#[test]
fn test_observability_config_logger() {
let config = ObservabilityConfig::new();
// Test logger configuration
assert!(config.logger.is_some(), "Logger should be configured by default");
if let Some(_logger) = &config.logger {
// Test that logger has valid configuration
// Note: We can't test specific values without knowing LoggerConfig implementation
// but we can test that it's properly initialized
}
// Test that logger can be disabled
let mut config_mut = config.clone();
config_mut.logger = None;
assert!(config_mut.logger.is_none(), "Logger should be able to be disabled");
}
#[test]
fn test_observability_config_serialization() {
let config = ObservabilityConfig::new();
// Test serialization to JSON
let json_result = serde_json::to_string(&config);
assert!(json_result.is_ok(), "Config should be serializable to JSON");
let json_str = json_result.unwrap();
assert!(!json_str.is_empty(), "Serialized JSON should not be empty");
assert!(json_str.contains("otel"), "JSON should contain otel configuration");
assert!(json_str.contains("sinks"), "JSON should contain sinks configuration");
assert!(json_str.contains("logger"), "JSON should contain logger configuration");
// Test deserialization from JSON
let deserialized_result: Result<ObservabilityConfig, _> = serde_json::from_str(&json_str);
assert!(deserialized_result.is_ok(), "Config should be deserializable from JSON");
let deserialized_config = deserialized_result.unwrap();
assert_eq!(deserialized_config.sinks.len(), config.sinks.len());
assert_eq!(deserialized_config.logger.is_some(), config.logger.is_some());
}
#[test]
fn test_observability_config_debug_format() {
let config = ObservabilityConfig::new();
let debug_str = format!("{:?}", config);
assert!(!debug_str.is_empty(), "Debug output should not be empty");
assert!(debug_str.contains("ObservabilityConfig"), "Debug output should contain struct name");
assert!(debug_str.contains("otel"), "Debug output should contain otel field");
assert!(debug_str.contains("sinks"), "Debug output should contain sinks field");
assert!(debug_str.contains("logger"), "Debug output should contain logger field");
}
#[test]
fn test_observability_config_clone() {
let config = ObservabilityConfig::new();
let cloned_config = config.clone();
// Test that clone creates an independent copy
assert_eq!(cloned_config.sinks.len(), config.sinks.len());
assert_eq!(cloned_config.logger.is_some(), config.logger.is_some());
assert_eq!(cloned_config.otel.endpoint, config.otel.endpoint);
assert_eq!(cloned_config.otel.use_stdout, config.otel.use_stdout);
assert_eq!(cloned_config.otel.sample_ratio, config.otel.sample_ratio);
assert_eq!(cloned_config.otel.meter_interval, config.otel.meter_interval);
assert_eq!(cloned_config.otel.service_name, config.otel.service_name);
assert_eq!(cloned_config.otel.service_version, config.otel.service_version);
assert_eq!(cloned_config.otel.environment, config.otel.environment);
assert_eq!(cloned_config.otel.logger_level, config.otel.logger_level);
}
#[test]
fn test_observability_config_modification() {
let mut config = ObservabilityConfig::new();
// Test modifying OTEL endpoint
let original_endpoint = config.otel.endpoint.clone();
config.otel.endpoint = "http://localhost:4317".to_string();
assert_ne!(config.otel.endpoint, original_endpoint);
assert_eq!(config.otel.endpoint, "http://localhost:4317");
// Test modifying sinks
let original_sinks_len = config.sinks.len();
config.sinks.push(SinkConfig::new());
assert_eq!(config.sinks.len(), original_sinks_len + 1);
// Test disabling logger
config.logger = None;
assert!(config.logger.is_none());
}
#[test]
fn test_observability_config_edge_cases() {
// Test with empty sinks
let mut config = ObservabilityConfig::new();
config.sinks.clear();
assert!(config.sinks.is_empty(), "Sinks should be empty after clearing");
// Test serialization with empty sinks
let json_result = serde_json::to_string(&config);
assert!(json_result.is_ok(), "Config with empty sinks should be serializable");
// Test with no logger
config.logger = None;
let json_result = serde_json::to_string(&config);
assert!(json_result.is_ok(), "Config with no logger should be serializable");
}
#[test]
fn test_observability_config_memory_efficiency() {
let config = ObservabilityConfig::new();
// Test that config doesn't use excessive memory
let config_size = std::mem::size_of_val(&config);
assert!(config_size < 5000, "Config should not use excessive memory");
// Test that endpoint string is not excessively long
assert!(config.otel.endpoint.len() < 1000, "Endpoint should not be excessively long");
// Test that collections are reasonably sized
assert!(config.sinks.len() < 100, "Sinks collection should be reasonably sized");
}
#[test]
fn test_observability_config_consistency() {
// Create multiple configs and ensure they're consistent
let config1 = ObservabilityConfig::new();
let config2 = ObservabilityConfig::new();
// Both configs should have the same default structure
assert_eq!(config1.sinks.len(), config2.sinks.len());
assert_eq!(config1.logger.is_some(), config2.logger.is_some());
assert_eq!(config1.otel.use_stdout, config2.otel.use_stdout);
assert_eq!(config1.otel.sample_ratio, config2.otel.sample_ratio);
assert_eq!(config1.otel.meter_interval, config2.otel.meter_interval);
assert_eq!(config1.otel.service_name, config2.otel.service_name);
assert_eq!(config1.otel.service_version, config2.otel.service_version);
assert_eq!(config1.otel.environment, config2.otel.environment);
assert_eq!(config1.otel.logger_level, config2.otel.logger_level);
}
}