//! Comprehensive tests for traits module //! //! Tests cover: //! - HealthStatus and DetailedHealth structures //! - RateLimitStatus structure //! - Trait implementations with mock types //! - Serialization/deserialization use chrono::Utc; use common::traits::{DetailedHealth, HealthStatus, RateLimitStatus}; use common::types::ServiceStatus; use std::collections::HashMap; // ============================================================================ // HealthStatus Tests // ============================================================================ #[test] fn test_health_status_creation() { let timestamp = Utc::now(); let status = HealthStatus { status: ServiceStatus::Running, timestamp, message: Some("All systems operational".to_string()), }; assert_eq!(status.status, ServiceStatus::Running); assert_eq!(status.timestamp, timestamp); assert_eq!(status.message, Some("All systems operational".to_string())); } #[test] fn test_health_status_without_message() { let timestamp = Utc::now(); let status = HealthStatus { status: ServiceStatus::Running, timestamp, message: None, }; assert_eq!(status.status, ServiceStatus::Running); assert!(status.message.is_none()); } #[test] fn test_health_status_degraded() { let status = HealthStatus { status: ServiceStatus::Degraded, timestamp: Utc::now(), message: Some("Performance degraded".to_string()), }; assert_eq!(status.status, ServiceStatus::Degraded); assert!(status.message.is_some()); } #[test] fn test_health_status_unhealthy() { let status = HealthStatus { status: ServiceStatus::Stopped, timestamp: Utc::now(), message: Some("Service unavailable".to_string()), }; assert_eq!(status.status, ServiceStatus::Stopped); } #[test] fn test_health_status_serialization() { let status = HealthStatus { status: ServiceStatus::Running, timestamp: Utc::now(), message: Some("OK".to_string()), }; let json = serde_json::to_string(&status).expect("Failed to serialize"); let deserialized: HealthStatus = serde_json::from_str(&json).expect("Failed to deserialize"); assert_eq!(deserialized.status, status.status); assert_eq!(deserialized.message, status.message); } #[test] fn test_health_status_clone() { let status = HealthStatus { status: ServiceStatus::Running, timestamp: Utc::now(), message: Some("Test".to_string()), }; let cloned = status.clone(); assert_eq!(cloned.status, status.status); assert_eq!(cloned.message, status.message); } // ============================================================================ // DetailedHealth Tests // ============================================================================ #[test] fn test_detailed_health_creation() { let timestamp = Utc::now(); let base_status = HealthStatus { status: ServiceStatus::Running, timestamp, message: None, }; let mut metrics = HashMap::new(); metrics.insert("cpu_usage".to_string(), 45.5); metrics.insert("memory_usage".to_string(), 60.2); let mut components = HashMap::new(); components.insert( "database".to_string(), HealthStatus { status: ServiceStatus::Running, timestamp, message: Some("DB OK".to_string()), }, ); let detailed = DetailedHealth { status: base_status.clone(), metrics: metrics.clone(), components: components.clone(), }; assert_eq!(detailed.status.status, ServiceStatus::Running); assert_eq!(detailed.metrics.len(), 2); assert_eq!(detailed.components.len(), 1); assert_eq!(detailed.metrics.get("cpu_usage"), Some(&45.5)); } #[test] fn test_detailed_health_empty_metrics() { let detailed = DetailedHealth { status: HealthStatus { status: ServiceStatus::Running, timestamp: Utc::now(), message: None, }, metrics: HashMap::new(), components: HashMap::new(), }; assert!(detailed.metrics.is_empty()); assert!(detailed.components.is_empty()); } #[test] fn test_detailed_health_multiple_components() { let timestamp = Utc::now(); let mut components = HashMap::new(); components.insert( "database".to_string(), HealthStatus { status: ServiceStatus::Running, timestamp, message: Some("DB OK".to_string()), }, ); components.insert( "cache".to_string(), HealthStatus { status: ServiceStatus::Degraded, timestamp, message: Some("Cache slow".to_string()), }, ); components.insert( "queue".to_string(), HealthStatus { status: ServiceStatus::Running, timestamp, message: Some("Queue OK".to_string()), }, ); let detailed = DetailedHealth { status: HealthStatus { status: ServiceStatus::Degraded, // Overall status reflects degraded component timestamp, message: Some("Some components degraded".to_string()), }, metrics: HashMap::new(), components, }; assert_eq!(detailed.components.len(), 3); assert_eq!(detailed.status.status, ServiceStatus::Degraded); let cache_status = detailed.components.get("cache").unwrap(); assert_eq!(cache_status.status, ServiceStatus::Degraded); } #[test] fn test_detailed_health_serialization() { let mut metrics = HashMap::new(); metrics.insert("latency_ms".to_string(), 12.5); let detailed = DetailedHealth { status: HealthStatus { status: ServiceStatus::Running, timestamp: Utc::now(), message: None, }, metrics, components: HashMap::new(), }; let json = serde_json::to_string(&detailed).expect("Failed to serialize"); let deserialized: DetailedHealth = serde_json::from_str(&json).expect("Failed to deserialize"); assert_eq!(deserialized.status.status, ServiceStatus::Running); assert_eq!(deserialized.metrics.len(), 1); assert_eq!(deserialized.metrics.get("latency_ms"), Some(&12.5)); } #[test] fn test_detailed_health_clone() { let mut metrics = HashMap::new(); metrics.insert("test_metric".to_string(), 100.0); let detailed = DetailedHealth { status: HealthStatus { status: ServiceStatus::Running, timestamp: Utc::now(), message: None, }, metrics, components: HashMap::new(), }; let cloned = detailed.clone(); assert_eq!(cloned.metrics.len(), detailed.metrics.len()); assert_eq!(cloned.status.status, detailed.status.status); } // ============================================================================ // RateLimitStatus Tests // ============================================================================ #[test] fn test_rate_limit_status_creation() { let status = RateLimitStatus { current_count: 50, max_requests: 100, window_seconds: 60, reset_in_seconds: 45, }; assert_eq!(status.current_count, 50); assert_eq!(status.max_requests, 100); assert_eq!(status.window_seconds, 60); assert_eq!(status.reset_in_seconds, 45); } #[test] fn test_rate_limit_status_at_limit() { let status = RateLimitStatus { current_count: 100, max_requests: 100, window_seconds: 60, reset_in_seconds: 30, }; assert_eq!(status.current_count, status.max_requests); } #[test] fn test_rate_limit_status_under_limit() { let status = RateLimitStatus { current_count: 25, max_requests: 100, window_seconds: 60, reset_in_seconds: 55, }; assert!(status.current_count < status.max_requests); } #[test] fn test_rate_limit_status_zero_count() { let status = RateLimitStatus { current_count: 0, max_requests: 100, window_seconds: 60, reset_in_seconds: 60, }; assert_eq!(status.current_count, 0); } #[test] fn test_rate_limit_status_about_to_reset() { let status = RateLimitStatus { current_count: 90, max_requests: 100, window_seconds: 60, reset_in_seconds: 1, // About to reset }; assert_eq!(status.reset_in_seconds, 1); } #[test] fn test_rate_limit_status_serialization() { let status = RateLimitStatus { current_count: 75, max_requests: 100, window_seconds: 60, reset_in_seconds: 30, }; let json = serde_json::to_string(&status).expect("Failed to serialize"); let deserialized: RateLimitStatus = serde_json::from_str(&json).expect("Failed to deserialize"); assert_eq!(deserialized.current_count, status.current_count); assert_eq!(deserialized.max_requests, status.max_requests); assert_eq!(deserialized.window_seconds, status.window_seconds); assert_eq!(deserialized.reset_in_seconds, status.reset_in_seconds); } #[test] fn test_rate_limit_status_clone() { let status = RateLimitStatus { current_count: 50, max_requests: 100, window_seconds: 60, reset_in_seconds: 45, }; let cloned = status.clone(); assert_eq!(cloned.current_count, status.current_count); assert_eq!(cloned.max_requests, status.max_requests); } #[test] fn test_rate_limit_status_utilization_calculation() { let status = RateLimitStatus { current_count: 75, max_requests: 100, window_seconds: 60, reset_in_seconds: 30, }; // Calculate utilization percentage let utilization = (status.current_count as f64 / status.max_requests as f64) * 100.0; assert_eq!(utilization, 75.0); } #[test] fn test_rate_limit_status_remaining_requests() { let status = RateLimitStatus { current_count: 40, max_requests: 100, window_seconds: 60, reset_in_seconds: 20, }; let remaining = status.max_requests - status.current_count; assert_eq!(remaining, 60); } // ============================================================================ // Mock Trait Implementations for Testing // ============================================================================ // ============================================================================ // Trait Default Implementation Tests // ============================================================================ #[test] fn test_reloadable_default_supports_reload() { use async_trait::async_trait; use common::traits::Reloadable; struct TestReloadable; #[async_trait] impl Reloadable for TestReloadable { async fn reload(&mut self) -> common::error::CommonResult<()> { Ok(()) } // uses default supports_reload implementation } let component = TestReloadable; assert!(component.supports_reload()); } #[test] fn test_graceful_shutdown_default_timeout() { use async_trait::async_trait; use common::traits::GracefulShutdown; struct TestShutdown; #[async_trait] impl GracefulShutdown for TestShutdown { async fn shutdown(&mut self) -> common::error::CommonResult<()> { Ok(()) } async fn force_shutdown(&mut self) -> common::error::CommonResult<()> { Ok(()) } // uses default shutdown_timeout_seconds implementation } let component = TestShutdown; assert_eq!(component.shutdown_timeout_seconds(), 30); } #[cfg(test)] mod mock_implementations { use super::*; use async_trait::async_trait; use common::error::CommonResult; use common::traits::{CircuitBreaker, Configurable, HealthCheck, RateLimited}; #[derive(Clone)] struct MockConfig { value: String, } struct MockComponent { config: MockConfig, is_healthy: bool, circuit_open: bool, failure_count: u64, request_count: u64, } #[async_trait] impl Configurable for MockComponent { type Config = MockConfig; async fn configure(&mut self, config: Self::Config) -> CommonResult<()> { self.config = config; Ok(()) } fn get_config(&self) -> &Self::Config { &self.config } fn validate_config(_config: &Self::Config) -> CommonResult<()> { Ok(()) } } #[async_trait] impl HealthCheck for MockComponent { async fn health_check(&self) -> CommonResult { Ok(HealthStatus { status: if self.is_healthy { ServiceStatus::Running } else { ServiceStatus::Stopped }, timestamp: Utc::now(), message: None, }) } async fn detailed_health(&self) -> CommonResult { let mut metrics = HashMap::new(); metrics.insert("request_count".to_string(), self.request_count as f64); Ok(DetailedHealth { status: self.health_check().await?, metrics, components: HashMap::new(), }) } } impl CircuitBreaker for MockComponent { fn is_circuit_open(&self) -> bool { self.circuit_open } fn failure_count(&self) -> u64 { self.failure_count } fn reset_circuit(&mut self) { self.circuit_open = false; self.failure_count = 0; } } impl RateLimited for MockComponent { fn is_allowed(&self) -> bool { self.request_count < 100 } fn rate_limit_status(&self) -> RateLimitStatus { RateLimitStatus { current_count: self.request_count, max_requests: 100, window_seconds: 60, reset_in_seconds: 30, } } } #[tokio::test] async fn test_mock_configurable() { let mut component = MockComponent { config: MockConfig { value: "initial".to_string(), }, is_healthy: true, circuit_open: false, failure_count: 0, request_count: 0, }; let new_config = MockConfig { value: "updated".to_string(), }; component.configure(new_config.clone()).await.unwrap(); assert_eq!(component.get_config().value, "updated"); } #[tokio::test] async fn test_mock_health_check() { let component = MockComponent { config: MockConfig { value: "test".to_string(), }, is_healthy: true, circuit_open: false, failure_count: 0, request_count: 0, }; let health = component.health_check().await.unwrap(); assert_eq!(health.status, ServiceStatus::Running); } #[tokio::test] async fn test_mock_detailed_health() { let component = MockComponent { config: MockConfig { value: "test".to_string(), }, is_healthy: true, circuit_open: false, failure_count: 0, request_count: 42, }; let detailed = component.detailed_health().await.unwrap(); assert_eq!(detailed.status.status, ServiceStatus::Running); assert_eq!(detailed.metrics.get("request_count"), Some(&42.0)); } #[test] fn test_mock_circuit_breaker() { let mut component = MockComponent { config: MockConfig { value: "test".to_string(), }, is_healthy: true, circuit_open: true, failure_count: 5, request_count: 0, }; assert!(component.is_circuit_open()); assert_eq!(component.failure_count(), 5); component.reset_circuit(); assert!(!component.is_circuit_open()); assert_eq!(component.failure_count(), 0); } #[test] fn test_mock_rate_limited() { let component = MockComponent { config: MockConfig { value: "test".to_string(), }, is_healthy: true, circuit_open: false, failure_count: 0, request_count: 50, }; assert!(component.is_allowed()); let status = component.rate_limit_status(); assert_eq!(status.current_count, 50); assert_eq!(status.max_requests, 100); } #[test] fn test_mock_rate_limited_at_limit() { let component = MockComponent { config: MockConfig { value: "test".to_string(), }, is_healthy: true, circuit_open: false, failure_count: 0, request_count: 100, }; assert!(!component.is_allowed()); } }