"""Unit tests for provider health monitoring system.""" import asyncio from datetime import datetime import pytest from src.core.providers.health_monitor import ( ProviderHealthMetrics, ProviderHealthMonitor, RequestMetric, get_health_monitor, ) class TestProviderHealthMetrics: """Test ProviderHealthMetrics dataclass.""" def test_metrics_initialization(self): """Test metrics initialization with defaults.""" metrics = ProviderHealthMetrics(provider_name="test_provider") assert metrics.provider_name == "test_provider" assert metrics.is_available is True assert metrics.total_requests == 0 assert metrics.successful_requests == 0 assert metrics.failed_requests == 0 assert metrics.average_response_time_ms == 0.0 assert metrics.consecutive_failures == 0 assert metrics.uptime_percentage == 100.0 def test_success_rate_calculation(self): """Test success rate calculation.""" metrics = ProviderHealthMetrics(provider_name="test") metrics.total_requests = 100 metrics.successful_requests = 75 assert metrics.success_rate == 75.0 assert metrics.failure_rate == 25.0 def test_success_rate_zero_requests(self): """Test success rate with zero requests.""" metrics = ProviderHealthMetrics(provider_name="test") assert metrics.success_rate == 0.0 assert metrics.failure_rate == 100.0 def test_to_dict(self): """Test metrics conversion to dictionary.""" metrics = ProviderHealthMetrics( provider_name="test", total_requests=10, successful_requests=8, ) result = metrics.to_dict() assert result["provider_name"] == "test" assert result["total_requests"] == 10 assert result["successful_requests"] == 8 assert result["success_rate"] == 80.0 assert "average_response_time_ms" in result class TestProviderHealthMonitor: """Test ProviderHealthMonitor class.""" def test_monitor_initialization(self): """Test monitor initialization.""" monitor = ProviderHealthMonitor( max_history_size=500, health_check_interval=60, failure_threshold=5, ) assert monitor._max_history_size == 500 assert monitor._health_check_interval == 60 assert monitor._failure_threshold == 5 assert not monitor._is_running def test_record_successful_request(self): """Test recording successful request.""" monitor = ProviderHealthMonitor() monitor.record_request( provider_name="test_provider", success=True, response_time_ms=150.0, bytes_transferred=1024, ) metrics = monitor.get_provider_metrics("test_provider") assert metrics is not None assert metrics.total_requests == 1 assert metrics.successful_requests == 1 assert metrics.failed_requests == 0 assert metrics.is_available is True assert metrics.consecutive_failures == 0 assert metrics.average_response_time_ms == 150.0 assert metrics.total_bytes_downloaded == 1024 def test_record_failed_request(self): """Test recording failed request.""" monitor = ProviderHealthMonitor(failure_threshold=2) monitor.record_request( provider_name="test_provider", success=False, response_time_ms=200.0, error_message="Connection timeout", ) metrics = monitor.get_provider_metrics("test_provider") assert metrics is not None assert metrics.total_requests == 1 assert metrics.failed_requests == 1 assert metrics.consecutive_failures == 1 assert metrics.last_error == "Connection timeout" assert metrics.is_available is True # Below threshold def test_mark_unavailable_after_failures(self): """Test marking provider unavailable after threshold.""" monitor = ProviderHealthMonitor(failure_threshold=3) for i in range(3): monitor.record_request( provider_name="test_provider", success=False, response_time_ms=100.0, error_message=f"Error {i}", ) metrics = monitor.get_provider_metrics("test_provider") assert metrics.is_available is False assert metrics.consecutive_failures == 3 def test_recovery_after_success(self): """Test provider recovery after successful request.""" monitor = ProviderHealthMonitor(failure_threshold=2) # Record failures for _ in range(2): monitor.record_request( provider_name="test_provider", success=False, response_time_ms=100.0, ) metrics = monitor.get_provider_metrics("test_provider") assert metrics.is_available is False # Record success monitor.record_request( provider_name="test_provider", success=True, response_time_ms=100.0, ) metrics = monitor.get_provider_metrics("test_provider") assert metrics.is_available is True assert metrics.consecutive_failures == 0 def test_average_response_time_calculation(self): """Test average response time calculation.""" monitor = ProviderHealthMonitor() monitor.record_request( "test", success=True, response_time_ms=100.0 ) monitor.record_request( "test", success=True, response_time_ms=200.0 ) monitor.record_request( "test", success=True, response_time_ms=300.0 ) metrics = monitor.get_provider_metrics("test") assert metrics.average_response_time_ms == 200.0 def test_get_all_metrics(self): """Test getting metrics for all providers.""" monitor = ProviderHealthMonitor() monitor.record_request("provider1", success=True, response_time_ms=100.0) # noqa: E501 monitor.record_request("provider2", success=True, response_time_ms=150.0) # noqa: E501 all_metrics = monitor.get_all_metrics() assert len(all_metrics) == 2 assert "provider1" in all_metrics assert "provider2" in all_metrics def test_get_available_providers(self): """Test getting available providers list.""" monitor = ProviderHealthMonitor(failure_threshold=2) # Available provider monitor.record_request("provider1", success=True, response_time_ms=100.0) # noqa: E501 # Unavailable provider for _ in range(3): monitor.record_request( "provider2", success=False, response_time_ms=100.0 ) available = monitor.get_available_providers() assert "provider1" in available assert "provider2" not in available def test_get_best_provider(self): """Test getting best provider based on performance.""" monitor = ProviderHealthMonitor() # Provider 1: 80% success, 100ms avg for i in range(10): monitor.record_request( "provider1", success=(i < 8), response_time_ms=100.0, ) # Provider 2: 90% success, 150ms avg for i in range(10): monitor.record_request( "provider2", success=(i < 9), response_time_ms=150.0, ) best = monitor.get_best_provider() # Provider 2 should be best (higher success rate) assert best == "provider2" def test_reset_provider_metrics(self): """Test resetting provider metrics.""" monitor = ProviderHealthMonitor() monitor.record_request("test", success=True, response_time_ms=100.0) success = monitor.reset_provider_metrics("test") assert success is True metrics = monitor.get_provider_metrics("test") assert metrics.total_requests == 0 def test_reset_nonexistent_provider(self): """Test resetting metrics for nonexistent provider.""" monitor = ProviderHealthMonitor() success = monitor.reset_provider_metrics("nonexistent") assert success is False def test_health_summary(self): """Test health summary generation.""" monitor = ProviderHealthMonitor() monitor.record_request("provider1", success=True, response_time_ms=100.0) # noqa: E501 monitor.record_request("provider2", success=True, response_time_ms=150.0) # noqa: E501 summary = monitor.get_health_summary() assert summary["total_providers"] == 2 assert summary["available_providers"] == 2 assert summary["availability_percentage"] == 100.0 assert "average_success_rate" in summary assert "average_response_time_ms" in summary assert "providers" in summary @pytest.mark.asyncio async def test_start_stop_monitoring(self): """Test starting and stopping health monitoring.""" monitor = ProviderHealthMonitor(health_check_interval=1) monitor.start_monitoring() assert monitor._is_running is True assert monitor._health_check_task is not None await asyncio.sleep(0.1) # Let it run briefly await monitor.stop_monitoring() assert monitor._is_running is False @pytest.mark.asyncio async def test_periodic_health_checks(self): """Test periodic health check execution.""" monitor = ProviderHealthMonitor(health_check_interval=0.1) # Add some data monitor.record_request("test", success=True, response_time_ms=100.0) monitor.start_monitoring() await asyncio.sleep(0.3) # Wait for health checks await monitor.stop_monitoring() metrics = monitor.get_provider_metrics("test") assert metrics.last_check_time is not None class TestRequestMetric: """Test RequestMetric dataclass.""" def test_metric_initialization(self): """Test request metric initialization.""" now = datetime.now() metric = RequestMetric( timestamp=now, success=True, response_time_ms=150.0, bytes_transferred=2048, error_message=None, ) assert metric.timestamp == now assert metric.success is True assert metric.response_time_ms == 150.0 assert metric.bytes_transferred == 2048 assert metric.error_message is None class TestHealthMonitorSingleton: """Test global health monitor singleton.""" def test_get_health_monitor_singleton(self): """Test that get_health_monitor returns singleton.""" monitor1 = get_health_monitor() monitor2 = get_health_monitor() assert monitor1 is monitor2