Spaces:
Sleeping
Sleeping
| """Unit tests for app/api_client.py — the HTTP client the Streamlit UI uses to call FastAPI.""" | |
| from __future__ import annotations | |
| from unittest.mock import Mock, patch | |
| import pytest | |
| import requests | |
| from app.api_client import ApiError, ApiUnavailableError, recommend | |
| def _response(status_code: int, json_body: object = None, text: str = "") -> Mock: | |
| resp = Mock() | |
| resp.status_code = status_code | |
| resp.text = text | |
| if json_body is None: | |
| resp.json.side_effect = ValueError("no JSON body") | |
| else: | |
| resp.json.return_value = json_body | |
| return resp | |
| class TestRecommendSuccess: | |
| def test_returns_parsed_json_on_200(self): | |
| with patch("app.api_client.requests.post", return_value=_response(200, {"frontier": []})): | |
| result = recommend(model_name="llama2-70b") | |
| assert result == {"frontier": []} | |
| def test_posts_payload_as_json_to_recommend_route(self): | |
| mock_post = Mock(return_value=_response(200, {})) | |
| with patch("app.api_client.requests.post", mock_post): | |
| recommend(model_name="llama2-70b", ranking_objective="tokens_per_watt") | |
| _, kwargs = mock_post.call_args | |
| assert mock_post.call_args[0][0].endswith("/recommend") | |
| assert kwargs["json"] == {"model_name": "llama2-70b", "ranking_objective": "tokens_per_watt"} | |
| class TestRecommendErrorResponse: | |
| def test_422_raises_api_error_with_detail(self): | |
| with patch("app.api_client.requests.post", return_value=_response(422, {"detail": "bad input"})): | |
| with pytest.raises(ApiError) as exc_info: | |
| recommend(model_name="not-a-model") | |
| assert exc_info.value.status_code == 422 | |
| assert exc_info.value.detail == "bad input" | |
| def test_429_plain_text_body_falls_back_to_response_text(self): | |
| # The rate-limiter's 429 response (src/api/main.py) is plain text, not JSON. | |
| with patch( | |
| "app.api_client.requests.post", | |
| return_value=_response(429, json_body=None, text="Rate limit exceeded"), | |
| ): | |
| with pytest.raises(ApiError) as exc_info: | |
| recommend(model_name="llama2-70b") | |
| assert exc_info.value.status_code == 429 | |
| assert exc_info.value.detail == "Rate limit exceeded" | |
| class TestRecommendUnavailable: | |
| def test_connection_error_retries_then_raises_api_unavailable(self): | |
| mock_post = Mock(side_effect=requests.exceptions.ConnectionError("refused")) | |
| with patch("app.api_client.requests.post", mock_post), patch("app.api_client.time.sleep"): | |
| with pytest.raises(ApiUnavailableError): | |
| recommend(model_name="llama2-70b") | |
| assert mock_post.call_count == 10 # _CONNECT_RETRIES | |
| def test_recovers_if_api_becomes_reachable_before_retries_exhausted(self): | |
| mock_post = Mock( | |
| side_effect=[ | |
| requests.exceptions.ConnectionError("refused"), | |
| requests.exceptions.ConnectionError("refused"), | |
| _response(200, {"frontier": []}), | |
| ] | |
| ) | |
| with patch("app.api_client.requests.post", mock_post), patch("app.api_client.time.sleep"): | |
| result = recommend(model_name="llama2-70b") | |
| assert result == {"frontier": []} | |
| assert mock_post.call_count == 3 | |