File size: 5,262 Bytes
6172a47 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | """Tests for providers/nvidia_nim/errors.py error mapping."""
from unittest.mock import MagicMock, patch
import openai
import pytest
from httpx import ReadTimeout, Request, Response
from providers.common import append_request_id, get_user_facing_error_message, map_error
from providers.exceptions import (
APIError,
AuthenticationError,
InvalidRequestError,
OverloadedError,
RateLimitError,
)
def _make_openai_error(cls, message="test error", status_code=None):
"""Helper to create openai exceptions with required httpx objects."""
response = Response(
status_code=status_code or 500, request=Request("POST", "http://test")
)
body = {"error": {"message": message}}
# openai.APIError base class has a different constructor signature
if cls is openai.APIError:
return cls(message, request=Request("POST", "http://test"), body=body)
return cls(message, response=response, body=body)
class TestMapError:
"""Tests for map_error function."""
def test_authentication_error(self):
"""openai.AuthenticationError -> AuthenticationError."""
exc = _make_openai_error(openai.AuthenticationError, status_code=401)
result = map_error(exc)
assert isinstance(result, AuthenticationError)
assert result.status_code == 401
def test_rate_limit_error(self):
"""openai.RateLimitError -> RateLimitError and triggers global block."""
exc = _make_openai_error(openai.RateLimitError, status_code=429)
with patch("providers.common.error_mapping.GlobalRateLimiter") as mock_rl:
mock_instance = MagicMock()
mock_rl.get_instance.return_value = mock_instance
result = map_error(exc)
assert isinstance(result, RateLimitError)
assert result.status_code == 429
mock_instance.set_blocked.assert_called_once_with(60)
def test_bad_request_error(self):
"""openai.BadRequestError -> InvalidRequestError."""
exc = _make_openai_error(openai.BadRequestError, status_code=400)
result = map_error(exc)
assert isinstance(result, InvalidRequestError)
assert result.status_code == 400
@pytest.mark.parametrize(
"message",
["Server overloaded", "No capacity available"],
ids=["overloaded", "capacity"],
)
def test_internal_server_error_overloaded(self, message):
"""InternalServerError with overloaded/capacity keywords -> OverloadedError."""
exc = _make_openai_error(
openai.InternalServerError, message=message, status_code=500
)
result = map_error(exc)
assert isinstance(result, OverloadedError)
assert result.status_code == 529
def test_internal_server_error_generic(self):
"""InternalServerError without keywords -> APIError(500)."""
exc = _make_openai_error(
openai.InternalServerError, message="Unknown error", status_code=500
)
result = map_error(exc)
assert isinstance(result, APIError)
assert result.status_code == 500
def test_generic_api_error(self):
"""openai.APIError -> APIError with original status_code."""
exc = _make_openai_error(
openai.APIError, message="Bad gateway", status_code=502
)
result = map_error(exc)
assert isinstance(result, APIError)
def test_unmapped_exception_passthrough(self):
"""Non-openai exceptions are returned as-is."""
exc = RuntimeError("unexpected")
result = map_error(exc)
assert result is exc
assert isinstance(result, RuntimeError)
def test_value_error_passthrough(self):
"""ValueError passes through unchanged."""
exc = ValueError("bad value")
result = map_error(exc)
assert result is exc
@pytest.mark.parametrize(
"exc_cls,expected_cls",
[
(openai.AuthenticationError, AuthenticationError),
(openai.RateLimitError, RateLimitError),
(openai.BadRequestError, InvalidRequestError),
],
ids=["auth", "rate_limit", "bad_request"],
)
def test_mapping_parametrized(self, exc_cls, expected_cls):
"""Parametrized check of openai -> provider error mapping."""
status_map = {
openai.AuthenticationError: 401,
openai.RateLimitError: 429,
openai.BadRequestError: 400,
}
exc = _make_openai_error(exc_cls, status_code=status_map[exc_cls])
with patch("providers.common.error_mapping.GlobalRateLimiter"):
result = map_error(exc)
assert isinstance(result, expected_cls)
def test_user_facing_message_read_timeout_empty_string():
"""ReadTimeout wrapping TimeoutError should still produce readable text."""
timeout_exc = ReadTimeout("")
message = get_user_facing_error_message(timeout_exc, read_timeout_s=60)
assert message == "Provider request timed out after 60s."
def test_append_request_id_suffix():
"""Request id suffix should be appended deterministically."""
message = append_request_id("Provider request failed.", "req_abc123")
assert message == "Provider request failed. (request_id=req_abc123)"
|