Skip to content
Draft
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
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ jobs:
- tests/mock_vws/test_flask_app_usage.py
- tests/mock_vws/test_model_target_generation_failure.py
- tests/mock_vws/test_model_target_generation_warning.py
- tests/mock_vws/test_model_target_training_allowance.py
- tests/mock_vws/test_model_target_web_api.py
- tests/mock_vws/test_vumark_generation_api.py
- tests/mock_vws/test_vumark_generation_failure.py
Expand Down
6 changes: 6 additions & 0 deletions docs/source/differences-to-vws.rst
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,12 @@ in-process Model Target datasets finish with a ``failed`` status and an
:paramref:`~mock_vws.MockVWS.processing_time_seconds`, so callers can test
both processing and failed states. This configuration is not supported by the
Flask/Docker backend.
Use
:paramref:`mock_vws.MockVWS.model_target_training_allowance_exceeded` to make
in-process Model Target dataset creation return Vuforia's
``TRAINING_ALLOWANCE_EXCEEDED`` response. Set the
:envvar:`MODEL_TARGET_TRAINING_ALLOWANCE_EXCEEDED` environment variable to
``true`` to configure the same response in the Flask/Docker backend.
Use :paramref:`mock_vws.MockVWS.model_target_generation_warning` to make
successful in-process Model Target datasets include a Vuforia-shaped
``warning`` object after processing completes. This configuration is not
Expand Down
7 changes: 7 additions & 0 deletions docs/source/docker.rst
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,13 @@ VWS container

Default: ``2.0``

.. envvar:: MODEL_TARGET_TRAINING_ALLOWANCE_EXCEEDED

Whether Model Target dataset creation returns Vuforia's
``TRAINING_ALLOWANCE_EXCEEDED`` response.

Default: ``false``

.. envvar:: VWS_BASE_URL

The base URL which clients use to reach the VWS container.
Expand Down
1 change: 1 addition & 0 deletions newsfragments/3462.change
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Add configurable Model Target ``TRAINING_ALLOWANCE_EXCEEDED`` responses to the in-process and Flask/Docker mocks.
7 changes: 7 additions & 0 deletions src/mock_vws/_flask_server/vws.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ class VWSSettings(BaseSettings):
_ImageMatcherChoice.STRUCTURAL_SIMILARITY
)
response_delay_seconds: float = 0.0
model_target_training_allowance_exceeded: bool = False


@beartype
Expand Down Expand Up @@ -504,6 +505,9 @@ def create_standard_model_target_dataset() -> Response:
dataset_type=ModelTargetDatasetType.STANDARD,
generation_failure=None,
generation_warning=None,
training_allowance_exceeded=(
settings.model_target_training_allowance_exceeded
),
),
)

Expand All @@ -524,6 +528,9 @@ def create_advanced_model_target_dataset() -> Response:
dataset_type=ModelTargetDatasetType.ADVANCED,
generation_failure=None,
generation_warning=None,
training_allowance_exceeded=(
settings.model_target_training_allowance_exceeded
),
),
)

Expand Down
15 changes: 12 additions & 3 deletions src/mock_vws/_model_target_web_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1364,6 +1364,7 @@ def create_model_target_dataset(
dataset_type: ModelTargetDatasetType,
generation_failure: ModelTargetGenerationFailure | None,
generation_warning: ModelTargetGenerationWarning | None,
training_allowance_exceeded: bool,
) -> _ResponseType:
"""Create a standard or advanced Model Target dataset."""
content_length_error = _content_length_error(request=request)
Expand Down Expand Up @@ -1395,12 +1396,20 @@ def create_model_target_dataset(
if state_scope_error is not None:
return state_scope_error

validation_error = _validate_dataset_request(
creation_error = _validate_dataset_request(
request_json=request_json_or_error,
dataset_type=dataset_type,
)
if validation_error is not None:
return validation_error
if creation_error is None and training_allowance_exceeded:
creation_error = _error_response(
status_code=HTTPStatus.UNPROCESSABLE_ENTITY,
code="TRAINING_ALLOWANCE_EXCEEDED",
message="User has reached total number of allowed trainings",
target=_MOCK_USER_TARGET,
details=None,
)
if creation_error is not None:
return creation_error

dataset = ModelTargetDataset(
request_body=request_json_or_error,
Expand Down
13 changes: 13 additions & 0 deletions src/mock_vws/_requests_mock_server/mock_web_services_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ def __init__(
processing_time_seconds: float,
model_target_generation_failure: (ModelTargetGenerationFailure | None),
model_target_generation_warning: (ModelTargetGenerationWarning | None),
model_target_training_allowance_exceeded: bool,
duplicate_match_checker: ImageMatcher,
target_tracking_rater: TargetTrackingRater,
vumark_generation_failure: VuMarkGenerationFailure | None,
Expand All @@ -162,6 +163,9 @@ def __init__(
after Model Target dataset processing completes.
model_target_generation_warning: A configured warning returned
after Model Target dataset processing completes.
model_target_training_allowance_exceeded: Whether Model Target
dataset creation is rejected because the account has no
training allowance remaining.
duplicate_match_checker: A callable which takes two image
values
and returns whether they are duplicates.
Expand All @@ -179,6 +183,9 @@ def __init__(
self._processing_time_seconds = processing_time_seconds
self._model_target_generation_failure = model_target_generation_failure
self._model_target_generation_warning = model_target_generation_warning
self._model_target_training_allowance_exceeded = (
model_target_training_allowance_exceeded
)
self._duplicate_match_checker = duplicate_match_checker
self._target_tracking_rater = target_tracking_rater
self._vumark_generation_failure = vumark_generation_failure
Expand Down Expand Up @@ -270,6 +277,9 @@ def create_standard_model_target_dataset(
dataset_type=ModelTargetDatasetType.STANDARD,
generation_failure=self._model_target_generation_failure,
generation_warning=self._model_target_generation_warning,
training_allowance_exceeded=(
self._model_target_training_allowance_exceeded
),
)

@route(
Expand All @@ -288,6 +298,9 @@ def create_advanced_model_target_dataset(
dataset_type=ModelTargetDatasetType.ADVANCED,
generation_failure=self._model_target_generation_failure,
generation_warning=self._model_target_generation_warning,
training_allowance_exceeded=(
self._model_target_training_allowance_exceeded
),
)

@route(
Expand Down
8 changes: 8 additions & 0 deletions src/mock_vws/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def __init__(
model_target_generation_warning: (
ModelTargetGenerationWarning | None
) = None,
model_target_training_allowance_exceeded: bool = False,
target_tracking_rater: TargetTrackingRater = _BRISQUE_TRACKING_RATER,
real_http: bool = False,
response_delay_seconds: float = 0.0,
Expand All @@ -96,6 +97,10 @@ def __init__(
Model Target dataset finishes processing. By default, Model
Target datasets finish without warnings. This cannot be
combined with ``model_target_generation_failure``.
model_target_training_allowance_exceeded: Whether Model Target
dataset creation returns Vuforia's
``TRAINING_ALLOWANCE_EXCEEDED`` response. By default, creation
is allowed.
base_vwq_url: The base URL for the VWQ API.
base_vws_url: The base URL for the VWS API.
cloud_query_failure_response: A response to return for every Cloud
Expand Down Expand Up @@ -153,6 +158,9 @@ def __init__(
processing_time_seconds=float(processing_time_seconds),
model_target_generation_failure=model_target_generation_failure,
model_target_generation_warning=model_target_generation_warning,
model_target_training_allowance_exceeded=(
model_target_training_allowance_exceeded
),
duplicate_match_checker=duplicate_match_checker,
target_tracking_rater=target_tracking_rater,
vumark_generation_failure=vumark_generation_failure,
Expand Down
96 changes: 96 additions & 0 deletions tests/mock_vws/test_model_target_training_allowance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""Tests for exhausted Model Target training allowance responses."""

from http import HTTPStatus
from typing import Any

import pytest
import requests

from mock_vws import MockVWS
from mock_vws._flask_server.vws import VWS_FLASK_APP

_AUTHORIZATION = (
"Bearer eyJhbGciOiJtb2NrIn0."
"eyJzY29wZSI6Im1vZGVsdGFyZ2V0cy5hbGwifQ."
"c2lnbmF0dXJl"
)
_REQUEST_BODY: dict[str, Any] = {
"name": "dataset-name",
"targetSdk": "10.18",
"models": [
{
"name": "model-name",
"cadDataUrl": "https://example.com/model.glb",
"views": [
{
"name": "view-name",
"guideViewPosition": {
"translation": [0, 0, 5],
"rotation": [0, 0, 0, 1],
},
},
],
},
],
}
_EXPECTED_BODY = {
"error": {
"code": "TRAINING_ALLOWANCE_EXCEEDED",
"message": "User has reached total number of allowed trainings",
"target": "userId:mock",
},
}


@pytest.mark.parametrize(
argnames="dataset_path",
argvalues=[
pytest.param("modeltargets/datasets", id="standard"),
pytest.param("modeltargets/advancedDatasets", id="advanced"),
],
)
def test_requests_mock_training_allowance_exceeded(dataset_path: str) -> None:
"""The in-process mock can reject creation when allowance is spent."""
with MockVWS(model_target_training_allowance_exceeded=True):
response = requests.post(
url=f"https://vws.vuforia.com/{dataset_path}",
headers={"Authorization": _AUTHORIZATION},
json=_REQUEST_BODY,
timeout=30,
)

assert response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY
assert response.json() == _EXPECTED_BODY


@pytest.mark.parametrize(
argnames="dataset_path",
argvalues=[
pytest.param("/modeltargets/datasets", id="standard"),
pytest.param("/modeltargets/advancedDatasets", id="advanced"),
],
)
def test_flask_training_allowance_exceeded(
*,
dataset_path: str,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""The Flask mock supports the exhausted allowance configuration."""
monkeypatch.setenv(
name="MODEL_TARGET_TRAINING_ALLOWANCE_EXCEEDED",
value="true",
)
monkeypatch.setenv(
name="TARGET_MANAGER_BASE_URL",
value="http://target-manager.example.com",
)

with VWS_FLASK_APP.test_client() as client:
response = client.post(
path=dataset_path,
headers={"Authorization": _AUTHORIZATION},
json=_REQUEST_BODY,
)

assert response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY
assert response.json == _EXPECTED_BODY
Loading