diff --git a/tests/unit/app/endpoints/test_config.py b/tests/unit/app/endpoints/test_config.py index 05f97791e..dcaca95ed 100644 --- a/tests/unit/app/endpoints/test_config.py +++ b/tests/unit/app/endpoints/test_config.py @@ -1,45 +1,60 @@ """Unit tests for the /config REST API endpoint.""" +from typing import Any import pytest from pytest_mock import MockerFixture from fastapi import HTTPException, Request, status +from authentication.interface import AuthTuple from app.endpoints.config import config_endpoint_handler from configuration import AppConfig from tests.unit.utils.auth_helpers import mock_authorization_resolvers @pytest.mark.asyncio -async def test_config_endpoint_handler_configuration_not_loaded(mocker: MockerFixture): - """Test the config endpoint handler.""" +async def test_config_endpoint_handler_configuration_not_loaded( + mocker: MockerFixture, +) -> None: + """Test the config endpoint handler when configuration is not loaded.""" mock_authorization_resolvers(mocker) + # mock for missing configuration mocker.patch( "app.endpoints.config.configuration._configuration", new=None, ) mocker.patch("app.endpoints.config.configuration", None) + # HTTP request mock required by URL endpoint handler request = Request( scope={ "type": "http", } ) - auth = ("test_user", "token", {}) + + # authorization tuple required by URL endpoint handler + auth: AuthTuple = ("test_user_id", "test_user", True, "test_token") + with pytest.raises(HTTPException) as exc_info: await config_endpoint_handler( auth=auth, request=request # pyright:ignore[reportArgumentType] ) assert exc_info.value.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR - assert exc_info.value.detail["response"] == "Configuration is not loaded" + + detail = exc_info.value.detail + assert isinstance(detail, dict) + assert detail["response"] == "Configuration is not loaded" @pytest.mark.asyncio -async def test_config_endpoint_handler_configuration_loaded(mocker: MockerFixture): - """Test the config endpoint handler.""" +async def test_config_endpoint_handler_configuration_loaded( + mocker: MockerFixture, +) -> None: + """Test the config endpoint handler when configuration is loaded.""" mock_authorization_resolvers(mocker) - config_dict = { + # configuration to be loaded + config_dict: dict[Any, Any] = { "name": "foo", "service": { "host": "localhost", @@ -63,18 +78,24 @@ async def test_config_endpoint_handler_configuration_loaded(mocker: MockerFixtur "authorization": {"access_rules": []}, "customization": None, } + + # load the configuration cfg = AppConfig() cfg.init_from_dict(config_dict) # Mock configuration mocker.patch("app.endpoints.config.configuration", cfg) + # HTTP request mock required by URL endpoint handler request = Request( scope={ "type": "http", } ) - auth = ("test_user", "token", {}) + + # authorization tuple required by URL endpoint handler + auth: AuthTuple = ("test_user_id", "test_user", True, "test_token") + response = await config_endpoint_handler( auth=auth, request=request # pyright:ignore[reportArgumentType] ) diff --git a/tests/unit/app/endpoints/test_feedback.py b/tests/unit/app/endpoints/test_feedback.py index a17147c3a..22b631f2a 100644 --- a/tests/unit/app/endpoints/test_feedback.py +++ b/tests/unit/app/endpoints/test_feedback.py @@ -1,5 +1,6 @@ """Unit tests for the /feedback REST API endpoint.""" +from typing import Any from fastapi import HTTPException, status import pytest from pytest_mock import MockerFixture @@ -12,6 +13,7 @@ store_feedback, update_feedback_status, ) +from authentication.interface import AuthTuple from models.requests import FeedbackStatusUpdateRequest, FeedbackRequest from tests.unit.utils.auth_helpers import mock_authorization_resolvers @@ -24,19 +26,19 @@ } -def test_is_feedback_enabled(): +def test_is_feedback_enabled() -> None: """Test that is_feedback_enabled returns True when feedback is not disabled.""" configuration.user_data_collection_configuration.feedback_enabled = True assert is_feedback_enabled() is True, "Feedback should be enabled" -def test_is_feedback_disabled(): +def test_is_feedback_disabled() -> None: """Test that is_feedback_enabled returns False when feedback is disabled.""" configuration.user_data_collection_configuration.feedback_enabled = False assert is_feedback_enabled() is False, "Feedback should be disabled" -async def test_assert_feedback_enabled_disabled(mocker: MockerFixture): +async def test_assert_feedback_enabled_disabled(mocker: MockerFixture) -> None: """Test that assert_feedback_enabled raises HTTPException when feedback is disabled.""" # Simulate feedback being disabled @@ -49,7 +51,7 @@ async def test_assert_feedback_enabled_disabled(mocker: MockerFixture): assert exc_info.value.detail == "Forbidden: Feedback is disabled" -async def test_assert_feedback_enabled(mocker: MockerFixture): +async def test_assert_feedback_enabled(mocker: MockerFixture) -> None: """Test that assert_feedback_enabled does not raise an exception when feedback is enabled.""" # Simulate feedback being enabled @@ -74,7 +76,9 @@ async def test_assert_feedback_enabled(mocker: MockerFixture): ids=["no_categories", "with_negative_categories"], ) @pytest.mark.asyncio -async def test_feedback_endpoint_handler(mocker, feedback_request_data): +async def test_feedback_endpoint_handler( + mocker: MockerFixture, feedback_request_data: dict[str, Any] +) -> None: """Test that feedback_endpoint_handler processes feedback for different payloads.""" mock_authorization_resolvers(mocker) @@ -87,11 +91,14 @@ async def test_feedback_endpoint_handler(mocker, feedback_request_data): feedback_request = mocker.Mock() feedback_request.model_dump.return_value = feedback_request_data + # Authorization tuple required by URL endpoint handler + auth: AuthTuple = ("test_user_id", "test_user", True, "test_token") + # Call the endpoint handler result = await feedback_endpoint_handler( feedback_request=feedback_request, _ensure_feedback_enabled=assert_feedback_enabled, - auth=("test_user_id", "test_username", False, "test_token"), + auth=auth, ) # Assert that the expected response is returned @@ -99,7 +106,7 @@ async def test_feedback_endpoint_handler(mocker, feedback_request_data): @pytest.mark.asyncio -async def test_feedback_endpoint_handler_error(mocker: MockerFixture): +async def test_feedback_endpoint_handler_error(mocker: MockerFixture) -> None: """Test that feedback_endpoint_handler raises an HTTPException on error.""" mock_authorization_resolvers(mocker) @@ -113,16 +120,22 @@ async def test_feedback_endpoint_handler_error(mocker: MockerFixture): # Mock the feedback request feedback_request = mocker.Mock() + # Authorization tuple required by URL endpoint handler + auth: AuthTuple = ("test_user_id", "test_user", True, "test_token") + # Call the endpoint handler and assert it raises an exception with pytest.raises(HTTPException) as exc_info: await feedback_endpoint_handler( feedback_request=feedback_request, _ensure_feedback_enabled=assert_feedback_enabled, - auth=("test_user_id", "test_username", False, "test_token"), + auth=auth, ) assert exc_info.value.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR - assert exc_info.value.detail["response"] == "Error storing user feedback" + + detail = exc_info.value.detail + assert isinstance(detail, dict) + assert detail["response"] == "Error storing user feedback" @pytest.mark.parametrize( @@ -145,7 +158,9 @@ async def test_feedback_endpoint_handler_error(mocker: MockerFixture): ], ids=["negative_text_feedback", "negative_feedback_with_categories"], ) -def test_store_feedback(mocker, feedback_request_data): +def test_store_feedback( + mocker: MockerFixture, feedback_request_data: dict[str, Any] +) -> None: """Test that store_feedback correctly stores various feedback payloads.""" configuration.user_data_collection_configuration.feedback_storage = "fake-path" @@ -191,7 +206,9 @@ def test_store_feedback(mocker, feedback_request_data): ], ids=["negative_text_feedback", "negative_feedback_with_categories"], ) -def test_store_feedback_on_io_error(mocker, feedback_request_data): +def test_store_feedback_on_io_error( + mocker: MockerFixture, feedback_request_data: dict[str, Any] +) -> None: """Test the OSError and IOError handlings during feedback storage.""" # non-writable path @@ -206,14 +223,17 @@ def test_store_feedback_on_io_error(mocker, feedback_request_data): store_feedback(user_id, feedback_request_data) -async def test_update_feedback_status_different(mocker: MockerFixture): +async def test_update_feedback_status_different(mocker: MockerFixture) -> None: """Test that update_feedback_status returns the correct status with an update.""" configuration.user_data_collection_configuration.feedback_enabled = True + # Authorization tuple required by URL endpoint handler + auth: AuthTuple = ("test_user_id", "test_user", True, "test_token") + req = FeedbackStatusUpdateRequest(status=False) resp = await update_feedback_status( req, - auth=("test_user_id", "test_username", False, "test_token"), + auth=auth, ) assert resp.status == { "previous_status": True, @@ -223,14 +243,17 @@ async def test_update_feedback_status_different(mocker: MockerFixture): } -async def test_update_feedback_status_no_change(mocker: MockerFixture): +async def test_update_feedback_status_no_change(mocker: MockerFixture) -> None: """Test that update_feedback_status returns the correct status with no update.""" configuration.user_data_collection_configuration.feedback_enabled = True + # Authorization tuple required by URL endpoint handler + auth: AuthTuple = ("test_user_id", "test_user", True, "test_token") + req = FeedbackStatusUpdateRequest(status=True) resp = await update_feedback_status( req, - auth=("test_user_id", "test_username", False, "test_token"), + auth=auth, ) assert resp.status == { "previous_status": True, @@ -250,7 +273,9 @@ async def test_update_feedback_status_no_change(mocker: MockerFixture): ids=["test_sentiment_only", "test_user_feedback_only", "test_categories_only"], ) @pytest.mark.asyncio -async def test_feedback_endpoint_valid_requests(mocker: MockerFixture, payload): +async def test_feedback_endpoint_valid_requests( + mocker: MockerFixture, payload: dict[str, Any] +) -> None: """Test endpoint with valid feedback payloads.""" mock_authorization_resolvers(mocker) mocker.patch("app.endpoints.feedback.store_feedback") diff --git a/tests/unit/app/endpoints/test_health.py b/tests/unit/app/endpoints/test_health.py index 2be703bcf..60782b01b 100644 --- a/tests/unit/app/endpoints/test_health.py +++ b/tests/unit/app/endpoints/test_health.py @@ -4,6 +4,7 @@ import pytest from llama_stack.providers.datatypes import HealthStatus +from authentication.interface import AuthTuple from app.endpoints.health import ( readiness_probe_get_method, liveness_probe_get_method, @@ -14,7 +15,9 @@ @pytest.mark.asyncio -async def test_readiness_probe_fails_due_to_unhealthy_providers(mocker: MockerFixture): +async def test_readiness_probe_fails_due_to_unhealthy_providers( + mocker: MockerFixture, +) -> None: """Test the readiness endpoint handler fails when providers are unhealthy.""" mock_authorization_resolvers(mocker) @@ -32,7 +35,9 @@ async def test_readiness_probe_fails_due_to_unhealthy_providers(mocker: MockerFi # Mock the Response object and auth mock_response = mocker.Mock() - auth = ("test_user", "token", {}) + + # Authorization tuple required by URL endpoint handler + auth: AuthTuple = ("test_user_id", "test_user", True, "test_token") response = await readiness_probe_get_method(auth=auth, response=mock_response) @@ -45,7 +50,7 @@ async def test_readiness_probe_fails_due_to_unhealthy_providers(mocker: MockerFi @pytest.mark.asyncio async def test_readiness_probe_success_when_all_providers_healthy( mocker: MockerFixture, -): +) -> None: """Test the readiness endpoint handler succeeds when all providers are healthy.""" mock_authorization_resolvers(mocker) @@ -68,7 +73,9 @@ async def test_readiness_probe_success_when_all_providers_healthy( # Mock the Response object and auth mock_response = mocker.Mock() - auth = ("test_user", "token", {}) + + # Authorization tuple required by URL endpoint handler + auth: AuthTuple = ("test_user_id", "test_user", True, "test_token") response = await readiness_probe_get_method(auth=auth, response=mock_response) assert response is not None @@ -80,11 +87,13 @@ async def test_readiness_probe_success_when_all_providers_healthy( @pytest.mark.asyncio -async def test_liveness_probe(mocker: MockerFixture): +async def test_liveness_probe(mocker: MockerFixture) -> None: """Test the liveness endpoint handler.""" mock_authorization_resolvers(mocker) - auth = ("test_user", "token", {}) + # Authorization tuple required by URL endpoint handler + auth: AuthTuple = ("test_user_id", "test_user", True, "test_token") + response = await liveness_probe_get_method(auth=auth) assert response is not None assert response.alive is True @@ -93,7 +102,7 @@ async def test_liveness_probe(mocker: MockerFixture): class TestProviderHealthStatus: """Test cases for the ProviderHealthStatus model.""" - def test_provider_health_status_creation(self): + def test_provider_health_status_creation(self) -> None: """Test creating a ProviderHealthStatus instance.""" status = ProviderHealthStatus( provider_id="test_provider", status="ok", message="All good" @@ -102,7 +111,7 @@ def test_provider_health_status_creation(self): assert status.status == "ok" assert status.message == "All good" - def test_provider_health_status_optional_fields(self): + def test_provider_health_status_optional_fields(self) -> None: """Test creating a ProviderHealthStatus with minimal fields.""" status = ProviderHealthStatus(provider_id="test_provider", status="ok") assert status.provider_id == "test_provider" @@ -113,7 +122,7 @@ def test_provider_health_status_optional_fields(self): class TestGetProvidersHealthStatuses: """Test cases for the get_providers_health_statuses function.""" - async def test_get_providers_health_statuses(self, mocker: MockerFixture): + async def test_get_providers_health_statuses(self, mocker: MockerFixture) -> None: """Test get_providers_health_statuses with healthy providers.""" # Mock the imports mock_lsc = mocker.patch("client.AsyncLlamaStackClientHolder.get_client") @@ -166,7 +175,7 @@ async def test_get_providers_health_statuses(self, mocker: MockerFixture): async def test_get_providers_health_statuses_connection_error( self, mocker: MockerFixture - ): + ) -> None: """Test get_providers_health_statuses when connection fails.""" # Mock the imports mock_lsc = mocker.patch("client.AsyncLlamaStackClientHolder.get_client") diff --git a/tests/unit/app/endpoints/test_info.py b/tests/unit/app/endpoints/test_info.py index 3265f1f75..816749e38 100644 --- a/tests/unit/app/endpoints/test_info.py +++ b/tests/unit/app/endpoints/test_info.py @@ -1,23 +1,26 @@ """Unit tests for the /info REST API endpoint.""" +from typing import Any import pytest from fastapi import Request, HTTPException, status +from pytest_mock import MockerFixture from llama_stack_client import APIConnectionError from llama_stack_client.types import VersionInfo +from authentication.interface import AuthTuple from app.endpoints.info import info_endpoint_handler from configuration import AppConfig from tests.unit.utils.auth_helpers import mock_authorization_resolvers @pytest.mark.asyncio -async def test_info_endpoint(mocker): +async def test_info_endpoint(mocker: MockerFixture) -> None: """Test the info endpoint handler.""" mock_authorization_resolvers(mocker) # configuration for tests - config_dict = { + config_dict: dict[Any, Any] = { "name": "foo", "service": { "host": "localhost", @@ -55,12 +58,16 @@ async def test_info_endpoint(mocker): mock_authorization_resolvers(mocker) + # HTTP request mock required by URL endpoint handler request = Request( scope={ "type": "http", } ) - auth = ("test_user", "token", {}) + + # Authorization tuple required by URL endpoint handler + auth: AuthTuple = ("test_user_id", "test_user", True, "test_token") + response = await info_endpoint_handler(auth=auth, request=request) assert response is not None assert response.name is not None @@ -69,12 +76,12 @@ async def test_info_endpoint(mocker): @pytest.mark.asyncio -async def test_info_endpoint_connection_error(mocker): +async def test_info_endpoint_connection_error(mocker: MockerFixture) -> None: """Test the info endpoint handler.""" mock_authorization_resolvers(mocker) # configuration for tests - config_dict = { + config_dict: dict[Any, Any] = { "name": "foo", "service": { "host": "localhost", @@ -101,7 +108,7 @@ async def test_info_endpoint_connection_error(mocker): # Mock the LlamaStack client mock_client = mocker.AsyncMock() - mock_client.inspect.version.side_effect = APIConnectionError(request=None) + mock_client.inspect.version.side_effect = APIConnectionError(request=None) # type: ignore mock_lsc = mocker.patch("client.AsyncLlamaStackClientHolder.get_client") mock_lsc.return_value = mock_client mock_config = mocker.Mock() @@ -112,12 +119,15 @@ async def test_info_endpoint_connection_error(mocker): mock_authorization_resolvers(mocker) + # HTTP request mock required by URL endpoint handler request = Request( scope={ "type": "http", } ) - auth = ("test_user", "token", {}) + + # Authorization tuple required by URL endpoint handler + auth: AuthTuple = ("test_user_id", "test_user", True, "test_token") with pytest.raises(HTTPException) as e: await info_endpoint_handler(auth=auth, request=request) diff --git a/tests/unit/app/endpoints/test_query.py b/tests/unit/app/endpoints/test_query.py index a96c4f003..0b18dc438 100644 --- a/tests/unit/app/endpoints/test_query.py +++ b/tests/unit/app/endpoints/test_query.py @@ -1,13 +1,16 @@ -# pylint: disable=redefined-outer-name - """Unit tests for the /query REST API endpoint.""" +# pylint: disable=redefined-outer-name # pylint: disable=too-many-lines +# pylint: disable=ungrouped-imports import json +from typing import Any import pytest +from pytest_mock import MockerFixture from fastapi import HTTPException, Request, status + from llama_stack_client import APIConnectionError from llama_stack_client.types import UserMessage # type: ignore from llama_stack_client.types.agents.turn import Turn @@ -16,6 +19,8 @@ from llama_stack_client.types.tool_response import ToolResponse from pydantic import AnyUrl +from tests.unit.conftest import AgentFixtures + from app.endpoints.query import ( evaluate_model_hints, get_topic_summary, @@ -63,7 +68,7 @@ def dummy_request() -> Request: return req -def mock_metrics(mocker) -> None: +def mock_metrics(mocker: MockerFixture) -> None: """Helper function to mock metrics operations for query endpoints.""" mocker.patch( "app.endpoints.query.extract_and_update_token_metrics", @@ -75,7 +80,7 @@ def mock_metrics(mocker) -> None: mocker.patch("metrics.llm_calls_total") -def mock_database_operations(mocker) -> None: +def mock_database_operations(mocker: MockerFixture) -> None: """Helper function to mock database operations for query endpoints.""" mocker.patch( "app.endpoints.query.validate_conversation_ownership", return_value=True @@ -91,9 +96,9 @@ def mock_database_operations(mocker) -> None: @pytest.fixture(name="setup_configuration") -def setup_configuration_fixture(): +def setup_configuration_fixture() -> AppConfig: """Set up configuration for tests.""" - config_dict = { + config_dict: dict[Any, Any] = { "name": "test", "service": { "host": "localhost", @@ -124,7 +129,7 @@ def setup_configuration_fixture(): @pytest.mark.asyncio async def test_query_endpoint_handler_configuration_not_loaded( - mocker, dummy_request + mocker: MockerFixture, dummy_request: Request ) -> None: """Test the query endpoint handler if configuration is not loaded.""" # simulate state when no configuration is loaded @@ -143,10 +148,15 @@ async def test_query_endpoint_handler_configuration_not_loaded( auth=("test-user", "", False, "token"), ) assert e.value.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR - assert e.value.detail["response"] == "Configuration is not loaded" + + detail = e.value.detail + assert isinstance(detail, dict) + assert detail["response"] == "Configuration is not loaded" -def test_is_transcripts_enabled(setup_configuration, mocker) -> None: +def test_is_transcripts_enabled( + setup_configuration: AppConfig, mocker: MockerFixture +) -> None: """Test that is_transcripts_enabled returns True when transcripts is not disabled.""" # Override the transcripts_enabled setting mocker.patch.object( @@ -159,7 +169,9 @@ def test_is_transcripts_enabled(setup_configuration, mocker) -> None: assert is_transcripts_enabled() is True, "Transcripts should be enabled" -def test_is_transcripts_disabled(setup_configuration, mocker) -> None: +def test_is_transcripts_disabled( + setup_configuration: AppConfig, mocker: MockerFixture +) -> None: """Test that is_transcripts_enabled returns False when transcripts is disabled.""" # Use default transcripts_enabled=False from setup mocker.patch("app.endpoints.query.configuration", setup_configuration) @@ -169,7 +181,9 @@ def test_is_transcripts_disabled(setup_configuration, mocker) -> None: # pylint: disable=too-many-locals async def _test_query_endpoint_handler( - mocker, dummy_request: Request, store_transcript_to_file=False + mocker: MockerFixture, + dummy_request: Request, + store_transcript_to_file: bool = False, ) -> None: """Test the query endpoint handler.""" mock_client = mocker.AsyncMock() @@ -285,7 +299,7 @@ async def _test_query_endpoint_handler( @pytest.mark.asyncio async def test_query_endpoint_handler_transcript_storage_disabled( - mocker, dummy_request + mocker: MockerFixture, dummy_request: Request ) -> None: """Test the query endpoint handler with transcript storage disabled.""" await _test_query_endpoint_handler( @@ -294,14 +308,16 @@ async def test_query_endpoint_handler_transcript_storage_disabled( @pytest.mark.asyncio -async def test_query_endpoint_handler_store_transcript(mocker, dummy_request) -> None: +async def test_query_endpoint_handler_store_transcript( + mocker: MockerFixture, dummy_request: Request +) -> None: """Test the query endpoint handler with transcript storage enabled.""" await _test_query_endpoint_handler( mocker, dummy_request, store_transcript_to_file=True ) -def test_select_model_and_provider_id_from_request(mocker) -> None: +def test_select_model_and_provider_id_from_request(mocker: MockerFixture) -> None: """Test the select_model_and_provider_id function.""" mocker.patch( "metrics.utils.configuration.inference.default_provider", @@ -341,7 +357,7 @@ def test_select_model_and_provider_id_from_request(mocker) -> None: assert provider_id == "provider2" -def test_select_model_and_provider_id_from_configuration(mocker) -> None: +def test_select_model_and_provider_id_from_configuration(mocker: MockerFixture) -> None: """Test the select_model_and_provider_id function.""" mocker.patch( "metrics.utils.configuration.inference.default_provider", @@ -378,7 +394,7 @@ def test_select_model_and_provider_id_from_configuration(mocker) -> None: assert provider_id == "default_provider" -def test_select_model_and_provider_id_first_from_list(mocker) -> None: +def test_select_model_and_provider_id_first_from_list(mocker: MockerFixture) -> None: """Test the select_model_and_provider_id function when no model is specified.""" model_list = [ mocker.Mock( @@ -405,7 +421,7 @@ def test_select_model_and_provider_id_first_from_list(mocker) -> None: assert provider_id == "provider1" -def test_select_model_and_provider_id_invalid_model(mocker) -> None: +def test_select_model_and_provider_id_invalid_model(mocker: MockerFixture) -> None: """Test the select_model_and_provider_id function with an invalid model.""" mock_client = mocker.Mock() mock_client.models.list.return_value = [ @@ -427,7 +443,9 @@ def test_select_model_and_provider_id_invalid_model(mocker) -> None: ) -def test_select_model_and_provider_id_no_available_models(mocker) -> None: +def test_select_model_and_provider_id_no_available_models( + mocker: MockerFixture, +) -> None: """Test the select_model_and_provider_id function with no available models.""" mock_client = mocker.Mock() # empty list of models @@ -475,10 +493,11 @@ def test_validate_attachments_metadata_invalid_type() -> None: with pytest.raises(HTTPException) as exc_info: validate_attachments_metadata(attachments) assert exc_info.value.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - assert ( - "Attachment with improper type invalid_type detected" - in exc_info.value.detail["cause"] - ) + + detail = exc_info.value.detail + assert isinstance(detail, dict) + assert detail["response"] == "Unable to process this request" + assert "Attachment with improper type invalid_type detected" in detail["cause"] def test_validate_attachments_metadata_invalid_content_type() -> None: @@ -494,15 +513,19 @@ def test_validate_attachments_metadata_invalid_content_type() -> None: with pytest.raises(HTTPException) as exc_info: validate_attachments_metadata(attachments) assert exc_info.value.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY + + detail = exc_info.value.detail + assert isinstance(detail, dict) + assert detail["response"] == "Unable to process this request" assert ( "Attachment with improper content type text/invalid_content_type detected" - in exc_info.value.detail["cause"] + in detail["cause"] ) @pytest.mark.asyncio async def test_retrieve_response_no_returned_message( - prepare_agent_mocks, mocker + prepare_agent_mocks: AgentFixtures, mocker: MockerFixture ) -> None: """Test the retrieve_response function.""" mock_client, mock_agent = prepare_agent_mocks @@ -540,7 +563,7 @@ async def test_retrieve_response_no_returned_message( @pytest.mark.asyncio async def test_retrieve_response_message_without_content( - prepare_agent_mocks, mocker + prepare_agent_mocks: AgentFixtures, mocker: MockerFixture ) -> None: """Test the retrieve_response function.""" mock_client, mock_agent = prepare_agent_mocks @@ -578,7 +601,7 @@ async def test_retrieve_response_message_without_content( @pytest.mark.asyncio async def test_retrieve_response_vector_db_available( - prepare_agent_mocks, mocker + prepare_agent_mocks: AgentFixtures, mocker: MockerFixture ) -> None: """Test the retrieve_response function.""" mock_metric = mocker.patch("metrics.llm_calls_validation_errors_total") @@ -626,7 +649,7 @@ async def test_retrieve_response_vector_db_available( @pytest.mark.asyncio async def test_retrieve_response_no_available_shields( - prepare_agent_mocks, mocker + prepare_agent_mocks: AgentFixtures, mocker: MockerFixture ) -> None: """Test the retrieve_response function.""" mock_client, mock_agent = prepare_agent_mocks @@ -669,20 +692,20 @@ async def test_retrieve_response_no_available_shields( @pytest.mark.asyncio async def test_retrieve_response_one_available_shield( - prepare_agent_mocks, mocker + prepare_agent_mocks: AgentFixtures, mocker: MockerFixture ) -> None: """Test the retrieve_response function.""" class MockShield: """Mock for Llama Stack shield to be used.""" - def __init__(self, identifier): + def __init__(self, identifier: str) -> None: self.identifier = identifier - def __str__(self): + def __str__(self) -> str: return "MockShield" - def __repr__(self): + def __repr__(self) -> str: return "MockShield" mock_client, mock_agent = prepare_agent_mocks @@ -725,20 +748,20 @@ def __repr__(self): @pytest.mark.asyncio async def test_retrieve_response_two_available_shields( - prepare_agent_mocks, mocker + prepare_agent_mocks: AgentFixtures, mocker: MockerFixture ) -> None: """Test the retrieve_response function.""" class MockShield: """Mock for Llama Stack shield to be used.""" - def __init__(self, identifier): + def __init__(self, identifier: str): self.identifier = identifier - def __str__(self): + def __str__(self) -> str: return "MockShield" - def __repr__(self): + def __repr__(self) -> str: return "MockShield" mock_client, mock_agent = prepare_agent_mocks @@ -784,20 +807,20 @@ def __repr__(self): @pytest.mark.asyncio async def test_retrieve_response_four_available_shields( - prepare_agent_mocks, mocker + prepare_agent_mocks: AgentFixtures, mocker: MockerFixture ) -> None: """Test the retrieve_response function.""" class MockShield: """Mock for Llama Stack shield to be used.""" - def __init__(self, identifier): + def __init__(self, identifier: str) -> None: self.identifier = identifier - def __str__(self): + def __str__(self) -> str: return "MockShield" - def __repr__(self): + def __repr__(self) -> str: return "MockShield" mock_client, mock_agent = prepare_agent_mocks @@ -857,7 +880,7 @@ def __repr__(self): @pytest.mark.asyncio async def test_retrieve_response_with_one_attachment( - prepare_agent_mocks, mocker + prepare_agent_mocks: AgentFixtures, mocker: MockerFixture ) -> None: """Test the retrieve_response function.""" mock_client, mock_agent = prepare_agent_mocks @@ -913,7 +936,7 @@ async def test_retrieve_response_with_one_attachment( @pytest.mark.asyncio async def test_retrieve_response_with_two_attachments( - prepare_agent_mocks, mocker + prepare_agent_mocks: AgentFixtures, mocker: MockerFixture ) -> None: """Test the retrieve_response function.""" mock_client, mock_agent = prepare_agent_mocks @@ -976,7 +999,7 @@ async def test_retrieve_response_with_two_attachments( ) -def test_parse_metadata_from_text_item_valid(mocker) -> None: +def test_parse_metadata_from_text_item_valid(mocker: MockerFixture) -> None: """Test parsing metadata from a TextContentItem.""" text = """ Some text... @@ -992,7 +1015,7 @@ def test_parse_metadata_from_text_item_valid(mocker) -> None: assert doc.doc_title == "Example Doc" -def test_parse_metadata_from_text_item_missing_title(mocker) -> None: +def test_parse_metadata_from_text_item_missing_title(mocker: MockerFixture) -> None: """Test parsing metadata from a TextContentItem with missing title.""" mock_item = mocker.Mock(spec=TextContentItem) mock_item.text = """Metadata: {"docs_url": "https://redhat.com"}""" @@ -1000,7 +1023,7 @@ def test_parse_metadata_from_text_item_missing_title(mocker) -> None: assert doc is None -def test_parse_metadata_from_text_item_missing_url(mocker) -> None: +def test_parse_metadata_from_text_item_missing_url(mocker: MockerFixture) -> None: """Test parsing metadata from a TextContentItem with missing url.""" mock_item = mocker.Mock(spec=TextContentItem) mock_item.text = """Metadata: {"title": "Example Doc"}""" @@ -1008,7 +1031,7 @@ def test_parse_metadata_from_text_item_missing_url(mocker) -> None: assert doc is None -def test_parse_metadata_from_text_item_malformed_url(mocker) -> None: +def test_parse_metadata_from_text_item_malformed_url(mocker: MockerFixture) -> None: """Test parsing metadata from a TextContentItem with malformed url.""" mock_item = mocker.Mock(spec=TextContentItem) mock_item.text = ( @@ -1018,7 +1041,7 @@ def test_parse_metadata_from_text_item_malformed_url(mocker) -> None: assert doc is None -def test_parse_referenced_documents_single_doc(mocker) -> None: +def test_parse_referenced_documents_single_doc(mocker: MockerFixture) -> None: """Test parsing metadata from a Turn containing a single doc.""" text_item = mocker.Mock(spec=TextContentItem) text_item.text = ( @@ -1042,7 +1065,7 @@ def test_parse_referenced_documents_single_doc(mocker) -> None: assert docs[0].doc_title == "Example Doc" -def test_parse_referenced_documents_multiple_docs(mocker) -> None: +def test_parse_referenced_documents_multiple_docs(mocker: MockerFixture) -> None: """Test parsing metadata from a Turn containing multiple docs.""" text_item = mocker.Mock(spec=TextContentItem) text_item.text = SAMPLE_KNOWLEDGE_SEARCH_RESULTS @@ -1071,7 +1094,7 @@ def test_parse_referenced_documents_multiple_docs(mocker) -> None: assert docs[1].doc_title == "Doc2" -def test_parse_referenced_documents_ignores_other_tools(mocker) -> None: +def test_parse_referenced_documents_ignores_other_tools(mocker: MockerFixture) -> None: """Test parsing metadata from a Turn with the wrong tool name.""" text_item = mocker.Mock(spec=TextContentItem) text_item.text = ( @@ -1094,7 +1117,9 @@ def test_parse_referenced_documents_ignores_other_tools(mocker) -> None: @pytest.mark.asyncio -async def test_retrieve_response_with_mcp_servers(prepare_agent_mocks, mocker) -> None: +async def test_retrieve_response_with_mcp_servers( + prepare_agent_mocks: AgentFixtures, mocker: MockerFixture +) -> None: """Test the retrieve_response function with MCP servers configured.""" mock_client, mock_agent = prepare_agent_mocks mock_agent.create_turn.return_value.output_message.content = "LLM answer" @@ -1174,7 +1199,7 @@ async def test_retrieve_response_with_mcp_servers(prepare_agent_mocks, mocker) - @pytest.mark.asyncio async def test_retrieve_response_with_mcp_servers_empty_token( - prepare_agent_mocks, mocker + prepare_agent_mocks: AgentFixtures, mocker: MockerFixture ) -> None: """Test the retrieve_response function with MCP servers and empty access token.""" mock_client, mock_agent = prepare_agent_mocks @@ -1233,7 +1258,7 @@ async def test_retrieve_response_with_mcp_servers_empty_token( @pytest.mark.asyncio async def test_retrieve_response_with_mcp_servers_and_mcp_headers( - prepare_agent_mocks, mocker + prepare_agent_mocks: AgentFixtures, mocker: MockerFixture ) -> None: """Test the retrieve_response function with MCP servers configured.""" mock_client, mock_agent = prepare_agent_mocks @@ -1332,7 +1357,9 @@ async def test_retrieve_response_with_mcp_servers_and_mcp_headers( @pytest.mark.asyncio -async def test_retrieve_response_shield_violation(prepare_agent_mocks, mocker) -> None: +async def test_retrieve_response_shield_violation( + prepare_agent_mocks: AgentFixtures, mocker: MockerFixture +) -> None: """Test the retrieve_response function.""" mock_metric = mocker.patch("metrics.llm_calls_validation_errors_total") mock_client, mock_agent = prepare_agent_mocks @@ -1401,7 +1428,7 @@ def test_get_rag_toolgroups() -> None: @pytest.mark.asyncio async def test_query_endpoint_handler_on_connection_error( - mocker, dummy_request + mocker: MockerFixture, dummy_request: Request ) -> None: """Test the query endpoint handler.""" mock_metric = mocker.patch("metrics.llm_calls_failures_total") @@ -1429,7 +1456,7 @@ async def test_query_endpoint_handler_on_connection_error( @pytest.mark.asyncio async def test_auth_tuple_unpacking_in_query_endpoint_handler( - mocker, dummy_request + mocker: MockerFixture, dummy_request: Request ) -> None: """Test that auth tuple is correctly unpacked in query endpoint handler.""" # Mock dependencies @@ -1490,7 +1517,9 @@ async def test_auth_tuple_unpacking_in_query_endpoint_handler( @pytest.mark.asyncio -async def test_query_endpoint_handler_no_tools_true(mocker, dummy_request) -> None: +async def test_query_endpoint_handler_no_tools_true( + mocker: MockerFixture, dummy_request: Request +) -> None: """Test the query endpoint handler with no_tools=True.""" mock_client = mocker.AsyncMock() mock_lsc = mocker.patch("client.AsyncLlamaStackClientHolder.get_client") @@ -1547,7 +1576,9 @@ async def test_query_endpoint_handler_no_tools_true(mocker, dummy_request) -> No @pytest.mark.asyncio -async def test_query_endpoint_handler_no_tools_false(mocker, dummy_request) -> None: +async def test_query_endpoint_handler_no_tools_false( + mocker: MockerFixture, dummy_request: Request +) -> None: """Test the query endpoint handler with no_tools=False (default behavior).""" mock_client = mocker.AsyncMock() mock_lsc = mocker.patch("client.AsyncLlamaStackClientHolder.get_client") @@ -1605,7 +1636,7 @@ async def test_query_endpoint_handler_no_tools_false(mocker, dummy_request) -> N @pytest.mark.asyncio async def test_retrieve_response_no_tools_bypasses_mcp_and_rag( - prepare_agent_mocks, mocker + prepare_agent_mocks: AgentFixtures, mocker: MockerFixture ) -> None: """Test that retrieve_response bypasses MCP servers and RAG when no_tools=True.""" mock_client, mock_agent = prepare_agent_mocks @@ -1660,7 +1691,7 @@ async def test_retrieve_response_no_tools_bypasses_mcp_and_rag( @pytest.mark.asyncio async def test_retrieve_response_no_tools_false_preserves_functionality( - prepare_agent_mocks, mocker + prepare_agent_mocks: AgentFixtures, mocker: MockerFixture ) -> None: """Test that retrieve_response preserves normal functionality when no_tools=False.""" mock_client, mock_agent = prepare_agent_mocks @@ -1800,9 +1831,9 @@ def test_no_tools_parameter_backward_compatibility() -> None: ], ) def test_evaluate_model_hints( - user_conversation, - request_values, - expected_values, + user_conversation: list, + request_values: list, + expected_values: list, ) -> None: """Test evaluate_model_hints function with various scenarios.""" # Unpack fixtures @@ -1823,7 +1854,7 @@ def test_evaluate_model_hints( @pytest.mark.asyncio async def test_query_endpoint_rejects_model_provider_override_without_permission( - mocker, dummy_request + mocker: MockerFixture, dummy_request: Request ) -> None: """Assert 403 and message when request includes model/provider without MODEL_OVERRIDE.""" # Patch endpoint configuration (no need to set customization) @@ -1873,11 +1904,14 @@ async def test_query_endpoint_rejects_model_provider_override_without_permission "fields from your request." ) assert exc_info.value.status_code == status.HTTP_403_FORBIDDEN - assert exc_info.value.detail["response"] == expected_msg + + detail = exc_info.value.detail + assert isinstance(detail, dict) + assert detail["response"] == expected_msg @pytest.mark.asyncio -async def test_get_topic_summary_successful_response(mocker) -> None: +async def test_get_topic_summary_successful_response(mocker: MockerFixture) -> None: """Test get_topic_summary with successful response from agent.""" # Mock the dependencies mock_client = mocker.AsyncMock() @@ -1933,7 +1967,7 @@ async def test_get_topic_summary_successful_response(mocker) -> None: @pytest.mark.asyncio -async def test_get_topic_summary_empty_response(mocker) -> None: +async def test_get_topic_summary_empty_response(mocker: MockerFixture) -> None: """Test get_topic_summary with empty response from agent.""" # Mock the dependencies mock_client = mocker.AsyncMock() @@ -1970,7 +2004,7 @@ async def test_get_topic_summary_empty_response(mocker) -> None: @pytest.mark.asyncio -async def test_get_topic_summary_none_content(mocker) -> None: +async def test_get_topic_summary_none_content(mocker: MockerFixture) -> None: """Test get_topic_summary with None content in response.""" # Mock the dependencies mock_client = mocker.AsyncMock() @@ -2007,7 +2041,9 @@ async def test_get_topic_summary_none_content(mocker) -> None: @pytest.mark.asyncio -async def test_get_topic_summary_with_interleaved_content(mocker) -> None: +async def test_get_topic_summary_with_interleaved_content( + mocker: MockerFixture, +) -> None: """Test get_topic_summary with interleaved content response.""" # Mock the dependencies mock_client = mocker.AsyncMock() @@ -2053,7 +2089,7 @@ async def test_get_topic_summary_with_interleaved_content(mocker) -> None: @pytest.mark.asyncio -async def test_get_topic_summary_system_prompt_retrieval(mocker) -> None: +async def test_get_topic_summary_system_prompt_retrieval(mocker: MockerFixture) -> None: """Test that get_topic_summary properly retrieves and uses the system prompt.""" # Mock the dependencies mock_client = mocker.AsyncMock() @@ -2099,7 +2135,7 @@ async def test_get_topic_summary_system_prompt_retrieval(mocker) -> None: @pytest.mark.asyncio async def test_query_endpoint_handler_conversation_not_found( - mocker, dummy_request + mocker: MockerFixture, dummy_request: Request ) -> None: """Test that a 404 is raised for a non-existant conversation_id.""" mock_config = mocker.Mock() @@ -2120,11 +2156,16 @@ async def test_query_endpoint_handler_conversation_not_found( ) assert exc_info.value.status_code == status.HTTP_404_NOT_FOUND - assert "Conversation not found" in exc_info.value.detail["response"] + + detail = exc_info.value.detail + assert isinstance(detail, dict) + assert "Conversation not found" in detail["response"] @pytest.mark.asyncio -async def test_get_topic_summary_agent_creation_parameters(mocker) -> None: +async def test_get_topic_summary_agent_creation_parameters( + mocker: MockerFixture, +) -> None: """Test that get_topic_summary creates agent with correct parameters.""" # Mock the dependencies mock_client = mocker.AsyncMock() @@ -2171,7 +2212,7 @@ async def test_get_topic_summary_agent_creation_parameters(mocker) -> None: @pytest.mark.asyncio -async def test_get_topic_summary_create_turn_parameters(mocker) -> None: +async def test_get_topic_summary_create_turn_parameters(mocker: MockerFixture) -> None: """Test that get_topic_summary calls create_turn with correct parameters.""" # Mock the dependencies mock_client = mocker.AsyncMock()