#[cfg(test)]
use crate::core::health::{
monitor::{HealthMonitor, HealthMonitorConfig},
provider::{ProviderHealth, SystemHealth},
types::{HealthCheckResult, HealthStatus},
};
#[cfg(test)]
use crate::utils::error::{gateway_error::Result as GatewayResult, recovery::types::CircuitState};
use std::collections::HashMap;
#[test]
fn test_health_status_scoring() {
assert_eq!(HealthStatus::Healthy.score(), 100);
assert_eq!(HealthStatus::Degraded.score(), 70);
assert_eq!(HealthStatus::Unhealthy.score(), 30);
assert_eq!(HealthStatus::Down.score(), 0);
assert!(HealthStatus::Healthy.allows_requests());
assert!(HealthStatus::Degraded.allows_requests());
assert!(!HealthStatus::Unhealthy.allows_requests());
assert!(!HealthStatus::Down.allows_requests());
}
#[test]
fn test_provider_health_update() {
let mut provider = ProviderHealth::new("test-provider".to_string());
assert_eq!(provider.status, HealthStatus::Healthy);
assert_eq!(provider.consecutive_failures, 0);
let unhealthy_result = HealthCheckResult::unhealthy("test error".to_string(), 1000);
provider.update(unhealthy_result);
assert_eq!(provider.status, HealthStatus::Unhealthy);
assert_eq!(provider.consecutive_failures, 1);
let healthy_result = HealthCheckResult::healthy(500);
provider.update(healthy_result);
assert_eq!(provider.status, HealthStatus::Healthy);
assert_eq!(provider.consecutive_failures, 0);
}
#[test]
fn test_provider_routing_weight() {
let mut provider = ProviderHealth::new("test-provider".to_string());
let healthy_result = HealthCheckResult::healthy(100);
provider.update(healthy_result);
let weight = provider.routing_weight();
assert!(weight > 0.8);
provider.status = HealthStatus::Down;
let weight = provider.routing_weight();
assert_eq!(weight, 0.0);
}
#[tokio::test]
async fn test_health_monitor_registration() {
let config = HealthMonitorConfig {
auto_check_enabled: false,
..Default::default()
};
let monitor = HealthMonitor::new(config);
monitor.register_provider("test-provider".to_string()).await;
let health = monitor.get_provider_health("test-provider").await;
assert!(health.is_some());
assert_eq!(health.unwrap().provider_id, "test-provider");
}
#[test]
fn test_system_health() {
let mut providers = HashMap::new();
providers.insert(
"provider1".to_string(),
ProviderHealth::new("provider1".to_string()),
);
let mut provider2 = ProviderHealth::new("provider2".to_string());
provider2.status = HealthStatus::Unhealthy;
providers.insert("provider2".to_string(), provider2);
let system_health = SystemHealth::new(providers);
assert_eq!(system_health.overall_status(), HealthStatus::Degraded);
let metrics = system_health.metrics();
assert_eq!(metrics.get("total_providers"), Some(&2.0));
assert_eq!(metrics.get("healthy_providers"), Some(&1.0));
}
#[test]
fn test_health_monitor_config_default_min_requests_and_success_threshold() {
let config = HealthMonitorConfig::default();
assert_eq!(config.min_requests, 10);
assert_eq!(config.success_threshold, 3);
}
#[test]
fn test_health_monitor_config_custom_min_requests_and_success_threshold() {
let config = HealthMonitorConfig {
min_requests: 20,
success_threshold: 5,
..Default::default()
};
assert_eq!(config.min_requests, 20);
assert_eq!(config.success_threshold, 5);
}
#[tokio::test]
async fn test_register_provider_circuit_breaker_respects_min_requests() {
let config = HealthMonitorConfig {
auto_check_enabled: false,
failure_threshold: 2,
min_requests: 2,
success_threshold: 1,
..Default::default()
};
let monitor = HealthMonitor::new(config);
monitor.register_provider("prov".to_string()).await;
let cb = monitor
.get_circuit_breaker("prov")
.await
.expect("circuit breaker must exist after register_provider");
let _: GatewayResult<()> = cb.call(async { Err::<(), _>("fail") }).await;
let _: GatewayResult<()> = cb.call(async { Err::<(), _>("fail") }).await;
assert_eq!(cb.state(), CircuitState::Open);
}
#[tokio::test]
async fn test_register_provider_circuit_breaker_high_min_requests_prevents_open() {
let config = HealthMonitorConfig {
auto_check_enabled: false,
failure_threshold: 2,
min_requests: 100,
success_threshold: 1,
..Default::default()
};
let monitor = HealthMonitor::new(config);
monitor.register_provider("prov".to_string()).await;
let cb = monitor
.get_circuit_breaker("prov")
.await
.expect("circuit breaker must exist after register_provider");
let _: GatewayResult<()> = cb.call(async { Err::<(), _>("fail") }).await;
let _: GatewayResult<()> = cb.call(async { Err::<(), _>("fail") }).await;
assert_eq!(cb.state(), CircuitState::Closed);
}
#[tokio::test]
async fn test_register_provider_circuit_breaker_respects_success_threshold() {
use std::time::Duration;
let config = HealthMonitorConfig {
auto_check_enabled: false,
failure_threshold: 2,
min_requests: 2,
success_threshold: 3,
..Default::default()
};
let monitor = HealthMonitor::new(config);
monitor.register_provider("prov".to_string()).await;
let cb = monitor
.get_circuit_breaker("prov")
.await
.expect("circuit breaker must exist after register_provider");
let _: GatewayResult<()> = cb.call(async { Err::<(), _>("fail") }).await;
let _: GatewayResult<()> = cb.call(async { Err::<(), _>("fail") }).await;
assert_eq!(cb.state(), CircuitState::Open);
let reject: GatewayResult<()> = cb.call(async { Ok::<(), String>(()) }).await;
assert!(reject.is_err(), "open circuit must reject requests");
cb.reset();
assert_eq!(cb.state(), CircuitState::Closed);
tokio::time::sleep(Duration::from_millis(1)).await; }