Skip to content

Commit c2f7249

Browse files
Merge pull request #3463 from VWS-Python/agent/mock-training-allowance-exhaustion
Mock exhausted Model Target training allowance
2 parents 7ca1c36 + 53555ab commit c2f7249

9 files changed

Lines changed: 151 additions & 3 deletions

File tree

.github/workflows/test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ jobs:
120120
- tests/mock_vws/test_flask_app_usage.py
121121
- tests/mock_vws/test_model_target_generation_failure.py
122122
- tests/mock_vws/test_model_target_generation_warning.py
123+
- tests/mock_vws/test_model_target_training_allowance.py
123124
- tests/mock_vws/test_model_target_web_api.py
124125
- tests/mock_vws/test_vumark_generation_api.py
125126
- tests/mock_vws/test_vumark_generation_failure.py

docs/source/differences-to-vws.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,12 @@ in-process Model Target datasets finish with a ``failed`` status and an
238238
:paramref:`~mock_vws.MockVWS.processing_time_seconds`, so callers can test
239239
both processing and failed states. This configuration is not supported by the
240240
Flask/Docker backend.
241+
Use
242+
:paramref:`mock_vws.MockVWS.model_target_training_allowance_exceeded` to make
243+
in-process Model Target dataset creation return Vuforia's
244+
``TRAINING_ALLOWANCE_EXCEEDED`` response. Set the
245+
:envvar:`MODEL_TARGET_TRAINING_ALLOWANCE_EXCEEDED` environment variable to
246+
``true`` to configure the same response in the Flask/Docker backend.
241247
Use :paramref:`mock_vws.MockVWS.model_target_generation_warning` to make
242248
successful in-process Model Target datasets include a Vuforia-shaped
243249
``warning`` object after processing completes. This configuration is not

docs/source/docker.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,13 @@ VWS container
158158

159159
Default: ``2.0``
160160

161+
.. envvar:: MODEL_TARGET_TRAINING_ALLOWANCE_EXCEEDED
162+
163+
Whether Model Target dataset creation returns Vuforia's
164+
``TRAINING_ALLOWANCE_EXCEEDED`` response.
165+
166+
Default: ``false``
167+
161168
.. envvar:: VWS_BASE_URL
162169

163170
The base URL which clients use to reach the VWS container.

newsfragments/3462.change

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Add configurable Model Target ``TRAINING_ALLOWANCE_EXCEEDED`` responses to the in-process and Flask/Docker mocks.

src/mock_vws/_flask_server/vws.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ class VWSSettings(BaseSettings):
129129
_ImageMatcherChoice.STRUCTURAL_SIMILARITY
130130
)
131131
response_delay_seconds: float = 0.0
132+
model_target_training_allowance_exceeded: bool = False
132133

133134

134135
@beartype
@@ -504,6 +505,9 @@ def create_standard_model_target_dataset() -> Response:
504505
dataset_type=ModelTargetDatasetType.STANDARD,
505506
generation_failure=None,
506507
generation_warning=None,
508+
training_allowance_exceeded=(
509+
settings.model_target_training_allowance_exceeded
510+
),
507511
),
508512
)
509513

@@ -524,6 +528,9 @@ def create_advanced_model_target_dataset() -> Response:
524528
dataset_type=ModelTargetDatasetType.ADVANCED,
525529
generation_failure=None,
526530
generation_warning=None,
531+
training_allowance_exceeded=(
532+
settings.model_target_training_allowance_exceeded
533+
),
527534
),
528535
)
529536

src/mock_vws/_model_target_web_api.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,6 +1364,7 @@ def create_model_target_dataset(
13641364
dataset_type: ModelTargetDatasetType,
13651365
generation_failure: ModelTargetGenerationFailure | None,
13661366
generation_warning: ModelTargetGenerationWarning | None,
1367+
training_allowance_exceeded: bool,
13671368
) -> _ResponseType:
13681369
"""Create a standard or advanced Model Target dataset."""
13691370
content_length_error = _content_length_error(request=request)
@@ -1395,12 +1396,20 @@ def create_model_target_dataset(
13951396
if state_scope_error is not None:
13961397
return state_scope_error
13971398

1398-
validation_error = _validate_dataset_request(
1399+
creation_error = _validate_dataset_request(
13991400
request_json=request_json_or_error,
14001401
dataset_type=dataset_type,
14011402
)
1402-
if validation_error is not None:
1403-
return validation_error
1403+
if creation_error is None and training_allowance_exceeded:
1404+
creation_error = _error_response(
1405+
status_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1406+
code="TRAINING_ALLOWANCE_EXCEEDED",
1407+
message="User has reached total number of allowed trainings",
1408+
target=_MOCK_USER_TARGET,
1409+
details=None,
1410+
)
1411+
if creation_error is not None:
1412+
return creation_error
14041413

14051414
dataset = ModelTargetDataset(
14061415
request_body=request_json_or_error,

src/mock_vws/_requests_mock_server/mock_web_services_api.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ def __init__(
145145
processing_time_seconds: float,
146146
model_target_generation_failure: (ModelTargetGenerationFailure | None),
147147
model_target_generation_warning: (ModelTargetGenerationWarning | None),
148+
model_target_training_allowance_exceeded: bool,
148149
duplicate_match_checker: ImageMatcher,
149150
target_tracking_rater: TargetTrackingRater,
150151
vumark_generation_failure: VuMarkGenerationFailure | None,
@@ -162,6 +163,9 @@ def __init__(
162163
after Model Target dataset processing completes.
163164
model_target_generation_warning: A configured warning returned
164165
after Model Target dataset processing completes.
166+
model_target_training_allowance_exceeded: Whether Model Target
167+
dataset creation is rejected because the account has no
168+
training allowance remaining.
165169
duplicate_match_checker: A callable which takes two image
166170
values
167171
and returns whether they are duplicates.
@@ -179,6 +183,9 @@ def __init__(
179183
self._processing_time_seconds = processing_time_seconds
180184
self._model_target_generation_failure = model_target_generation_failure
181185
self._model_target_generation_warning = model_target_generation_warning
186+
self._model_target_training_allowance_exceeded = (
187+
model_target_training_allowance_exceeded
188+
)
182189
self._duplicate_match_checker = duplicate_match_checker
183190
self._target_tracking_rater = target_tracking_rater
184191
self._vumark_generation_failure = vumark_generation_failure
@@ -270,6 +277,9 @@ def create_standard_model_target_dataset(
270277
dataset_type=ModelTargetDatasetType.STANDARD,
271278
generation_failure=self._model_target_generation_failure,
272279
generation_warning=self._model_target_generation_warning,
280+
training_allowance_exceeded=(
281+
self._model_target_training_allowance_exceeded
282+
),
273283
)
274284

275285
@route(
@@ -288,6 +298,9 @@ def create_advanced_model_target_dataset(
288298
dataset_type=ModelTargetDatasetType.ADVANCED,
289299
generation_failure=self._model_target_generation_failure,
290300
generation_warning=self._model_target_generation_warning,
301+
training_allowance_exceeded=(
302+
self._model_target_training_allowance_exceeded
303+
),
291304
)
292305

293306
@route(

src/mock_vws/decorators.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ def __init__(
7070
model_target_generation_warning: (
7171
ModelTargetGenerationWarning | None
7272
) = None,
73+
model_target_training_allowance_exceeded: bool = False,
7374
target_tracking_rater: TargetTrackingRater = _BRISQUE_TRACKING_RATER,
7475
real_http: bool = False,
7576
response_delay_seconds: float = 0.0,
@@ -96,6 +97,10 @@ def __init__(
9697
Model Target dataset finishes processing. By default, Model
9798
Target datasets finish without warnings. This cannot be
9899
combined with ``model_target_generation_failure``.
100+
model_target_training_allowance_exceeded: Whether Model Target
101+
dataset creation returns Vuforia's
102+
``TRAINING_ALLOWANCE_EXCEEDED`` response. By default, creation
103+
is allowed.
99104
base_vwq_url: The base URL for the VWQ API.
100105
base_vws_url: The base URL for the VWS API.
101106
cloud_query_failure_response: A response to return for every Cloud
@@ -153,6 +158,9 @@ def __init__(
153158
processing_time_seconds=float(processing_time_seconds),
154159
model_target_generation_failure=model_target_generation_failure,
155160
model_target_generation_warning=model_target_generation_warning,
161+
model_target_training_allowance_exceeded=(
162+
model_target_training_allowance_exceeded
163+
),
156164
duplicate_match_checker=duplicate_match_checker,
157165
target_tracking_rater=target_tracking_rater,
158166
vumark_generation_failure=vumark_generation_failure,
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
"""Tests for exhausted Model Target training allowance responses."""
2+
3+
from http import HTTPStatus
4+
from typing import Any
5+
6+
import pytest
7+
import requests
8+
9+
from mock_vws import MockVWS
10+
from mock_vws._flask_server.vws import VWS_FLASK_APP
11+
12+
_AUTHORIZATION = (
13+
"Bearer eyJhbGciOiJtb2NrIn0."
14+
"eyJzY29wZSI6Im1vZGVsdGFyZ2V0cy5hbGwifQ."
15+
"c2lnbmF0dXJl"
16+
)
17+
_REQUEST_BODY: dict[str, Any] = {
18+
"name": "dataset-name",
19+
"targetSdk": "10.18",
20+
"models": [
21+
{
22+
"name": "model-name",
23+
"cadDataUrl": "https://example.com/model.glb",
24+
"views": [
25+
{
26+
"name": "view-name",
27+
"guideViewPosition": {
28+
"translation": [0, 0, 5],
29+
"rotation": [0, 0, 0, 1],
30+
},
31+
},
32+
],
33+
},
34+
],
35+
}
36+
_EXPECTED_BODY = {
37+
"error": {
38+
"code": "TRAINING_ALLOWANCE_EXCEEDED",
39+
"message": "User has reached total number of allowed trainings",
40+
"target": "userId:mock",
41+
},
42+
}
43+
44+
45+
@pytest.mark.parametrize(
46+
argnames="dataset_path",
47+
argvalues=[
48+
pytest.param("modeltargets/datasets", id="standard"),
49+
pytest.param("modeltargets/advancedDatasets", id="advanced"),
50+
],
51+
)
52+
def test_requests_mock_training_allowance_exceeded(dataset_path: str) -> None:
53+
"""The in-process mock can reject creation when allowance is spent."""
54+
with MockVWS(model_target_training_allowance_exceeded=True):
55+
response = requests.post(
56+
url=f"https://vws.vuforia.com/{dataset_path}",
57+
headers={"Authorization": _AUTHORIZATION},
58+
json=_REQUEST_BODY,
59+
timeout=30,
60+
)
61+
62+
assert response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY
63+
assert response.json() == _EXPECTED_BODY
64+
65+
66+
@pytest.mark.parametrize(
67+
argnames="dataset_path",
68+
argvalues=[
69+
pytest.param("/modeltargets/datasets", id="standard"),
70+
pytest.param("/modeltargets/advancedDatasets", id="advanced"),
71+
],
72+
)
73+
def test_flask_training_allowance_exceeded(
74+
*,
75+
dataset_path: str,
76+
monkeypatch: pytest.MonkeyPatch,
77+
) -> None:
78+
"""The Flask mock supports the exhausted allowance configuration."""
79+
monkeypatch.setenv(
80+
name="MODEL_TARGET_TRAINING_ALLOWANCE_EXCEEDED",
81+
value="true",
82+
)
83+
monkeypatch.setenv(
84+
name="TARGET_MANAGER_BASE_URL",
85+
value="http://target-manager.example.com",
86+
)
87+
88+
with VWS_FLASK_APP.test_client() as client:
89+
response = client.post(
90+
path=dataset_path,
91+
headers={"Authorization": _AUTHORIZATION},
92+
json=_REQUEST_BODY,
93+
)
94+
95+
assert response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY
96+
assert response.json == _EXPECTED_BODY

0 commit comments

Comments
 (0)