From bc299e614ceefb59ba3fd9bb368ff7929003cec1 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Fri, 11 Sep 2026 00:02:37 +0100 Subject: [PATCH] Cover service HTTP status handling --- src/vws/async_vumark_service.py | 8 +-- src/vws/async_vws.py | 4 +- src/vws/vumark_service.py | 8 +-- src/vws/vws.py | 4 +- tests/test_transports.py | 114 ++++++++++++++++++++++++++++++++ 5 files changed, 120 insertions(+), 18 deletions(-) diff --git a/src/vws/async_vumark_service.py b/src/vws/async_vumark_service.py index 466622f51..b478cb096 100644 --- a/src/vws/async_vumark_service.py +++ b/src/vws/async_vumark_service.py @@ -133,16 +133,12 @@ async def generate_vumark_instance( transport=self._transport, ) - if ( - response.status_code == HTTPStatus.TOO_MANY_REQUESTS - ): # pragma: no cover + if response.status_code == HTTPStatus.TOO_MANY_REQUESTS: # The Vuforia API returns a 429 response with no # JSON body. raise TooManyRequestsError(response=response) - if ( - response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR - ): # pragma: no cover + if response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR: raise ServerError(response=response) if response.status_code == HTTPStatus.OK: diff --git a/src/vws/async_vws.py b/src/vws/async_vws.py index 4dcbe5321..89a3cd3db 100644 --- a/src/vws/async_vws.py +++ b/src/vws/async_vws.py @@ -142,9 +142,7 @@ async def make_request( transport=self._transport, ) - if ( - response.status_code == HTTPStatus.TOO_MANY_REQUESTS - ): # pragma: no cover + if response.status_code == HTTPStatus.TOO_MANY_REQUESTS: # The Vuforia API returns a 429 response with no JSON body. raise TooManyRequestsError(response=response) diff --git a/src/vws/vumark_service.py b/src/vws/vumark_service.py index 8d61dee18..6c50aeee8 100644 --- a/src/vws/vumark_service.py +++ b/src/vws/vumark_service.py @@ -117,15 +117,11 @@ def generate_vumark_instance( transport=self._transport, ) - if ( - response.status_code == HTTPStatus.TOO_MANY_REQUESTS - ): # pragma: no cover + if response.status_code == HTTPStatus.TOO_MANY_REQUESTS: # The Vuforia API returns a 429 response with no JSON body. raise TooManyRequestsError(response=response) - if ( - response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR - ): # pragma: no cover + if response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR: raise ServerError(response=response) if response.status_code == HTTPStatus.OK: diff --git a/src/vws/vws.py b/src/vws/vws.py index d8c4b0e5a..b74b078ba 100644 --- a/src/vws/vws.py +++ b/src/vws/vws.py @@ -128,9 +128,7 @@ def make_request( transport=self._transport, ) - if ( - response.status_code == HTTPStatus.TOO_MANY_REQUESTS - ): # pragma: no cover + if response.status_code == HTTPStatus.TOO_MANY_REQUESTS: # The Vuforia API returns a 429 response with no JSON body. raise TooManyRequestsError(response=response) diff --git a/tests/test_transports.py b/tests/test_transports.py index e2ff7b59d..82b9db76d 100644 --- a/tests/test_transports.py +++ b/tests/test_transports.py @@ -24,6 +24,8 @@ ModelTargetService, VuMarkService, ) +from vws.exceptions.custom_exceptions import ServerError +from vws.exceptions.vws_exceptions import TooManyRequestsError from vws.model_target_datasets import ( ModelTargetDatasetType, ModelTargetModel, @@ -430,6 +432,118 @@ async def test_falsy_async_transport_is_retained( ) +@pytest.mark.parametrize( + argnames=("status_code", "exception_type"), + argvalues=[ + (HTTPStatus.TOO_MANY_REQUESTS, TooManyRequestsError), + (HTTPStatus.INTERNAL_SERVER_ERROR, ServerError), + ], +) +@respx.mock +def test_vumark_service_handles_bodyless_http_errors( + *, + status_code: HTTPStatus, + exception_type: type[ServerError | TooManyRequestsError], +) -> None: + """VuMark HTTP errors which have no JSON body remain meaningful.""" + route = respx.post( + url="https://example.com/targets/target/instances" + ).mock(return_value=httpx.Response(status_code=status_code)) + + with HTTPXTransport() as transport: + service = VuMarkService( + server_access_key="access-key", + server_secret_key=uuid.uuid4().hex, + base_vws_url="https://example.com", + transport=transport, + ) + with pytest.raises(expected_exception=exception_type): + _ = service.generate_vumark_instance( + target_id="target", + instance_id="instance", + accept=VuMarkAccept.PNG, + ) + + assert route.called + + +@respx.mock +def test_vws_handles_bodyless_rate_limit_response() -> None: + """A body-less target-manager rate limit raises its specific error.""" + route = respx.get(url="https://example.com/targets").mock( + return_value=httpx.Response(status_code=HTTPStatus.TOO_MANY_REQUESTS) + ) + + with HTTPXTransport() as transport: + service = VWS( + server_access_key="access-key", + server_secret_key=uuid.uuid4().hex, + base_vws_url="https://example.com", + transport=transport, + ) + with pytest.raises(expected_exception=TooManyRequestsError): + _ = service.list_targets() + + assert route.called + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + argnames=("status_code", "exception_type"), + argvalues=[ + (HTTPStatus.TOO_MANY_REQUESTS, TooManyRequestsError), + (HTTPStatus.INTERNAL_SERVER_ERROR, ServerError), + ], +) +@respx.mock +async def test_async_vumark_service_handles_bodyless_http_errors( + *, + status_code: HTTPStatus, + exception_type: type[ServerError | TooManyRequestsError], +) -> None: + """Async VuMark HTTP errors without JSON remain meaningful.""" + route = respx.post( + url="https://example.com/targets/target/instances" + ).mock(return_value=httpx.Response(status_code=status_code)) + + async with AsyncHTTPXTransport() as transport: + service = AsyncVuMarkService( + server_access_key="access-key", + server_secret_key=uuid.uuid4().hex, + base_vws_url="https://example.com", + transport=transport, + ) + with pytest.raises(expected_exception=exception_type): + _ = await service.generate_vumark_instance( + target_id="target", + instance_id="instance", + accept=VuMarkAccept.PNG, + ) + + assert route.called + + +@pytest.mark.asyncio +@respx.mock +async def test_async_vws_handles_bodyless_rate_limit_response() -> None: + """An async target-manager rate limit raises its specific error.""" + route = respx.get(url="https://example.com/targets").mock( + return_value=httpx.Response(status_code=HTTPStatus.TOO_MANY_REQUESTS) + ) + + async with AsyncHTTPXTransport() as transport: + service = AsyncVWS( + server_access_key="access-key", + server_secret_key=uuid.uuid4().hex, + base_vws_url="https://example.com", + transport=transport, + ) + with pytest.raises(expected_exception=TooManyRequestsError): + _ = await service.list_targets() + + assert route.called + + # The mock accepts one hard-coded pair of Model Target Web API OAuth2 # credentials, which it does not expose. _MODEL_TARGET_CLIENT_ID = "client-id"