Configuration Management Test Plan
This comprehensive test plan follows Test-Driven Development (TDD) principles to ensure systematic testing of all Configuration Management functionalities. The plan covers unit tests, integration tests, performance tests, security tests, and end-to-end scenarios.
Test-Driven Development Strategy
TDD Cycle Implementation
Red-Green-Refactor Cycle:
- Red: Write failing tests first
- Green: Write minimal code to make tests pass
- Refactor: Improve code while keeping tests green
Test Pyramid Structure
Unit Test Specifications
1. Configuration Discovery Tests
1.1 Discovery Engine Tests
#[cfg(test)]
mod discovery_engine_tests {
use super::*;
use std::path::PathBuf;
use tempfile::TempDir;
use tokio::fs;
#[tokio::test]
async fn test_current_directory_discovery_toml_priority() {
// Arrange
let temp_dir = TempDir::new().unwrap();
let config_dir = temp_dir.path();
// Create multiple format files
fs::write(config_dir.join("test-module.toml"), "[section]\nkey = \"toml_value\"").await.unwrap();
fs::write(config_dir.join("test-module.yaml"), "section:\n key: yaml_value").await.unwrap();
fs::write(config_dir.join("test-module.json"), r#"{"section": {"key": "json_value"}}"#).await.unwrap();
let handler = CurrentDirectoryHandler::new(vec![
ConfigurationFormat::Toml,
ConfigurationFormat::Yaml,
ConfigurationFormat::Json,
]);
// Act
let result = handler.discover("test-module").await.unwrap();
// Assert
assert!(result.is_some());
let files = result.unwrap();
assert_eq!(files.len(), 1);
assert_eq!(files[0].format, ConfigurationFormat::Toml);
assert!(files[0].path.ends_with("test-module.toml"));
}
#[tokio::test]
async fn test_discovery_chain_priority_order() {
// Arrange
let discovery = ConfigurationDiscovery::builder()
.build()
.unwrap();
// Act & Assert - Test that current directory has highest priority
// Implementation would test the full chain
}
#[tokio::test]
async fn test_home_directory_discovery() {
// Test ~/.sindhan directory discovery
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_environment_directory_discovery() {
// Test SINDHAN_CONFIG_DIR environment variable
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_discovery_with_no_files_returns_default() {
// Arrange
let temp_dir = TempDir::new().unwrap();
let handler = CurrentDirectoryHandler::new(vec![ConfigurationFormat::Toml]);
// Act
let result = handler.discover("nonexistent-module").await.unwrap();
// Assert
assert!(result.is_none());
}
#[tokio::test]
async fn test_custom_search_path_discovery() {
// Test custom search paths added via builder
// Arrange, Act, Assert pattern
}
}1.2 File Format Detection Tests
#[cfg(test)]
mod format_detection_tests {
use super::*;
#[test]
fn test_toml_format_detection() {
// Arrange
let toml_content = r#"
[database]
host = "localhost"
port = 5432
"#;
let parser = TomlParser::new();
// Act
let can_parse = parser.can_parse(toml_content);
// Assert
assert!(can_parse);
}
#[test]
fn test_yaml_format_detection() {
// Arrange
let yaml_content = r#"
database:
host: localhost
port: 5432
"#;
let parser = YamlParser::new();
// Act
let can_parse = parser.can_parse(yaml_content);
// Assert
assert!(can_parse);
}
#[test]
fn test_json_format_detection() {
// Arrange
let json_content = r#"
{
"database": {
"host": "localhost",
"port": 5432
}
}
"#;
let parser = JsonParser::new();
// Act
let can_parse = parser.can_parse(json_content);
// Assert
assert!(can_parse);
}
#[test]
fn test_format_detection_priority() {
// Test that format detection follows priority order
let factory = FormatParserFactory::new();
// Test implementation
}
}2. Configuration Parsing Tests
2.1 TOML Parser Tests
#[cfg(test)]
mod toml_parser_tests {
use super::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, PartialEq)]
struct TestConfig {
database: DatabaseConfig,
logging: LoggingConfig,
}
#[derive(Debug, Deserialize, Serialize, PartialEq)]
struct DatabaseConfig {
host: String,
port: u16,
ssl: bool,
}
#[derive(Debug, Deserialize, Serialize, PartialEq)]
struct LoggingConfig {
level: String,
file: Option<String>,
}
#[tokio::test]
async fn test_parse_valid_toml() {
// Arrange
let toml_content = r#"
[database]
host = "localhost"
port = 5432
ssl = true
[logging]
level = "info"
file = "/var/log/app.log"
"#;
let parser = TomlParser::new();
// Act
let result: Result<TestConfig> = parser.parse(toml_content).await;
// Assert
assert!(result.is_ok());
let config = result.unwrap();
assert_eq!(config.database.host, "localhost");
assert_eq!(config.database.port, 5432);
assert!(config.database.ssl);
assert_eq!(config.logging.level, "info");
assert_eq!(config.logging.file, Some("/var/log/app.log".to_string()));
}
#[tokio::test]
async fn test_parse_invalid_toml_syntax() {
// Arrange
let invalid_toml = r#"
[database
host = "localhost"
"#;
let parser = TomlParser::new();
// Act
let result: Result<TestConfig> = parser.parse(invalid_toml).await;
// Assert
assert!(result.is_err());
let error_message = result.unwrap_err().to_string();
assert!(error_message.contains("Failed to parse TOML"));
}
#[tokio::test]
async fn test_parse_missing_required_fields() {
// Test parsing with missing required fields
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_parse_type_mismatch() {
// Test parsing with type mismatches
// Arrange, Act, Assert pattern
}
}2.2 YAML Parser Tests
#[cfg(test)]
mod yaml_parser_tests {
use super::*;
#[tokio::test]
async fn test_parse_valid_yaml() {
// Arrange
let yaml_content = r#"
database:
host: localhost
port: 5432
ssl: true
logging:
level: info
file: /var/log/app.log
"#;
let parser = YamlParser::new();
// Act
let result: Result<TestConfig> = parser.parse(yaml_content).await;
// Assert
assert!(result.is_ok());
// Additional assertions...
}
#[tokio::test]
async fn test_parse_yaml_with_arrays() {
// Test YAML arrays parsing
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_parse_yaml_with_null_values() {
// Test YAML null values
// Arrange, Act, Assert pattern
}
}2.3 JSON Parser Tests
#[cfg(test)]
mod json_parser_tests {
use super::*;
#[tokio::test]
async fn test_parse_valid_json() {
// Similar structure to TOML/YAML tests
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_parse_json_with_nested_objects() {
// Test deeply nested JSON structures
// Arrange, Act, Assert pattern
}
}3. Configuration Merging Tests
3.1 Deep Merge Strategy Tests
#[cfg(test)]
mod deep_merge_tests {
use super::*;
#[test]
fn test_deep_merge_nested_objects() {
// Arrange
let base_config = TestConfig {
database: DatabaseConfig {
host: "localhost".to_string(),
port: 5432,
ssl: false,
},
logging: LoggingConfig {
level: "warn".to_string(),
file: None,
},
};
let override_config = TestConfig {
database: DatabaseConfig {
host: "remote".to_string(),
port: 5432, // Same value
ssl: true, // Override
},
logging: LoggingConfig {
level: "warn".to_string(), // Same value
file: Some("/var/log/override.log".to_string()), // New value
},
};
let strategy = DeepMergeStrategy;
// Act
let result = strategy.merge(base_config, override_config).unwrap();
// Assert
assert_eq!(result.database.host, "remote");
assert_eq!(result.database.port, 5432);
assert!(result.database.ssl);
assert_eq!(result.logging.level, "warn");
assert_eq!(result.logging.file, Some("/var/log/override.log".to_string()));
}
#[test]
fn test_deep_merge_array_handling() {
// Test array merging behavior
// Arrange, Act, Assert pattern
}
#[test]
fn test_deep_merge_null_values() {
// Test null value handling
// Arrange, Act, Assert pattern
}
}3.2 Shallow Merge Strategy Tests
#[cfg(test)]
mod shallow_merge_tests {
use super::*;
#[test]
fn test_shallow_merge_replaces_entire_sections() {
// Test that shallow merge replaces entire sections
// Arrange, Act, Assert pattern
}
}3.3 Array Append Strategy Tests
#[cfg(test)]
mod array_append_tests {
use super::*;
#[test]
fn test_array_append_combines_arrays() {
// Test array combination behavior
// Arrange, Act, Assert pattern
}
}4. Validation Tests
4.1 Validation Pipeline Tests
#[cfg(test)]
mod validation_tests {
use super::*;
#[tokio::test]
async fn test_required_field_validation_success() {
// Arrange
let config = TestConfig {
database: DatabaseConfig {
host: "localhost".to_string(),
port: 5432,
ssl: true,
},
logging: LoggingConfig {
level: "info".to_string(),
file: None,
},
};
let validator = RequiredFieldValidator::new("database.host".to_string());
// Act
let result = validator.validate(&config);
// Assert
assert!(result.is_ok());
let validation_result = result.unwrap();
assert_eq!(validation_result.severity, ValidationSeverity::Info);
}
#[tokio::test]
async fn test_required_field_validation_failure() {
// Test missing required field
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_range_validation_within_bounds() {
// Arrange
let config = TestConfig {
database: DatabaseConfig {
host: "localhost".to_string(),
port: 5432,
ssl: true,
},
logging: LoggingConfig {
level: "info".to_string(),
file: None,
},
};
let validator = RangeValidator::new("database.port".to_string(), 1.0, 65535.0);
// Act
let result = validator.validate(&config);
// Assert
assert!(result.is_ok());
let validation_result = result.unwrap();
assert_eq!(validation_result.severity, ValidationSeverity::Info);
}
#[tokio::test]
async fn test_range_validation_out_of_bounds() {
// Test value outside allowed range
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_validation_pipeline_multiple_rules() {
// Test multiple validation rules in pipeline
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_validation_pipeline_early_termination() {
// Test pipeline behavior on critical errors
// Arrange, Act, Assert pattern
}
}5. Schema Manager Tests
5.1 Schema Discovery Tests
#[cfg(test)]
mod schema_discovery_tests {
use super::*;
use tempfile::TempDir;
use tokio::fs;
#[tokio::test]
async fn test_schema_discovery_priority_order() {
// Arrange - Create schemas in multiple locations
let temp_dir = TempDir::new().unwrap();
let project_schema_dir = temp_dir.path().join("schemas");
let user_schema_dir = temp_dir.path().join(".sindhan/schemas");
let system_schema_dir = temp_dir.path().join("system/schemas");
fs::create_dir_all(&project_schema_dir).await.unwrap();
fs::create_dir_all(&user_schema_dir).await.unwrap();
fs::create_dir_all(&system_schema_dir).await.unwrap();
// Create different schemas with priority conflicts
fs::write(project_schema_dir.join("test-module.schema.toml"), r#"
[schema]
name = "test-module"
version = "1.0.0"
description = "Project Schema"
[config.priority]
type = "string"
default = "project"
"#).await.unwrap();
fs::write(user_schema_dir.join("test-module.schema.toml"), r#"
[schema]
name = "test-module"
version = "1.1.0"
description = "User Schema"
[config.priority]
type = "string"
default = "user"
"#).await.unwrap();
fs::write(system_schema_dir.join("test-module.schema.toml"), r#"
[schema]
name = "test-module"
version = "1.2.0"
description = "System Schema"
[config.priority]
type = "string"
default = "system"
"#).await.unwrap();
std::env::set_var("SINDHAN_SCHEMA_DIR", system_schema_dir.parent().unwrap());
let discovery = SchemaDiscoveryEngine::builder()
.add_project_search_path(project_schema_dir.parent().unwrap().to_path_buf())
.add_user_search_path(user_schema_dir.parent().unwrap().to_path_buf())
.build()
.unwrap();
// Act
let discovered_files = discovery.discover_schema_files("test-module").await.unwrap();
// Assert - Project schema should have highest priority
assert!(!discovered_files.is_empty());
let primary_schema = discovered_files.iter().min_by_key(|f| f.priority).unwrap();
assert_eq!(primary_schema.source, SchemaSource::Project);
// Cleanup
std::env::remove_var("SINDHAN_SCHEMA_DIR");
}
#[tokio::test]
async fn test_schema_format_precedence() {
// Arrange - Create multiple format schemas
let temp_dir = TempDir::new().unwrap();
let schema_dir = temp_dir.path().join("schemas");
fs::create_dir_all(&schema_dir).await.unwrap();
// Create schemas in different formats
fs::write(schema_dir.join("test-module.schema.toml"), "# TOML schema").await.unwrap();
fs::write(schema_dir.join("test-module.schema.yaml"), "# YAML schema").await.unwrap();
fs::write(schema_dir.join("test-module.schema.json"), "# JSON schema").await.unwrap();
let discovery = SchemaDiscoveryEngine::builder()
.add_project_search_path(schema_dir.parent().unwrap().to_path_buf())
.build()
.unwrap();
// Act
let discovered_files = discovery.discover_schema_files("test-module").await.unwrap();
// Assert - TOML should be preferred
assert!(!discovered_files.is_empty());
let primary_schema = discovered_files.iter().min_by_key(|f| f.priority).unwrap();
assert_eq!(primary_schema.format, SchemaFormat::Toml);
}
#[tokio::test]
async fn test_embedded_schema_fallback() {
// Arrange - No external schemas, should fall back to embedded
let discovery = SchemaDiscoveryEngine::builder()
.enable_embedded_schemas(true)
.build()
.unwrap();
// Act
let discovered_files = discovery.discover_schema_files("agent-identity").await.unwrap();
// Assert - Should find embedded schema
assert!(!discovered_files.is_empty());
let embedded_schema = discovered_files.iter().find(|f| f.source == SchemaSource::Embedded).unwrap();
assert!(embedded_schema.path.to_string_lossy().starts_with("embedded://"));
}
#[tokio::test]
async fn test_schema_discovery_with_custom_naming() {
// Test various module naming conventions
let test_cases = vec![
("agent-identity", "agent-identity.schema.toml"),
("agent_identity", "agent-identity.schema.toml"),
("AgentIdentity", "agent-identity.schema.toml"),
("sindhan::agent::identity", "agent-identity.schema.toml"),
];
for (module_name, expected_file) in test_cases {
// Test module name resolution
let resolved_name = SchemaDiscoveryEngine::resolve_module_name(module_name);
assert_eq!(resolved_name, "agent-identity");
}
}
}5.2 Schema Loading and Validation Tests
#[cfg(test)]
mod schema_loading_tests {
use super::*;
#[tokio::test]
async fn test_schema_loading_with_metadata() {
// Arrange
let temp_dir = TempDir::new().unwrap();
let schema_path = temp_dir.path().join("test-module.schema.toml");
let schema_content = r#"
[schema]
name = "test-module"
version = "1.2.0"
compatible_versions = ["^1.0.0"]
description = "Test Module Schema"
author = "Sindhan AI Platform"
created = "2024-01-15T10:30:00Z"
last_modified = "2024-03-20T14:45:00Z"
tags = ["test", "module"]
[schema.dependencies]
required_schemas = ["global"]
optional_schemas = ["logging"]
[config.database]
type = "object"
required = true
description = "Database configuration"
[config.database.fields.host]
type = "string"
required = true
default = "localhost"
validation = "hostname"
description = "Database host"
"#;
fs::write(&schema_path, schema_content).await.unwrap();
let loader = SchemaLoader::new();
// Act
let schema_definition = loader.load_schema_file(&schema_path).await.unwrap();
// Assert
assert_eq!(schema_definition.metadata.name, "test-module");
assert_eq!(schema_definition.metadata.version.to_string(), "1.2.0");
assert_eq!(schema_definition.metadata.dependencies.required_schemas, vec!["global"]);
assert!(schema_definition.config_structure.sections.contains_key("database"));
}
#[tokio::test]
async fn test_schema_inheritance_processing() {
// Arrange - Create parent and child schemas
let temp_dir = TempDir::new().unwrap();
let schema_dir = temp_dir.path().join("schemas");
fs::create_dir_all(&schema_dir).await.unwrap();
// Parent schema
fs::write(schema_dir.join("base.schema.toml"), r#"
[schema]
name = "base"
version = "1.0.0"
description = "Base Schema"
[config.common]
type = "object"
required = true
[config.common.fields.name]
type = "string"
required = true
default = "default-name"
[config.common.fields.enabled]
type = "boolean"
default = true
"#).await.unwrap();
// Child schema with inheritance
fs::write(schema_dir.join("child.schema.toml"), r#"
[schema]
name = "child"
version = "1.0.0"
description = "Child Schema"
[schema.inheritance]
parent_schema = "base"
override_mode = "deep_merge"
[config.common.fields.version]
type = "string"
required = true
default = "1.0.0"
[config.specific]
type = "object"
required = true
[config.specific.fields.feature]
type = "boolean"
default = false
"#).await.unwrap();
let schema_manager = SchemaManager::new(
SchemaDiscoveryEngine::builder()
.add_project_search_path(schema_dir.parent().unwrap().to_path_buf())
.build().unwrap(),
SchemaLoader::new(),
SchemaValidator::new(),
);
// Act
let final_schema = schema_manager.load_schema("child").await.unwrap();
// Assert - Should have merged parent and child schemas
assert!(final_schema.config_structure.sections.contains_key("common"));
assert!(final_schema.config_structure.sections.contains_key("specific"));
let common_section = &final_schema.config_structure.sections["common"];
assert!(common_section.fields.contains_key("name")); // From parent
assert!(common_section.fields.contains_key("enabled")); // From parent
assert!(common_section.fields.contains_key("version")); // From child
}
#[tokio::test]
async fn test_schema_validation_errors() {
// Arrange - Invalid schema content
let temp_dir = TempDir::new().unwrap();
let schema_path = temp_dir.path().join("invalid.schema.toml");
let invalid_schema = r#"
[schema]
# Missing required name field
version = "1.0.0"
[config.invalid_field]
type = "unsupported_type"
"#;
fs::write(&schema_path, invalid_schema).await.unwrap();
let loader = SchemaLoader::new();
// Act
let result = loader.load_schema_file(&schema_path).await;
// Assert
assert!(result.is_err());
let error_message = result.unwrap_err().to_string();
assert!(error_message.contains("schema validation failed") || error_message.contains("missing required field"));
}
#[tokio::test]
async fn test_schema_dependency_resolution() {
// Arrange - Schema with dependencies
let temp_dir = TempDir::new().unwrap();
let schema_dir = temp_dir.path().join("schemas");
fs::create_dir_all(&schema_dir).await.unwrap();
// Dependency schema
fs::write(schema_dir.join("logging.schema.toml"), r#"
[schema]
name = "logging"
version = "1.0.0"
description = "Logging Schema"
[config.logging]
type = "object"
required = true
[config.logging.fields.level]
type = "enum"
values = ["debug", "info", "warn", "error"]
default = "info"
"#).await.unwrap();
// Main schema with dependency
fs::write(schema_dir.join("main.schema.toml"), r#"
[schema]
name = "main"
version = "1.0.0"
description = "Main Schema"
[schema.dependencies]
required_schemas = ["logging"]
[config.app]
type = "object"
required = true
[config.app.fields.name]
type = "string"
default = "my-app"
"#).await.unwrap();
let schema_manager = SchemaManager::new(
SchemaDiscoveryEngine::builder()
.add_project_search_path(schema_dir.parent().unwrap().to_path_buf())
.build().unwrap(),
SchemaLoader::new(),
SchemaValidator::new(),
);
// Act
let schema = schema_manager.load_schema("main").await.unwrap();
// Assert - Dependencies should be resolved
assert_eq!(schema.metadata.dependencies.required_schemas, vec!["logging"]);
// Dependency validation would be done by the validator
}
}5.3 Schema Cache Tests
#[cfg(test)]
mod schema_cache_tests {
use super::*;
#[tokio::test]
async fn test_schema_caching_behavior() {
// Arrange
let temp_dir = TempDir::new().unwrap();
let schema_path = temp_dir.path().join("schemas/cached-module.schema.toml");
fs::create_dir_all(schema_path.parent().unwrap()).await.unwrap();
fs::write(&schema_path, r#"
[schema]
name = "cached-module"
version = "1.0.0"
description = "Cached Schema"
[config.test]
type = "string"
default = "original"
"#).await.unwrap();
let schema_manager = SchemaManager::new(
SchemaDiscoveryEngine::builder()
.add_project_search_path(temp_dir.path().to_path_buf())
.build().unwrap(),
SchemaLoader::new(),
SchemaValidator::new(),
);
// Act - First load
let start_time = std::time::Instant::now();
let schema1 = schema_manager.load_schema("cached-module").await.unwrap();
let first_load_time = start_time.elapsed();
// Act - Second load (should be cached)
let start_time = std::time::Instant::now();
let schema2 = schema_manager.load_schema("cached-module").await.unwrap();
let second_load_time = start_time.elapsed();
// Assert
assert_eq!(schema1.metadata.name, schema2.metadata.name);
assert!(second_load_time < first_load_time); // Cached should be faster
}
#[tokio::test]
async fn test_schema_cache_invalidation() {
// Arrange
let temp_dir = TempDir::new().unwrap();
let schema_path = temp_dir.path().join("schemas/invalidation-test.schema.toml");
fs::create_dir_all(schema_path.parent().unwrap()).await.unwrap();
let original_content = r#"
[schema]
name = "invalidation-test"
version = "1.0.0"
description = "Original Schema"
[config.value]
type = "string"
default = "original"
"#;
fs::write(&schema_path, original_content).await.unwrap();
let schema_manager = SchemaManager::new(
SchemaDiscoveryEngine::builder()
.add_project_search_path(temp_dir.path().to_path_buf())
.build().unwrap(),
SchemaLoader::new(),
SchemaValidator::new(),
);
// Act - Load original
let original_schema = schema_manager.load_schema("invalidation-test").await.unwrap();
// Simulate cache expiration by waiting
tokio::time::sleep(tokio::time::Duration::from_millis(400)).await;
// Update schema file
let updated_content = r#"
[schema]
name = "invalidation-test"
version = "1.1.0"
description = "Updated Schema"
[config.value]
type = "string"
default = "updated"
"#;
fs::write(&schema_path, updated_content).await.unwrap();
// Act - Load again (cache should be invalidated)
let updated_schema = schema_manager.load_schema("invalidation-test").await.unwrap();
// Assert
assert_eq!(original_schema.metadata.version.to_string(), "1.0.0");
assert_eq!(updated_schema.metadata.version.to_string(), "1.1.0");
}
}6. Hot Reload Tests
6.1 File System Monitoring Tests
#[cfg(test)]
mod hot_reload_tests {
use super::*;
use std::sync::Arc;
use tokio::sync::Notify;
use std::time::Duration;
#[tokio::test]
async fn test_configuration_file_modification_detection() {
// Arrange
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("hot-reload-test.toml");
let initial_content = r#"
[database]
host = "localhost"
port = 5432
ssl = false
[logging]
level = "info"
"#;
fs::write(&config_path, initial_content).await.unwrap();
let change_notifier = Arc::new(Notify::new());
let change_notifier_clone = change_notifier.clone();
let hot_reload_manager = HotReloadManager::new(
vec![temp_dir.path().to_path_buf()],
Box::new(move |change_event| {
let notifier = change_notifier_clone.clone();
Box::pin(async move {
println!("Configuration change detected: {:?}", change_event);
notifier.notify_one();
Ok(())
})
})
).await.unwrap();
// Act - Modify configuration file
let updated_content = r#"
[database]
host = "localhost"
port = 5432
ssl = true # Changed from false to true
[logging]
level = "debug" # Changed from info to debug
"#;
fs::write(&config_path, updated_content).await.unwrap();
// Wait for change detection
tokio::time::timeout(Duration::from_secs(5), change_notifier.notified())
.await
.expect("Change should be detected within 5 seconds");
// Assert - Verification happens through the callback
assert!(true); // If we reach here, change was detected
}
#[tokio::test]
async fn test_configuration_hot_reload_validation() {
// Arrange
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("validation-test.toml");
let valid_content = r#"
[database]
host = "localhost"
port = 5432
ssl = true
"#;
fs::write(&config_path, valid_content).await.unwrap();
let config_manager = ConfigurationManager::<TestConfigSchema>::builder()
.with_discovery(ConfigurationDiscovery::builder()
.add_search_path(temp_dir.path().to_path_buf())
.enable_hot_reload()
.build().unwrap()
)
.build()
.await
.unwrap();
// Load initial configuration
config_manager.load_configuration().await.unwrap();
// Act - Update with invalid configuration
let invalid_content = r#"
[database]
host = "localhost"
port = 99999 # Invalid port number
ssl = "invalid_boolean" # Invalid boolean value
"#;
fs::write(&config_path, invalid_content).await.unwrap();
// Wait for hot reload attempt
tokio::time::sleep(Duration::from_millis(500)).await;
// Assert - Configuration should remain unchanged due to validation failure
let current_config = config_manager.get_configuration().await;
assert_eq!(current_config.database.port, 5432); // Should still be original value
assert!(current_config.database.ssl); // Should still be original value
}
#[tokio::test]
async fn test_hot_reload_rollback_on_error() {
// Arrange
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("rollback-test.toml");
let working_content = r#"
[database]
host = "localhost"
port = 5432
ssl = true
[logging]
level = "info"
"#;
fs::write(&config_path, working_content).await.unwrap();
let mut error_observer = MockConfigurationObserver::new();
let config_manager = ConfigurationManager::<TestConfigSchema>::builder()
.with_discovery(ConfigurationDiscovery::builder()
.add_search_path(temp_dir.path().to_path_buf())
.enable_hot_reload()
.build().unwrap()
)
.add_observer(Arc::new(error_observer.clone()))
.build()
.await
.unwrap();
// Load initial configuration
config_manager.load_configuration().await.unwrap();
let original_config = config_manager.get_configuration().await;
// Act - Update with syntactically invalid configuration
let broken_content = r#"
[database
host = "localhost" # Missing closing bracket
port = 5432
"#;
fs::write(&config_path, broken_content).await.unwrap();
// Wait for hot reload attempt and rollback
tokio::time::sleep(Duration::from_millis(1000)).await;
// Assert - Configuration should be rolled back to previous working state
let current_config = config_manager.get_configuration().await;
assert_eq!(current_config.database.host, original_config.database.host);
assert_eq!(current_config.database.port, original_config.database.port);
// Check that error was reported to observer
let notifications = error_observer.get_notifications().await;
assert!(!notifications.is_empty());
}
#[tokio::test]
async fn test_hot_reload_performance_impact() {
// Arrange
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("performance-test.toml");
let initial_content = r#"
[database]
host = "localhost"
port = 5432
"#;
fs::write(&config_path, initial_content).await.unwrap();
let config_manager = ConfigurationManager::<TestConfigSchema>::builder()
.with_discovery(ConfigurationDiscovery::builder()
.add_search_path(temp_dir.path().to_path_buf())
.enable_hot_reload()
.build().unwrap()
)
.build()
.await
.unwrap();
config_manager.load_configuration().await.unwrap();
// Measure baseline configuration access performance
let start_time = std::time::Instant::now();
for _ in 0..1000 {
let _ = config_manager.get_configuration().await;
}
let baseline_duration = start_time.elapsed();
// Act - Trigger multiple hot reloads
for i in 0..10 {
let content = format!(r#"
[database]
host = "localhost"
port = {}
"#, 5432 + i);
fs::write(&config_path, content).await.unwrap();
tokio::time::sleep(Duration::from_millis(100)).await;
}
// Measure performance after hot reloads
let start_time = std::time::Instant::now();
for _ in 0..1000 {
let _ = config_manager.get_configuration().await;
}
let post_reload_duration = start_time.elapsed();
// Assert - Performance should not be significantly degraded
let performance_ratio = post_reload_duration.as_nanos() as f64 / baseline_duration.as_nanos() as f64;
assert!(performance_ratio < 2.0, "Hot reload should not degrade performance by more than 2x");
}
#[tokio::test]
async fn test_hot_reload_concurrency_safety() {
// Arrange
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("concurrency-test.toml");
let initial_content = r#"
[database]
host = "localhost"
port = 5432
"#;
fs::write(&config_path, initial_content).await.unwrap();
let config_manager = Arc::new(
ConfigurationManager::<TestConfigSchema>::builder()
.with_discovery(ConfigurationDiscovery::builder()
.add_search_path(temp_dir.path().to_path_buf())
.enable_hot_reload()
.build().unwrap()
)
.build()
.await
.unwrap()
);
config_manager.load_configuration().await.unwrap();
// Act - Spawn multiple concurrent tasks accessing configuration during reloads
let mut handles = Vec::new();
// Concurrent configuration readers
for i in 0..5 {
let manager = config_manager.clone();
let handle = tokio::spawn(async move {
for _ in 0..100 {
let config = manager.get_configuration().await;
assert_eq!(config.database.host, "localhost");
tokio::time::sleep(Duration::from_millis(10)).await;
}
i
});
handles.push(handle);
}
// Concurrent configuration updater
let config_path_clone = config_path.clone();
let update_handle = tokio::spawn(async move {
for i in 0..20 {
let content = format!(r#"
[database]
host = "localhost"
port = {}
ssl = {}
"#, 5432 + i, i % 2 == 0);
fs::write(&config_path_clone, content).await.unwrap();
tokio::time::sleep(Duration::from_millis(50)).await;
}
});
// Assert - All tasks should complete without errors
for handle in handles {
handle.await.unwrap();
}
update_handle.await.unwrap();
}
#[tokio::test]
async fn test_schema_hot_reload() {
// Arrange
let temp_dir = TempDir::new().unwrap();
let schema_path = temp_dir.path().join("schemas/hot-reload-schema.schema.toml");
let config_path = temp_dir.path().join("hot-reload-schema.toml");
fs::create_dir_all(schema_path.parent().unwrap()).await.unwrap();
let initial_schema = r#"
[schema]
name = "hot-reload-schema"
version = "1.0.0"
description = "Initial Schema"
[config.database]
type = "object"
required = true
[config.database.fields.host]
type = "string"
required = true
default = "localhost"
"#;
fs::write(&schema_path, initial_schema).await.unwrap();
let initial_config = r#"
[database]
host = "localhost"
"#;
fs::write(&config_path, initial_config).await.unwrap();
let schema_manager = SchemaManager::new(
SchemaDiscoveryEngine::builder()
.add_project_search_path(temp_dir.path().to_path_buf())
.enable_hot_reload()
.build().unwrap(),
SchemaLoader::new(),
SchemaValidator::new(),
);
// Load initial schema
let original_schema = schema_manager.load_schema("hot-reload-schema").await.unwrap();
assert_eq!(original_schema.metadata.version.to_string(), "1.0.0");
// Act - Update schema file
let updated_schema = r#"
[schema]
name = "hot-reload-schema"
version = "1.1.0"
description = "Updated Schema"
[config.database]
type = "object"
required = true
[config.database.fields.host]
type = "string"
required = true
default = "localhost"
[config.database.fields.port]
type = "integer"
required = false
default = 5432
"#;
fs::write(&schema_path, updated_schema).await.unwrap();
// Wait for schema hot reload
tokio::time::sleep(Duration::from_millis(500)).await;
// Act - Load schema again (should get updated version)
let reloaded_schema = schema_manager.load_schema("hot-reload-schema").await.unwrap();
// Assert
assert_eq!(reloaded_schema.metadata.version.to_string(), "1.1.0");
assert!(reloaded_schema.config_structure.sections["database"]
.fields.contains_key("port")); // New field should be present
}
}6.2 Hot Reload Integration Tests
#[cfg(test)]
mod hot_reload_integration_tests {
use super::*;
#[tokio::test]
async fn test_end_to_end_hot_reload_workflow() {
// Arrange - Complete hot reload setup
let temp_dir = TempDir::new().unwrap();
let schema_dir = temp_dir.path().join("schemas");
let config_path = temp_dir.path().join("e2e-test.toml");
fs::create_dir_all(&schema_dir).await.unwrap();
// Create schema
fs::write(schema_dir.join("e2e-test.schema.toml"), r#"
[schema]
name = "e2e-test"
version = "1.0.0"
description = "End-to-End Test Schema"
[config.server]
type = "object"
required = true
[config.server.fields.host]
type = "string"
default = "localhost"
[config.server.fields.port]
type = "integer"
default = 8080
range = { min = 1, max = 65535 }
[config.server.fields.enabled]
type = "boolean"
default = true
"#).await.unwrap();
// Create initial configuration
fs::write(&config_path, r#"
[server]
host = "localhost"
port = 8080
enabled = true
"#).await.unwrap();
let change_events = Arc::new(tokio::sync::Mutex::new(Vec::new()));
let change_events_clone = change_events.clone();
// Create configuration manager with hot reload
let config_manager = ConfigurationManager::<E2ETestConfigSchema>::builder()
.with_discovery(ConfigurationDiscovery::builder()
.add_search_path(temp_dir.path().to_path_buf())
.enable_hot_reload()
.build().unwrap()
)
.add_observer(Arc::new(TestConfigurationObserver::new(change_events_clone)))
.build()
.await
.unwrap();
// Load initial configuration
config_manager.load_configuration().await.unwrap();
let initial_config = config_manager.get_configuration().await;
assert_eq!(initial_config.server.port, 8080);
// Act - Stage 1: Valid configuration update
fs::write(&config_path, r#"
[server]
host = "0.0.0.0"
port = 9090
enabled = true
"#).await.unwrap();
// Wait for hot reload
tokio::time::sleep(Duration::from_millis(1000)).await;
let updated_config = config_manager.get_configuration().await;
assert_eq!(updated_config.server.host, "0.0.0.0");
assert_eq!(updated_config.server.port, 9090);
// Act - Stage 2: Invalid configuration update (should be rejected)
fs::write(&config_path, r#"
[server]
host = "0.0.0.0"
port = 99999 # Invalid port
enabled = "not_a_boolean" # Invalid boolean
"#).await.unwrap();
// Wait for hot reload attempt
tokio::time::sleep(Duration::from_millis(1000)).await;
// Configuration should remain unchanged
let current_config = config_manager.get_configuration().await;
assert_eq!(current_config.server.host, "0.0.0.0"); // From previous valid update
assert_eq!(current_config.server.port, 9090); // From previous valid update
// Act - Stage 3: Fix configuration
fs::write(&config_path, r#"
[server]
host = "127.0.0.1"
port = 3000
enabled = false
"#).await.unwrap();
// Wait for hot reload
tokio::time::sleep(Duration::from_millis(1000)).await;
let final_config = config_manager.get_configuration().await;
assert_eq!(final_config.server.host, "127.0.0.1");
assert_eq!(final_config.server.port, 3000);
assert!(!final_config.server.enabled);
// Assert - Check change events were recorded
let events = change_events.lock().await;
assert!(events.len() >= 2); // At least 2 successful changes
}
#[tokio::test]
async fn test_hot_reload_with_multiple_configuration_files() {
// Test hot reload behavior with hierarchical configuration merging
// Arrange, Act, Assert pattern for complex scenarios
todo!("Implement multi-file hot reload test");
}
#[tokio::test]
async fn test_hot_reload_resource_cleanup() {
// Test that hot reload properly cleans up resources
// Arrange, Act, Assert pattern for resource management
todo!("Implement resource cleanup test");
}
}7. Configuration Manager Tests
5.1 Configuration Loading Tests
#[cfg(test)]
mod configuration_manager_tests {
use super::*;
#[tokio::test]
async fn test_load_configuration_success() {
// Arrange
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("test-module.toml");
let config_content = r#"
[database]
host = "localhost"
port = 5432
ssl = true
[logging]
level = "info"
"#;
fs::write(&config_path, config_content).await.unwrap();
let discovery = ConfigurationDiscovery::builder()
.add_search_path(temp_dir.path().to_path_buf())
.build()
.unwrap();
let manager = ConfigurationManager::<TestConfigSchema>::builder()
.with_discovery(ConfigurationDiscovery::builder())
.build()
.await
.unwrap();
// Act
let result = manager.load_configuration().await;
// Assert
assert!(result.is_ok());
let config = manager.get_configuration().await;
assert_eq!(config.database.host, "localhost");
}
#[tokio::test]
async fn test_load_configuration_validation_failure() {
// Test configuration loading with validation failures
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_update_configuration_success() {
// Test configuration updates
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_configuration_observer_notifications() {
// Test observer pattern notifications
// Arrange, Act, Assert pattern
}
}8. Builder Pattern Tests
6.1 Configuration Discovery Builder Tests
#[cfg(test)]
mod builder_tests {
use super::*;
#[test]
fn test_discovery_builder_default_configuration() {
// Arrange & Act
let discovery = ConfigurationDiscovery::builder()
.build()
.unwrap();
// Assert
assert_eq!(discovery.discovery_chain.len(), 3); // Current, Home, Environment
}
#[test]
fn test_discovery_builder_custom_search_paths() {
// Arrange & Act
let discovery = ConfigurationDiscovery::builder()
.add_search_path(PathBuf::from("/custom/path1"))
.add_search_path(PathBuf::from("/custom/path2"))
.build()
.unwrap();
// Assert
assert_eq!(discovery.discovery_chain.len(), 5); // 3 default + 2 custom
}
#[test]
fn test_discovery_builder_format_precedence() {
// Test custom format precedence
// Arrange, Act, Assert pattern
}
#[test]
fn test_configuration_manager_builder() {
// Test configuration manager builder
// Arrange, Act, Assert pattern
}
}Integration Test Specifications
1. End-to-End Configuration Flow Tests
#[cfg(test)]
mod integration_tests {
use super::*;
#[tokio::test]
async fn test_complete_configuration_workflow() {
// Arrange
let temp_dir = TempDir::new().unwrap();
// Create configuration files in different locations
let current_dir = temp_dir.path().join("current");
let home_dir = temp_dir.path().join("home/.sindhan");
let env_dir = temp_dir.path().join("env");
fs::create_dir_all(¤t_dir).await.unwrap();
fs::create_dir_all(&home_dir).await.unwrap();
fs::create_dir_all(&env_dir).await.unwrap();
// Create base configuration in environment directory
fs::write(env_dir.join("test-module.toml"), r#"
[database]
host = "env-host"
port = 5432
ssl = false
[logging]
level = "warn"
"#).await.unwrap();
// Create override configuration in home directory
fs::write(home_dir.join("test-module.toml"), r#"
[database]
ssl = true
[logging]
level = "info"
file = "/home/user/app.log"
"#).await.unwrap();
// Set environment variable
std::env::set_var("SINDHAN_CONFIG_DIR", env_dir.to_str().unwrap());
let discovery = ConfigurationDiscovery::builder()
.add_search_path(current_dir)
.build()
.unwrap();
let manager = ConfigurationManager::<TestConfigSchema>::builder()
.with_discovery(ConfigurationDiscovery::builder())
.build()
.await
.unwrap();
// Act
manager.load_configuration().await.unwrap();
let config = manager.get_configuration().await;
// Assert - Home directory should override environment directory
assert_eq!(config.database.host, "env-host"); // From env
assert_eq!(config.database.port, 5432); // From env
assert!(config.database.ssl); // Overridden by home
assert_eq!(config.logging.level, "info"); // Overridden by home
assert_eq!(config.logging.file, Some("/home/user/app.log".to_string())); // From home
// Cleanup
std::env::remove_var("SINDHAN_CONFIG_DIR");
}
#[tokio::test]
async fn test_configuration_hot_reload() {
// Test hot-reload functionality
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_external_configuration_adapter() {
// Test external configuration source integration
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_encrypted_configuration_decorator() {
// Test encryption decorator integration
// Arrange, Act, Assert pattern
}
}2. Cross-Module Integration Tests
#[cfg(test)]
mod cross_module_tests {
use super::*;
#[tokio::test]
async fn test_multiple_module_configurations() {
// Test loading configurations for multiple modules
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_shared_configuration_sections() {
// Test shared configuration between modules
// Arrange, Act, Assert pattern
}
}Performance Test Specifications
1. Configuration Loading Performance Tests
#[cfg(test)]
mod performance_tests {
use super::*;
use std::time::Instant;
#[tokio::test]
async fn test_large_configuration_loading_performance() {
// Arrange
let large_config = generate_large_configuration(10000); // 10k entries
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("large-config.toml");
fs::write(&config_path, large_config).await.unwrap();
let discovery = ConfigurationDiscovery::builder()
.add_search_path(temp_dir.path().to_path_buf())
.build()
.unwrap();
// Act
let start = Instant::now();
let result = discovery.discover_files("large-config").await;
let duration = start.elapsed();
// Assert
assert!(result.is_ok());
assert!(duration.as_millis() < 100); // Should load within 100ms
}
#[tokio::test]
async fn test_concurrent_configuration_access() {
// Test concurrent access performance
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_memory_usage_large_configurations() {
// Test memory usage with large configurations
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_configuration_caching_performance() {
// Test caching performance benefits
// Arrange, Act, Assert pattern
}
fn generate_large_configuration(size: usize) -> String {
let mut config = String::new();
for i in 0..size {
config.push_str(&format!("key_{} = \"value_{}\"\n", i, i));
}
config
}
}2. Memory Usage Tests
#[cfg(test)]
mod memory_tests {
use super::*;
#[tokio::test]
async fn test_memory_leak_detection() {
// Test for memory leaks during configuration operations
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_configuration_cleanup() {
// Test proper cleanup of configuration resources
// Arrange, Act, Assert pattern
}
}Security Test Specifications
1. Configuration Security Tests
#[cfg(test)]
mod security_tests {
use super::*;
#[tokio::test]
async fn test_encrypted_field_handling() {
// Arrange
let config = TestConfig {
database: DatabaseConfig {
host: "localhost".to_string(),
port: 5432,
ssl: true,
},
logging: LoggingConfig {
level: "info".to_string(),
file: Some("secret-path".to_string()),
},
};
let manager = Arc::new(ConfigurationManager::<TestConfigSchema>::builder()
.build()
.await
.unwrap());
let encrypted_decorator = EncryptedConfigurationDecorator::new(
manager,
vec![0u8; 32], // Test encryption key
vec!["logging.file".to_string()],
);
// Act
encrypted_decorator.update_configuration(config).await.unwrap();
let retrieved_config = encrypted_decorator.get_configuration().await.unwrap();
// Assert
assert_eq!(retrieved_config.logging.file, Some("secret-path".to_string()));
}
#[tokio::test]
async fn test_configuration_access_control() {
// Test access control mechanisms
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_sensitive_data_detection() {
// Test automatic sensitive data detection
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_configuration_tampering_detection() {
// Test configuration integrity verification
// Arrange, Act, Assert pattern
}
}2. Input Validation Security Tests
#[cfg(test)]
mod input_validation_security_tests {
use super::*;
#[tokio::test]
async fn test_malicious_configuration_rejection() {
// Test rejection of malicious configuration inputs
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_path_traversal_prevention() {
// Test prevention of path traversal attacks
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_configuration_size_limits() {
// Test configuration size limit enforcement
// Arrange, Act, Assert pattern
}
}Error Handling and Edge Case Tests
1. Error Handling Tests
#[cfg(test)]
mod error_handling_tests {
use super::*;
#[tokio::test]
async fn test_file_not_found_error_handling() {
// Arrange
let discovery = ConfigurationDiscovery::builder().build().unwrap();
// Act
let result = discovery.discover_files("nonexistent-module").await;
// Assert
assert!(result.is_ok());
let files = result.unwrap();
assert_eq!(files.len(), 1);
assert_eq!(files[0].source, DiscoverySource::Default);
}
#[tokio::test]
async fn test_permission_denied_error_handling() {
// Test handling of permission denied errors
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_corrupted_configuration_handling() {
// Test handling of corrupted configuration files
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_network_failure_handling() {
// Test handling of network failures for external sources
// Arrange, Act, Assert pattern
}
}2. Edge Case Tests
#[cfg(test)]
mod edge_case_tests {
use super::*;
#[tokio::test]
async fn test_empty_configuration_file() {
// Test handling of empty configuration files
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_very_large_configuration_values() {
// Test handling of very large configuration values
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_unicode_configuration_content() {
// Test handling of Unicode content in configurations
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_circular_configuration_references() {
// Test detection and handling of circular references
// Arrange, Act, Assert pattern
}
}Compatibility and Platform Tests
1. Cross-Platform Tests
#[cfg(test)]
mod platform_tests {
use super::*;
#[tokio::test]
#[cfg(target_os = "windows")]
async fn test_windows_path_handling() {
// Test Windows-specific path handling
// Arrange, Act, Assert pattern
}
#[tokio::test]
#[cfg(target_os = "macos")]
async fn test_macos_path_handling() {
// Test macOS-specific path handling
// Arrange, Act, Assert pattern
}
#[tokio::test]
#[cfg(target_os = "linux")]
async fn test_linux_path_handling() {
// Test Linux-specific path handling
// Arrange, Act, Assert pattern
}
}2. Format Compatibility Tests
#[cfg(test)]
mod format_compatibility_tests {
use super::*;
#[tokio::test]
async fn test_toml_version_compatibility() {
// Test TOML version compatibility
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_yaml_version_compatibility() {
// Test YAML version compatibility
// Arrange, Act, Assert pattern
}
#[tokio::test]
async fn test_json_standard_compliance() {
// Test JSON standard compliance
// Arrange, Act, Assert pattern
}
}Test Utilities and Helpers
1. Test Data Generators
pub mod test_utils {
use super::*;
pub fn create_test_config() -> TestConfig {
TestConfig {
database: DatabaseConfig {
host: "localhost".to_string(),
port: 5432,
ssl: true,
},
logging: LoggingConfig {
level: "info".to_string(),
file: None,
},
}
}
pub fn create_temp_config_file(dir: &Path, name: &str, content: &str) -> PathBuf {
let file_path = dir.join(name);
std::fs::write(&file_path, content).unwrap();
file_path
}
pub async fn create_test_discovery() -> ConfigurationDiscovery {
ConfigurationDiscovery::builder()
.build()
.unwrap()
}
pub async fn create_test_manager() -> ConfigurationManager<TestConfigSchema> {
ConfigurationManager::<TestConfigSchema>::builder()
.build()
.await
.unwrap()
}
}2. Mock Objects
pub mod mocks {
use super::*;
pub struct MockDiscoveryHandler {
pub discovery_result: Option<Vec<DiscoveredFile>>,
pub should_error: bool,
}
#[async_trait]
impl DiscoveryHandler for MockDiscoveryHandler {
async fn discover(&self, _module_name: &str) -> Result<Option<Vec<DiscoveredFile>>> {
if self.should_error {
Err(anyhow::anyhow!("Mock discovery error"))
} else {
Ok(self.discovery_result.clone())
}
}
fn priority(&self) -> u8 { 1 }
fn name(&self) -> &'static str { "Mock" }
}
pub struct MockConfigurationParser {
pub parse_result: Result<serde_json::Value>,
pub can_parse_result: bool,
}
#[async_trait]
impl ConfigurationParser for MockConfigurationParser {
async fn parse<T>(&self, _content: &str) -> Result<T>
where
T: for<'de> Deserialize<'de>
{
match &self.parse_result {
Ok(value) => serde_json::from_value(value.clone())
.context("Mock parser conversion failed"),
Err(e) => Err(anyhow::anyhow!("Mock parser error: {}", e)),
}
}
fn format(&self) -> ConfigurationFormat {
ConfigurationFormat::Json
}
fn can_parse(&self, _content: &str) -> bool {
self.can_parse_result
}
}
pub struct MockObserver {
pub change_notifications: Arc<RwLock<Vec<ConfigurationChange>>>,
}
impl MockObserver {
pub fn new() -> Self {
Self {
change_notifications: Arc::new(RwLock::new(Vec::new())),
}
}
pub async fn get_notifications(&self) -> Vec<ConfigurationChange> {
self.change_notifications.read().await.clone()
}
}
#[async_trait]
impl ConfigurationObserver for MockObserver {
async fn on_configuration_changed(&self, change: ConfigurationChange) -> Result<()> {
let mut notifications = self.change_notifications.write().await;
notifications.push(change);
Ok(())
}
async fn on_validation_failed(&self, _error: ValidationError) -> Result<()> {
Ok(())
}
async fn on_reload_completed(&self, _success: bool) -> Result<()> {
Ok(())
}
}
}Test Execution Strategy
1. Test Organization
// tests/lib.rs
mod unit_tests {
mod discovery_tests;
mod parser_tests;
mod validation_tests;
mod merge_strategy_tests;
mod builder_tests;
}
mod integration_tests {
mod end_to_end_tests;
mod cross_module_tests;
mod external_integration_tests;
}
mod performance_tests {
mod loading_performance_tests;
mod memory_usage_tests;
mod concurrency_tests;
}
mod security_tests {
mod encryption_tests;
mod access_control_tests;
mod input_validation_tests;
}
mod compatibility_tests {
mod platform_tests;
mod format_tests;
mod version_tests;
}2. Test Configuration
# Cargo.toml test configuration
[package]
name = "sindhan-config"
version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
toml = "0.8"
serde_yaml = "0.9"
anyhow = "1.0"
async-trait = "0.1"
dirs = "5.0"
uuid = { version = "1.0", features = ["v4"] }
chrono = { version = "0.4", features = ["serde"] }
base64 = "0.21"
reqwest = { version = "0.11", features = ["json"] }
[dev-dependencies]
tempfile = "3.0"
mockall = "0.11"
proptest = "1.0"
criterion = "0.5"
[[bench]]
name = "configuration_benchmarks"
harness = false
[features]
default = []
test-utils = []3. Continuous Integration Configuration
# .github/workflows/test.yml
name: Test Suite
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
rust: [stable, beta, nightly]
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
components: rustfmt, clippy
- name: Run tests
uses: actions-rs/cargo@v1
with:
command: test
args: --all-features --verbose
- name: Run integration tests
uses: actions-rs/cargo@v1
with:
command: test
args: --test '*' --all-features
- name: Run benchmarks
uses: actions-rs/cargo@v1
with:
command: bench
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: -- --check
- name: Run clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --all-features -- -D warnings
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install cargo-tarpaulin
uses: actions-rs/install@v0.1
with:
crate: cargo-tarpaulin
version: latest
- name: Generate code coverage
run: cargo tarpaulin --verbose --all-features --workspace --timeout 120 --out Xml
- name: Upload to codecov.io
uses: codecov/codecov-action@v3
with:
file: cobertura.xml
fail_ci_if_error: trueTest Coverage Requirements
Coverage Targets
- Unit Tests: 90%+ line coverage
- Integration Tests: 80%+ feature coverage
- Security Tests: 100% critical path coverage
- Performance Tests: All major operations benchmarked
Test Quality Metrics
- Test Reliability: 99.9% pass rate
- Test Performance: < 5 minutes for full suite
- Test Maintainability: Tests updated with code changes
- Test Documentation: All test purposes documented
Test Execution Commands
# Run all tests
cargo test
# Run specific test categories
cargo test unit_tests
cargo test integration_tests
cargo test performance_tests
cargo test security_tests
# Run tests with coverage
cargo tarpaulin --all-features
# Run benchmarks
cargo bench
# Run tests in release mode
cargo test --release
# Run tests with specific features
cargo test --features "test-utils"
# Run tests with verbose output
cargo test -- --nocapture
# Run specific test
cargo test test_discovery_chain_priority_orderThis comprehensive test plan ensures systematic testing of all Configuration Management functionalities following TDD principles, providing confidence in the system's reliability, security, and performance across all supported platforms and use cases.