From d9ca6cb7dca25cc01607bb265df1b6fdc8cf5324 Mon Sep 17 00:00:00 2001 From: Eva Micankova Date: Thu, 30 Oct 2025 16:23:44 +0100 Subject: [PATCH 1/6] Adding health endpoint integration tests. --- tests/integration/conftest.py | 8 +- .../endpoints/test_health_integration.py | 143 ++++++++++++++++++ 2 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 tests/integration/endpoints/test_health_integration.py diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 60bde90cc..4ae1b8233 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -4,7 +4,7 @@ from typing import Generator import pytest -from fastapi import Request +from fastapi import Request, Response from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker, Session @@ -117,6 +117,12 @@ def test_request_fixture() -> Request: ) +@pytest.fixture(name="test_response") +def test_response_fixture() -> Response: + """Create a test FastAPI Response object with proper scope.""" + return Response(content="", status_code=200, media_type="application/json") + + @pytest.fixture(name="test_auth") async def test_auth_fixture(test_request: Request) -> AuthTuple: """Create authentication using real noop auth module. diff --git a/tests/integration/endpoints/test_health_integration.py b/tests/integration/endpoints/test_health_integration.py new file mode 100644 index 000000000..0144128d5 --- /dev/null +++ b/tests/integration/endpoints/test_health_integration.py @@ -0,0 +1,143 @@ +"""Integration tests for the /health endpoint.""" + +from typing import Generator, Any +import pytest +from pytest_mock import MockerFixture, AsyncMockType + +from fastapi import Response, status +from authentication.interface import AuthTuple + +from configuration import AppConfig, LogicError +from app.endpoints.health import liveness_probe_get_method, readiness_probe_get_method + + +@pytest.fixture(name="mock_llama_stack_client_health") +def mock_llama_stack_client_fixture( + mocker: MockerFixture, +) -> Generator[Any, None, None]: + """Mock only the external Llama Stack client. + + This is the only external dependency we mock for integration tests, + as it represents an external service call. + """ + mock_holder_class = mocker.patch("app.endpoints.health.AsyncLlamaStackClientHolder") + + mock_client = mocker.AsyncMock() + # Mock the version endpoint to return a known version + mock_client.inspect.version.return_value = [] + + # Create a mock holder instance + mock_holder_instance = mock_holder_class.return_value + mock_holder_instance.get_client.return_value = mock_client + + yield mock_client + + +@pytest.mark.asyncio +async def test_health_liveness_fails_without_configuration( + test_auth: AuthTuple, +) -> None: + """Test that liveness probe endpoint fails without loaded configuration. + + This integration test verifies: + - Real noop authentication is used + - Error response structure matches expected format + + Args: + test_auth: noop authentication tuple + """ + + with pytest.raises(LogicError) as exc_info: + await liveness_probe_get_method(auth=test_auth) + + # Verify error message + assert "configuration is not loaded" in str(exc_info.value) + + +@pytest.mark.asyncio +async def test_health_liveness( + test_config: AppConfig, + test_auth: AuthTuple, +) -> None: + """Test that liveness probe endpoint is alive + + This integration test verifies: + - Endpoint handler integrates with configuration system + - Real noop authentication is used + - Response structure matches expected format + + Args: + test_config: Loads test configuration + test_auth: noop authentication tuple + """ + _ = test_config + + response = await liveness_probe_get_method(auth=test_auth) + + # Verify that service is alive + assert response.alive is True + + +@pytest.mark.asyncio +async def test_health_readiness_config_error( + test_response: Response, + test_auth: AuthTuple, +) -> None: + """Test that readiness probe endpoint handles uninitialized client gracefully. + + This integration test verifies: + - Endpoint handles missing client initialization gracefully + - Error is caught and returned as proper health status + - Service returns 503 status code for unhealthy state + - Error message includes details about initialization failure + + Args: + test_response: FastAPI response object + test_auth: noop authentication tuple + """ + result = await readiness_probe_get_method(auth=test_auth, response=test_response) + + # Verify HTTP status code is 503 (Service Unavailable) + assert test_response.status_code == status.HTTP_503_SERVICE_UNAVAILABLE + + # Verify that service returns error response when client not initialized + assert result.ready is False + assert "Providers not healthy" in result.reason + assert "unknown" in result.reason + + # Verify the response includes provider error details + assert len(result.providers) == 1 + assert result.providers[0].provider_id == "unknown" + assert result.providers[0].status == "Error" + assert ( + "AsyncLlamaStackClient has not been initialised" in result.providers[0].message + ) + + +@pytest.mark.asyncio +async def test_health_readiness( + mock_llama_stack_client_health: AsyncMockType, + test_response: Response, + test_auth: AuthTuple, +) -> None: + """Test that readiness probe endpoint returns readiness status. + + This integration test verifies: + - Endpoint handler integrates with configuration system + - Configuration values are correctly accessed + - Real noop authentication is used + - Response structure matches expected format + + Args: + mock_llama_stack_client_health: Mocked Llama Stack client + test_response: FastAPI response object + test_auth: noop authentication tuple + """ + _ = mock_llama_stack_client_health + + result = await readiness_probe_get_method(auth=test_auth, response=test_response) + + # Verify that service returns readiness response + assert result.ready is True + assert result.reason == "All providers are healthy" + assert result.providers is not None From 6d8bcc4865c56fd2654198642a3a7dfad9d783cf Mon Sep 17 00:00:00 2001 From: Eva Micankova Date: Thu, 30 Oct 2025 16:40:16 +0100 Subject: [PATCH 2/6] Removing liveness probe endpoint test when config is not loaded. --- .../endpoints/test_health_integration.py | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/tests/integration/endpoints/test_health_integration.py b/tests/integration/endpoints/test_health_integration.py index 0144128d5..1e04ff223 100644 --- a/tests/integration/endpoints/test_health_integration.py +++ b/tests/integration/endpoints/test_health_integration.py @@ -33,27 +33,6 @@ def mock_llama_stack_client_fixture( yield mock_client -@pytest.mark.asyncio -async def test_health_liveness_fails_without_configuration( - test_auth: AuthTuple, -) -> None: - """Test that liveness probe endpoint fails without loaded configuration. - - This integration test verifies: - - Real noop authentication is used - - Error response structure matches expected format - - Args: - test_auth: noop authentication tuple - """ - - with pytest.raises(LogicError) as exc_info: - await liveness_probe_get_method(auth=test_auth) - - # Verify error message - assert "configuration is not loaded" in str(exc_info.value) - - @pytest.mark.asyncio async def test_health_liveness( test_config: AppConfig, From 9e02f793d57bd1e24fa346b70dbff54b03777012 Mon Sep 17 00:00:00 2001 From: Eva Micankova Date: Fri, 31 Oct 2025 09:58:23 +0100 Subject: [PATCH 3/6] Removing unused imports. --- tests/integration/endpoints/test_health_integration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/endpoints/test_health_integration.py b/tests/integration/endpoints/test_health_integration.py index 1e04ff223..a6524335f 100644 --- a/tests/integration/endpoints/test_health_integration.py +++ b/tests/integration/endpoints/test_health_integration.py @@ -7,7 +7,7 @@ from fastapi import Response, status from authentication.interface import AuthTuple -from configuration import AppConfig, LogicError +from configuration import AppConfig from app.endpoints.health import liveness_probe_get_method, readiness_probe_get_method From 683a8f16734f5cb37abc71a1176b9f207f428cd7 Mon Sep 17 00:00:00 2001 From: Eva Micankova Date: Fri, 31 Oct 2025 13:21:49 +0100 Subject: [PATCH 4/6] Adding test for provider health statuses. --- .../endpoints/test_health_integration.py | 57 ++++++++++++++++++- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/tests/integration/endpoints/test_health_integration.py b/tests/integration/endpoints/test_health_integration.py index a6524335f..de7b570d1 100644 --- a/tests/integration/endpoints/test_health_integration.py +++ b/tests/integration/endpoints/test_health_integration.py @@ -8,8 +8,8 @@ from authentication.interface import AuthTuple from configuration import AppConfig -from app.endpoints.health import liveness_probe_get_method, readiness_probe_get_method - +from app.endpoints.health import liveness_probe_get_method, readiness_probe_get_method, get_providers_health_statuses +from llama_stack.providers.datatypes import HealthStatus @pytest.fixture(name="mock_llama_stack_client_health") def mock_llama_stack_client_fixture( @@ -58,7 +58,58 @@ async def test_health_liveness( @pytest.mark.asyncio -async def test_health_readiness_config_error( +async def test_health_readiness_provider_statuses( + mock_llama_stack_client_health: AsyncMockType, + mocker +) -> None: + """Test that get_providers_health_statuses correctly retrieves and returns + provider health statuses. + + This integration test verifies: + - Function correctly retrieves provider list from Llama Stack client + - Both healthy and unhealthy providers are properly processed + - Provider health status, ID, and error messages are correctly mapped + - Multiple providers with different health states are handled correctly + + Args: + mock_llama_stack_client_health: Mocked Llama Stack client + mocker: pytest-mock fixture for creating mock objects + """ + # Arrange: Set up mock provider list with mixed health statuses + mock_llama_stack_client_health.providers.list.return_value = [ + mocker.Mock( + provider_id="unhealthy-provider-1", + health={"status": HealthStatus.ERROR.value, "message": "Database connection failed"} + ), + mocker.Mock( + provider_id="unhealthy-provider-2", + health={"status": HealthStatus.ERROR.value, "message": "Service unavailable"} + ), + mocker.Mock( + provider_id="healthy-provider", + health={"status": "ok", "message": ""} + ), + ] + + # Call the function to retrieve provider health statuses + result = await get_providers_health_statuses() + + # Verify providers + assert result[0].provider_id == "unhealthy-provider-1" + assert result[0].status == "Error" + assert result[0].message == "Database connection failed" + + assert result[1].provider_id == "unhealthy-provider-2" + assert result[1].status == "Error" + assert result[1].message == "Service unavailable" + + assert result[2].provider_id == "healthy-provider" + assert result[2].status == "ok" + assert result[2].message == "" + + +@pytest.mark.asyncio +async def test_health_readiness_client_error( test_response: Response, test_auth: AuthTuple, ) -> None: From 6adf5fc65475f3041adf63f2de75ad8f8180b54e Mon Sep 17 00:00:00 2001 From: Eva Micankova Date: Fri, 31 Oct 2025 13:22:15 +0100 Subject: [PATCH 5/6] Adding test for provider health statuses. --- .../endpoints/test_health_integration.py | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/tests/integration/endpoints/test_health_integration.py b/tests/integration/endpoints/test_health_integration.py index de7b570d1..7c188588a 100644 --- a/tests/integration/endpoints/test_health_integration.py +++ b/tests/integration/endpoints/test_health_integration.py @@ -8,9 +8,14 @@ from authentication.interface import AuthTuple from configuration import AppConfig -from app.endpoints.health import liveness_probe_get_method, readiness_probe_get_method, get_providers_health_statuses +from app.endpoints.health import ( + liveness_probe_get_method, + readiness_probe_get_method, + get_providers_health_statuses, +) from llama_stack.providers.datatypes import HealthStatus + @pytest.fixture(name="mock_llama_stack_client_health") def mock_llama_stack_client_fixture( mocker: MockerFixture, @@ -60,7 +65,7 @@ async def test_health_liveness( @pytest.mark.asyncio async def test_health_readiness_provider_statuses( mock_llama_stack_client_health: AsyncMockType, - mocker + mocker: MockerFixture, ) -> None: """Test that get_providers_health_statuses correctly retrieves and returns provider health statuses. @@ -79,15 +84,20 @@ async def test_health_readiness_provider_statuses( mock_llama_stack_client_health.providers.list.return_value = [ mocker.Mock( provider_id="unhealthy-provider-1", - health={"status": HealthStatus.ERROR.value, "message": "Database connection failed"} + health={ + "status": HealthStatus.ERROR.value, + "message": "Database connection failed", + }, ), mocker.Mock( provider_id="unhealthy-provider-2", - health={"status": HealthStatus.ERROR.value, "message": "Service unavailable"} + health={ + "status": HealthStatus.ERROR.value, + "message": "Service unavailable", + }, ), mocker.Mock( - provider_id="healthy-provider", - health={"status": "ok", "message": ""} + provider_id="healthy-provider", health={"status": "ok", "message": ""} ), ] From 64ce25a7f0e9b3c0f59e7559bd90f5a3c1d0c9dc Mon Sep 17 00:00:00 2001 From: Eva Micankova Date: Fri, 31 Oct 2025 13:26:59 +0100 Subject: [PATCH 6/6] Fixing pylinter imports order. --- tests/integration/endpoints/test_health_integration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/endpoints/test_health_integration.py b/tests/integration/endpoints/test_health_integration.py index 7c188588a..10fd9b2a5 100644 --- a/tests/integration/endpoints/test_health_integration.py +++ b/tests/integration/endpoints/test_health_integration.py @@ -3,6 +3,7 @@ from typing import Generator, Any import pytest from pytest_mock import MockerFixture, AsyncMockType +from llama_stack.providers.datatypes import HealthStatus from fastapi import Response, status from authentication.interface import AuthTuple @@ -13,7 +14,6 @@ readiness_probe_get_method, get_providers_health_statuses, ) -from llama_stack.providers.datatypes import HealthStatus @pytest.fixture(name="mock_llama_stack_client_health")