Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions newsfragments/3169.change
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Test synchronous and asynchronous Model Target error responses through the
public mock API, and include rate-limit and server-error branches in coverage.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ optional-dependencies.dev = [
"types-requests==2.33.0.20260712",
"vale==3.18.0.0",
"vulture==2.16",
"vws-python-mock==2026.8.14",
"vws-python-mock==2026.8.26.1",
"vws-test-fixtures==2026.8.23",
"yamlfix==1.19.1",
"zizmor==1.29.0",
Expand Down
8 changes: 2 additions & 6 deletions src/vws/_model_targets.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,15 +269,11 @@ def raise_for_error(*, response: Response) -> None:
~vws.exceptions.vws_exceptions.TooManyRequestsError: Vuforia is
rate limiting access.
"""
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.BAD_REQUEST:
Expand Down
120 changes: 108 additions & 12 deletions tests/test_async_model_targets.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
"""Tests for the async Model Target Web API client."""

import io
import json
import uuid
import zipfile
from http import HTTPStatus

import pytest
from mock_vws import (
MockVWS,
ModelTargetFailureResponse,
ModelTargetGenerationFailure,
ModelTargetGenerationWarning,
)

from vws import AsyncModelTargetService
from vws.exceptions.custom_exceptions import ServerError
from vws.exceptions.model_target_exceptions import (
ModelTargetAuthenticationError,
ModelTargetDatasetNotDoneError,
ModelTargetDatasetTimeoutError,
ModelTargetError,
ModelTargetOAuth2Error,
ModelTargetValidationError,
UnknownModelTargetDatasetError,
)
from vws.exceptions.vws_exceptions import TooManyRequestsError
from vws.model_target_datasets import (
CadDataFormat,
ModelTargetDatasetType,
Expand All @@ -40,6 +44,39 @@
]


async def _assert_dataset_error_response(
*,
model_target_model: ModelTargetModel,
status_code: HTTPStatus,
body: str,
expected_exception: (
type[ModelTargetError | TooManyRequestsError | ServerError]
),
) -> None:
"""Assert that a mocked dataset failure maps to an exception."""
async with AsyncModelTargetService(
client_id=_CLIENT_ID,
client_secret=_CLIENT_SECRET,
) as client:
with pytest.raises(
expected_exception=(
ModelTargetError,
TooManyRequestsError,
ServerError,
)
) as exc:
await client.create_dataset(
name="dataset",
target_sdk="11.0",
models=[model_target_model],
dataset_type=ModelTargetDatasetType.STANDARD,
)

assert isinstance(exc.value, expected_exception)
assert exc.value.response.status_code == status_code
assert exc.value.response.text == body


class TestAccessToken:
"""Tests for getting an access token."""

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

@staticmethod
@pytest.mark.asyncio
@pytest.mark.parametrize(
argnames=("status_code", "body", "expected_exception"),
argvalues=[
pytest.param(
HTTPStatus.UNAUTHORIZED,
'{"error":{"code":"AUTHENTICATION_ERROR","message":"No"}}',
ModelTargetAuthenticationError,
id="authentication",
),
pytest.param(
HTTPStatus.FORBIDDEN,
'{"error":{"code":"FORBIDDEN","message":"Denied"}}',
ModelTargetError,
id="generic-json",
),
pytest.param(
HTTPStatus.CONFLICT,
"not json",
ModelTargetError,
id="generic-non-json",
),
pytest.param(
HTTPStatus.TOO_MANY_REQUESTS,
"rate limited",
TooManyRequestsError,
id="rate-limit",
),
pytest.param(
HTTPStatus.BAD_GATEWAY,
"server error",
ServerError,
id="server-error",
),
],
)
async def test_dataset_error_response(
*,
model_target_model: ModelTargetModel,
status_code: HTTPStatus,
body: str,
expected_exception: (
type[ModelTargetError | TooManyRequestsError | ServerError]
),
) -> None:
"""Dataset failures map to exceptions through the mock."""
failure = ModelTargetFailureResponse(
status_code=status_code,
body=body,
)

with MockVWS(model_target_failure_response=failure):
await _assert_dataset_error_response(
model_target_model=model_target_model,
status_code=status_code,
body=body,
expected_exception=expected_exception,
)


class TestDatasetLifecycle:
"""Tests for the dataset lifecycle."""
Expand Down Expand Up @@ -113,10 +210,7 @@ async def test_create_wait_download_delete(
with zipfile.ZipFile(
file=io.BytesIO(initial_bytes=dataset)
) as archive:
dataset_json = json.loads(s=archive.read(name="dataset.json"))

assert dataset_json["uuid"] == dataset_uuid
assert dataset_json["type"] == dataset_type.value
assert archive.namelist() == ["MTDataset.dat", "MTDataset.xml"]

await async_model_target_client.delete_dataset(
dataset_uuid=dataset_uuid,
Expand Down Expand Up @@ -183,24 +277,25 @@ async def test_download_while_processing(

@staticmethod
@pytest.mark.asyncio
async def test_dataset_types_are_separate(
async def test_dataset_is_visible_to_other_type(
*,
async_model_target_client: AsyncModelTargetService,
model_target_model: ModelTargetModel,
) -> None:
"""A dataset is not visible to requests for the other type."""
"""Standard and advanced routes share datasets by UUID."""
dataset_uuid = await async_model_target_client.create_dataset(
name="dataset",
target_sdk="11.0",
models=[model_target_model],
dataset_type=ModelTargetDatasetType.ADVANCED,
)

with pytest.raises(expected_exception=UnknownModelTargetDatasetError):
await async_model_target_client.get_dataset_status(
dataset_uuid=dataset_uuid,
dataset_type=ModelTargetDatasetType.STANDARD,
)
report = await async_model_target_client.get_dataset_status(
dataset_uuid=dataset_uuid,
dataset_type=ModelTargetDatasetType.STANDARD,
)

assert report.dataset_uuid == dataset_uuid

@staticmethod
@pytest.mark.asyncio
Expand All @@ -215,6 +310,7 @@ async def test_advanced_dataset_takes_multiple_models(
cad_data_blob="ZmFrZS1jYWQtZGF0YQ==",
cad_data_format=CadDataFormat.GLB,
realistic_appearance=RealisticAppearance.TRUE,
views=[],
)

assert await async_model_target_client.create_dataset(
Expand Down
Loading
Loading