Skip to content

Commit 70aab3e

Browse files
committed
Test Model Target errors through the mock
1 parent 431e383 commit 70aab3e

5 files changed

Lines changed: 180 additions & 97 deletions

File tree

newsfragments/3169.change

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Test synchronous and asynchronous Model Target error responses through the
2+
public mock API, and include rate-limit and server-error branches in coverage.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ optional-dependencies.dev = [
8686
"types-requests==2.33.0.20260712",
8787
"vale==3.18.0.0",
8888
"vulture==2.16",
89-
"vws-python-mock==2026.8.14",
89+
"vws-python-mock==2026.8.26.1",
9090
"vws-test-fixtures==2026.8.23",
9191
"yamlfix==1.19.1",
9292
"zizmor==1.29.0",

src/vws/_model_targets.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -269,15 +269,11 @@ def raise_for_error(*, response: Response) -> None:
269269
~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is
270270
rate limiting access.
271271
"""
272-
if (
273-
response.status_code == HTTPStatus.TOO_MANY_REQUESTS
274-
): # pragma: no cover
272+
if response.status_code == HTTPStatus.TOO_MANY_REQUESTS:
275273
# The Vuforia API returns a 429 response with no JSON body.
276274
raise TooManyRequestsError(response=response)
277275

278-
if (
279-
response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR
280-
): # pragma: no cover
276+
if response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
281277
raise ServerError(response=response)
282278

283279
if response.status_code < HTTPStatus.BAD_REQUEST:

tests/test_async_model_targets.py

Lines changed: 108 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,30 @@
11
"""Tests for the async Model Target Web API client."""
22

33
import io
4-
import json
54
import uuid
65
import zipfile
76
from http import HTTPStatus
87

98
import pytest
109
from mock_vws import (
1110
MockVWS,
11+
ModelTargetFailureResponse,
1212
ModelTargetGenerationFailure,
1313
ModelTargetGenerationWarning,
1414
)
1515

1616
from vws import AsyncModelTargetService
17+
from vws.exceptions.custom_exceptions import ServerError
1718
from vws.exceptions.model_target_exceptions import (
19+
ModelTargetAuthenticationError,
1820
ModelTargetDatasetNotDoneError,
1921
ModelTargetDatasetTimeoutError,
22+
ModelTargetError,
2023
ModelTargetOAuth2Error,
2124
ModelTargetValidationError,
2225
UnknownModelTargetDatasetError,
2326
)
27+
from vws.exceptions.vws_exceptions import TooManyRequestsError
2428
from vws.model_target_datasets import (
2529
CadDataFormat,
2630
ModelTargetDatasetType,
@@ -40,6 +44,39 @@
4044
]
4145

4246

47+
async def _assert_dataset_error_response(
48+
*,
49+
model_target_model: ModelTargetModel,
50+
status_code: HTTPStatus,
51+
body: str,
52+
expected_exception: (
53+
type[ModelTargetError | TooManyRequestsError | ServerError]
54+
),
55+
) -> None:
56+
"""Assert that a mocked dataset failure maps to an exception."""
57+
async with AsyncModelTargetService(
58+
client_id=_CLIENT_ID,
59+
client_secret=_CLIENT_SECRET,
60+
) as client:
61+
with pytest.raises(
62+
expected_exception=(
63+
ModelTargetError,
64+
TooManyRequestsError,
65+
ServerError,
66+
)
67+
) as exc:
68+
await client.create_dataset(
69+
name="dataset",
70+
target_sdk="11.0",
71+
models=[model_target_model],
72+
dataset_type=ModelTargetDatasetType.STANDARD,
73+
)
74+
75+
assert isinstance(exc.value, expected_exception)
76+
assert exc.value.response.status_code == status_code
77+
assert exc.value.response.text == body
78+
79+
4380
class TestAccessToken:
4481
"""Tests for getting an access token."""
4582

@@ -69,6 +106,66 @@ async def test_invalid_credentials() -> None:
69106
assert exc.value.response.status_code == HTTPStatus.UNAUTHORIZED
70107
assert exc.value.error == "invalid_client"
71108

109+
@staticmethod
110+
@pytest.mark.asyncio
111+
@pytest.mark.parametrize(
112+
argnames=("status_code", "body", "expected_exception"),
113+
argvalues=[
114+
pytest.param(
115+
HTTPStatus.UNAUTHORIZED,
116+
'{"error":{"code":"AUTHENTICATION_ERROR","message":"No"}}',
117+
ModelTargetAuthenticationError,
118+
id="authentication",
119+
),
120+
pytest.param(
121+
HTTPStatus.FORBIDDEN,
122+
'{"error":{"code":"FORBIDDEN","message":"Denied"}}',
123+
ModelTargetError,
124+
id="generic-json",
125+
),
126+
pytest.param(
127+
HTTPStatus.CONFLICT,
128+
"not json",
129+
ModelTargetError,
130+
id="generic-non-json",
131+
),
132+
pytest.param(
133+
HTTPStatus.TOO_MANY_REQUESTS,
134+
"rate limited",
135+
TooManyRequestsError,
136+
id="rate-limit",
137+
),
138+
pytest.param(
139+
HTTPStatus.BAD_GATEWAY,
140+
"server error",
141+
ServerError,
142+
id="server-error",
143+
),
144+
],
145+
)
146+
async def test_dataset_error_response(
147+
*,
148+
model_target_model: ModelTargetModel,
149+
status_code: HTTPStatus,
150+
body: str,
151+
expected_exception: (
152+
type[ModelTargetError | TooManyRequestsError | ServerError]
153+
),
154+
) -> None:
155+
"""Dataset failures map to exceptions through the mock."""
156+
failure = ModelTargetFailureResponse(
157+
status_code=status_code,
158+
body=body,
159+
)
160+
161+
with MockVWS(model_target_failure_response=failure):
162+
await _assert_dataset_error_response(
163+
model_target_model=model_target_model,
164+
status_code=status_code,
165+
body=body,
166+
expected_exception=expected_exception,
167+
)
168+
72169

73170
class TestDatasetLifecycle:
74171
"""Tests for the dataset lifecycle."""
@@ -113,10 +210,7 @@ async def test_create_wait_download_delete(
113210
with zipfile.ZipFile(
114211
file=io.BytesIO(initial_bytes=dataset)
115212
) as archive:
116-
dataset_json = json.loads(s=archive.read(name="dataset.json"))
117-
118-
assert dataset_json["uuid"] == dataset_uuid
119-
assert dataset_json["type"] == dataset_type.value
213+
assert archive.namelist() == ["MTDataset.dat", "MTDataset.xml"]
120214

121215
await async_model_target_client.delete_dataset(
122216
dataset_uuid=dataset_uuid,
@@ -183,24 +277,25 @@ async def test_download_while_processing(
183277

184278
@staticmethod
185279
@pytest.mark.asyncio
186-
async def test_dataset_types_are_separate(
280+
async def test_dataset_is_visible_to_other_type(
187281
*,
188282
async_model_target_client: AsyncModelTargetService,
189283
model_target_model: ModelTargetModel,
190284
) -> None:
191-
"""A dataset is not visible to requests for the other type."""
285+
"""Standard and advanced routes share datasets by UUID."""
192286
dataset_uuid = await async_model_target_client.create_dataset(
193287
name="dataset",
194288
target_sdk="11.0",
195289
models=[model_target_model],
196290
dataset_type=ModelTargetDatasetType.ADVANCED,
197291
)
198292

199-
with pytest.raises(expected_exception=UnknownModelTargetDatasetError):
200-
await async_model_target_client.get_dataset_status(
201-
dataset_uuid=dataset_uuid,
202-
dataset_type=ModelTargetDatasetType.STANDARD,
203-
)
293+
report = await async_model_target_client.get_dataset_status(
294+
dataset_uuid=dataset_uuid,
295+
dataset_type=ModelTargetDatasetType.STANDARD,
296+
)
297+
298+
assert report.dataset_uuid == dataset_uuid
204299

205300
@staticmethod
206301
@pytest.mark.asyncio
@@ -215,6 +310,7 @@ async def test_advanced_dataset_takes_multiple_models(
215310
cad_data_blob="ZmFrZS1jYWQtZGF0YQ==",
216311
cad_data_format=CadDataFormat.GLB,
217312
realistic_appearance=RealisticAppearance.TRUE,
313+
views=[],
218314
)
219315

220316
assert await async_model_target_client.create_dataset(

0 commit comments

Comments
 (0)