""" Tests for status_checker module """ import asyncio from unittest.mock import AsyncMock, patch import pytest from services.health_checkers.base import HealthCheckResult from services.status_checker import ServiceStatusChecker class TestServiceStatusChecker: """Test ServiceStatusChecker class""" @pytest.fixture def status_checker(self): """Create ServiceStatusChecker instance""" return ServiceStatusChecker(timeout=5.0) @pytest.mark.asyncio async def test_check_service_health_enabled(self, status_checker): """Test checking enabled service health""" config = {"enabled": True, "url": "http://test.local:8080", "health_check_type": "api", "health_endpoint": "/health"} # Mock the checker mock_checker = AsyncMock() mock_checker.check_health.return_value = HealthCheckResult("healthy", 1.5, uptime="2h 30m") with patch.object(status_checker, "_get_checker_for_service", return_value=mock_checker): result = await status_checker.check_service_health("test_service", config) assert result["status"] == "healthy" assert result["response_time"] == 1.5 assert result["uptime"] == "2h 30m" assert result["error"] is None @pytest.mark.asyncio async def test_check_service_health_disabled(self, status_checker): """Test checking disabled service health""" config = {"enabled": False, "url": "http://test.local:8080"} result = await status_checker.check_service_health("test_service", config) assert result["status"] == "disabled" assert result["response_time"] is None assert result["error"] is None @pytest.mark.asyncio async def test_check_service_health_exception(self, status_checker): """Test checking service health with exception""" config = {"enabled": True, "url": "http://test.local:8080", "health_check_type": "api"} with patch.object(status_checker, "_get_checker_for_service", side_effect=Exception("Test error")): result = await status_checker.check_service_health("test_service", config) assert result["status"] == "error" assert "Test error" in result["error"] @pytest.mark.asyncio async def test_get_checker_for_service_new(self, status_checker): """Test getting new checker for service""" config = {"health_check_type": "api", "url": "http://test.local:8080"} mock_checker = AsyncMock() with patch("services.status_checker.factory.create_checker_for_service", return_value=mock_checker): checker = await status_checker._get_checker_for_service("test_service", config) assert checker == mock_checker assert "test_service" in status_checker.checkers @pytest.mark.asyncio async def test_get_checker_for_service_cached(self, status_checker): """Test getting cached checker for service""" config = {"health_check_type": "api", "url": "http://test.local:8080"} mock_checker = AsyncMock() status_checker.checkers["test_service"] = mock_checker checker = await status_checker._get_checker_for_service("test_service", config) assert checker == mock_checker @pytest.mark.asyncio async def test_check_all_services_success(self, status_checker): """Test checking all services successfully""" # Mock the services configuration with patch( "services.status_checker.SERVICES", { "service1": {"enabled": True, "url": "http://test1.local:8080", "health_check_type": "api"}, "service2": {"enabled": True, "url": "http://test2.local:8080", "health_check_type": "api"}, "service3": {"enabled": False, "url": "http://test3.local:8080"}, }, ): # Mock individual health checks with patch.object(status_checker, "check_service_health") as mock_check: mock_check.side_effect = [ {"status": "healthy", "response_time": 1.0, "error": None, "uptime": "1h", "metadata": {}}, {"status": "unhealthy", "response_time": 2.0, "error": "Connection failed", "uptime": None, "metadata": {}}, {"status": "disabled", "response_time": None, "error": None, "uptime": None, "metadata": {}}, ] result = await status_checker.check_all_services() assert "service1" in result assert "service2" in result assert "service3" in result assert result["service1"]["status"] == "healthy" assert result["service2"]["status"] == "unhealthy" assert result["service3"]["status"] == "disabled" @pytest.mark.asyncio async def test_check_all_services_with_exceptions(self, status_checker): """Test checking all services with some exceptions""" with patch( "services.status_checker.SERVICES", { "service1": {"enabled": True, "url": "http://test1.local:8080", "health_check_type": "api"}, "service2": {"enabled": True, "url": "http://test2.local:8080", "health_check_type": "api"}, }, ): with patch.object(status_checker, "check_service_health") as mock_check: mock_check.side_effect = [ {"status": "healthy", "response_time": 1.0, "error": None, "uptime": "1h", "metadata": {}}, Exception("Service error"), ] result = await status_checker.check_all_services() assert "service1" in result assert "service2" in result assert result["service1"]["status"] == "healthy" assert result["service2"]["status"] == "error" assert "Service error" in result["service2"]["error"] @pytest.mark.asyncio async def test_close(self, status_checker): """Test closing the status checker""" # Add some mock checkers mock_checker1 = AsyncMock() mock_checker2 = AsyncMock() status_checker.checkers = {"service1": mock_checker1, "service2": mock_checker2} await status_checker.close() # Verify close was called on all checkers mock_checker1.close.assert_called_once() mock_checker2.close.assert_called_once() def test_status_checker_initialization(self, status_checker): """Test status checker initialization""" assert status_checker.timeout == 5.0 assert status_checker.checkers == {} class TestStatusCheckerIntegration: """Integration tests for status checker""" @pytest.mark.asyncio async def test_full_health_check_flow(self): """Test the complete health check flow""" status_checker = ServiceStatusChecker(timeout=5.0) # Mock the factory and checkers mock_checker = AsyncMock() mock_checker.check_health.return_value = HealthCheckResult("healthy", 1.5, uptime="2h 30m") with patch("services.status_checker.factory.create_checker_for_service", return_value=mock_checker): with patch( "services.status_checker.SERVICES", {"test_service": {"enabled": True, "url": "http://test.local:8080", "health_check_type": "api", "health_endpoint": "/health"}}, ): result = await status_checker.check_all_services() assert "test_service" in result assert result["test_service"]["status"] == "healthy" assert result["test_service"]["response_time"] == 1.5 assert result["test_service"]["uptime"] == "2h 30m" # Clean up await status_checker.close() @pytest.mark.asyncio async def test_concurrent_health_checks(self): """Test that health checks run concurrently""" status_checker = ServiceStatusChecker(timeout=5.0) # Track the order of calls call_order = [] async def mock_check_health(service_name, config): call_order.append(service_name) # Simulate some processing time await asyncio.sleep(0.1) return {"status": "healthy", "response_time": 0.1, "error": None, "uptime": "1h", "metadata": {}} with patch.object(status_checker, "check_service_health", side_effect=mock_check_health): with patch( "services.status_checker.SERVICES", { "service1": {"enabled": True, "url": "http://test1.local:8080", "health_check_type": "api"}, "service2": {"enabled": True, "url": "http://test2.local:8080", "health_check_type": "api"}, "service3": {"enabled": True, "url": "http://test3.local:8080", "health_check_type": "api"}, }, ): start_time = asyncio.get_event_loop().time() await status_checker.check_all_services() end_time = asyncio.get_event_loop().time() # Should complete in roughly 0.1s (concurrent) rather than 0.3s (sequential) assert end_time - start_time < 0.2 assert len(call_order) == 3 assert "service1" in call_order assert "service2" in call_order assert "service3" in call_order # Clean up await status_checker.close()