production-rag-backend / tests /unit /test_api_llm_client.py
UDHOV's picture
Sync from GitHub via hub-sync
41de262 verified
Raw
History Blame Contribute Delete
3.45 kB
"""Tests for api_llm_client.py — 401 Unauthorized detection.
Covers:
- InvalidAPIKeyError raised on 401 response
- Other HTTP errors raised as httpx.HTTPStatusError
- Successful responses parsed correctly
"""
from unittest.mock import MagicMock, patch
import httpx
import pytest
from src.reasoning.utils.api_llm_client import (
APIConfig,
APILLMClient,
InvalidAPIKeyError,
)
@pytest.fixture
def config() -> APIConfig:
return APIConfig(
endpoint="https://openrouter.ai/api/v1/chat/completions",
api_key="test-key", # pragma: allowlist secret
model="test-model",
timeout=10,
)
@pytest.fixture
def client(config: APIConfig) -> APILLMClient:
return APILLMClient(config)
class TestInvalidAPIKeyError:
"""401 Unauthorized → InvalidAPIKeyError (no retry)."""
def test_401_raises_invalid_api_key_error(self, client: APILLMClient) -> None:
mock_response = MagicMock(spec=httpx.Response)
mock_response.status_code = 401
mock_response.json.return_value = {"error": {"message": "Invalid API key", "code": 401}}
mock_response.request = MagicMock(spec=httpx.Request)
with patch.object(client.client, "post", return_value=mock_response):
with pytest.raises(InvalidAPIKeyError) as exc_info:
client.generate("test prompt")
assert exc_info.value.status_code == 401
assert "openrouter.ai" in str(exc_info.value)
def test_401_does_not_retry(self, client: APILLMClient) -> None:
mock_response = MagicMock(spec=httpx.Response)
mock_response.status_code = 401
mock_response.json.return_value = {"error": {"message": "Unauthorized"}}
mock_response.request = MagicMock(spec=httpx.Request)
with patch.object(client.client, "post", return_value=mock_response) as mock_post:
with pytest.raises(InvalidAPIKeyError):
client.generate("test prompt")
# Should only be called once — no retries for 401
assert mock_post.call_count == 1
class TestOtherHTTPErrors:
"""Non-401 errors should still raise httpx.HTTPStatusError."""
def test_500_raises_http_status_error(self, client: APILLMClient) -> None:
mock_response = MagicMock(spec=httpx.Response)
mock_response.status_code = 500
mock_response.json.return_value = {"error": {"message": "Internal error"}}
mock_response.request = MagicMock(spec=httpx.Request)
mock_response.raise_for_status.side_effect = httpx.HTTPStatusError(
"Server Error", request=mock_response.request, response=mock_response
)
with patch.object(client.client, "post", return_value=mock_response), pytest.raises(httpx.HTTPStatusError):
client.generate("test prompt")
class TestSuccessfulResponse:
"""Valid API key → successful response."""
def test_valid_key_returns_success(self, client: APILLMClient) -> None:
mock_response = MagicMock(spec=httpx.Response)
mock_response.status_code = 200
mock_response.json.return_value = {"choices": [{"message": {"content": "Hello!"}}]}
mock_response.raise_for_status.return_value = None
with patch.object(client.client, "post", return_value=mock_response):
result = client.generate("test prompt")
assert result["success"] is True
assert result["text"] == "Hello!"