Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 29 additions & 8 deletions tests/unit/app/endpoints/test_config.py
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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]
)
Expand Down
57 changes: 41 additions & 16 deletions tests/unit/app/endpoints/test_feedback.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -87,19 +91,22 @@ 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
assert result.response == "feedback received"


@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)

Expand All @@ -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(
Expand All @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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")
Expand Down
29 changes: 19 additions & 10 deletions tests/unit/app/endpoints/test_health.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)

Expand All @@ -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)

Expand All @@ -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)

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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"
Expand All @@ -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"
Expand All @@ -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")
Expand Down Expand Up @@ -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")
Expand Down
Loading
Loading