Skip to content

Commit 7550c05

Browse files
Merge pull request #2931 from VWS-Python/adamtheturtle/vumark-api-test
Add initial VuMark generation API test
2 parents c56ef17 + 270f9d9 commit 7550c05

14 files changed

Lines changed: 339 additions & 6 deletions

File tree

.github/workflows/test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ jobs:
113113
- tests/mock_vws/test_update_target.py::TestInactiveProject
114114
- tests/mock_vws/test_requests_mock_usage.py
115115
- tests/mock_vws/test_flask_app_usage.py
116+
- tests/mock_vws/test_vumark_generation_api.py
116117
- tests/mock_vws/test_docker.py
117118
- README.rst
118119
- docs/source/basic-example.rst

docs/source/contributing.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,14 @@ Then, add a database from the `Vuforia Target Manager`_.
8484

8585
To find the environment variables to set in the :file:`vuforia_secrets.env` file, visit the Target Database in the `Vuforia Target Manager`_ and view the "Database Access Keys".
8686

87-
Two databases are necessary in order to run all the tests.
87+
Two Cloud databases are necessary in order to run all the Cloud Target tests.
8888
One of those must be an inactive project.
8989
To create an inactive project, delete the license key associated with a database.
9090

91+
VuMark tests require one VuMark database.
92+
When creating multiple credentials files, the same inactive database and the
93+
same VuMark database can be reused across all files.
94+
9195
Targets sometimes get stuck at the "Processing" stage meaning that they cannot be deleted.
9296
When this happens, create a new target database to use for testing.
9397

@@ -101,6 +105,8 @@ To create databases without using the browser, use :file:`admin/create_secrets_f
101105
$ export EXISTING_SECRETS_FILE=/existing/file/with/inactive/db/creds
102106
# You may have to run this a few times, but it is idempotent.
103107
$ python admin/create_secrets_files.py
108+
# Each generated file gets its own Cloud database credentials and shares
109+
# one VuMark database credential set.
104110
# After creating the secrets, update the encrypted archive:
105111
$ tar cvf secrets.tar "${NEW_SECRETS_DIR}"
106112
$ gpg \

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ ignore_names = [
448448
exclude = [ ".venv" ]
449449
ignore_decorators = [
450450
"@pytest.fixture",
451+
"@route",
451452
# Flask
452453
"@*APP.route",
453454
"@*APP.after_request",

src/mock_vws/_constants.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44

55
from beartype import beartype
66

7+
VUMARK_PNG = (
8+
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00"
9+
b"\x01\x08\x04\x00\x00\x00\xb5\x1c\x0c\x02\x00\x00\x00\x0bIDATx\xdac"
10+
b"\xfc\xff\x1f\x00\x03\x03\x02\x00\xee\xd9\x97\xa9\x00\x00\x00\x00IEND"
11+
b"\xaeB`\x82"
12+
)
13+
714

815
@beartype
916
@unique

src/mock_vws/_flask_server/vws.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from flask import Flask, Response, request
1919
from pydantic_settings import BaseSettings
2020

21-
from mock_vws._constants import ResultCodes, TargetStatuses
21+
from mock_vws._constants import VUMARK_PNG, ResultCodes, TargetStatuses
2222
from mock_vws._database_matchers import get_database_matching_server_keys
2323
from mock_vws._mock_common import json_dump
2424
from mock_vws._services_validators import run_services_validators
@@ -338,6 +338,37 @@ def delete_target(target_id: str) -> Response:
338338
)
339339

340340

341+
@VWS_FLASK_APP.route(
342+
rule="/targets/<string:target_id>/instances",
343+
methods=[HTTPMethod.POST],
344+
)
345+
@beartype
346+
def generate_vumark_instance(target_id: str) -> Response:
347+
"""Generate a VuMark instance.
348+
349+
Fake implementation of
350+
https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#generate-instance
351+
"""
352+
# ``target_id`` is validated by request validators.
353+
del target_id
354+
date = email.utils.formatdate(timeval=None, localtime=False, usegmt=True)
355+
headers = {
356+
"Connection": "keep-alive",
357+
"Content-Type": "image/png",
358+
"server": "envoy",
359+
"Date": date,
360+
"x-envoy-upstream-service-time": "5",
361+
"strict-transport-security": "max-age=31536000",
362+
"x-aws-region": "us-east-2, us-west-2",
363+
"x-content-type-options": "nosniff",
364+
}
365+
return Response(
366+
status=HTTPStatus.OK,
367+
response=VUMARK_PNG,
368+
headers=headers,
369+
)
370+
371+
341372
@VWS_FLASK_APP.route(rule="/summary", methods=[HTTPMethod.GET])
342373
@beartype
343374
def database_summary() -> Response:

src/mock_vws/_requests_mock_server/decorators.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from .mock_web_query_api import MockVuforiaWebQueryAPI
2727
from .mock_web_services_api import MockVuforiaWebServicesAPI
2828

29-
_ResponseType = tuple[int, Mapping[str, str], str]
29+
_ResponseType = tuple[int, Mapping[str, str], str | bytes]
3030
_Callback = Callable[[PreparedRequest], _ResponseType]
3131

3232
_STRUCTURAL_SIMILARITY_MATCHER = StructuralSimilarityMatcher()

src/mock_vws/_requests_mock_server/mock_web_services_api.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from beartype import BeartypeConf, beartype
1919
from requests.models import PreparedRequest
2020

21-
from mock_vws._constants import ResultCodes, TargetStatuses
21+
from mock_vws._constants import VUMARK_PNG, ResultCodes, TargetStatuses
2222
from mock_vws._database_matchers import get_database_matching_server_keys
2323
from mock_vws._mock_common import Route, json_dump
2424
from mock_vws._services_validators import run_services_validators
@@ -38,7 +38,7 @@
3838

3939
_ROUTES: set[Route] = set()
4040

41-
_ResponseType = tuple[int, Mapping[str, str], str]
41+
_ResponseType = tuple[int, Mapping[str, str], str | bytes]
4242
_P = ParamSpec("_P")
4343

4444

@@ -287,6 +287,39 @@ def delete_target(self, request: PreparedRequest) -> _ResponseType:
287287
}
288288
return HTTPStatus.OK, headers, body_json
289289

290+
@route(
291+
path_pattern=f"/targets/{_TARGET_ID_PATTERN}/instances",
292+
http_methods={HTTPMethod.POST},
293+
)
294+
def generate_vumark_instance(
295+
self, request: PreparedRequest
296+
) -> _ResponseType:
297+
"""Generate a VuMark instance."""
298+
run_services_validators(
299+
request_headers=request.headers,
300+
request_body=_body_bytes(request=request),
301+
request_method=request.method or "",
302+
request_path=request.path_url,
303+
databases=self._target_manager.databases,
304+
)
305+
306+
date = email.utils.formatdate(
307+
timeval=None,
308+
localtime=False,
309+
usegmt=True,
310+
)
311+
headers = {
312+
"Connection": "keep-alive",
313+
"Content-Type": "image/png",
314+
"Date": date,
315+
"server": "envoy",
316+
"x-envoy-upstream-service-time": "5",
317+
"strict-transport-security": "max-age=31536000",
318+
"x-aws-region": "us-east-2, us-west-2",
319+
"x-content-type-options": "nosniff",
320+
}
321+
return HTTPStatus.OK, headers, VUMARK_PNG
322+
290323
@route(path_pattern="/summary", http_methods={HTTPMethod.GET})
291324
def database_summary(self, request: PreparedRequest) -> _ResponseType:
292325
"""Get a database summary report.

src/mock_vws/_services_validators/key_validators.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@ def validate_keys(
114114
},
115115
)
116116

117+
generate_instance = _Route(
118+
path_pattern=f"/targets/{target_id_pattern}/instances",
119+
http_methods={HTTPMethod.POST},
120+
mandatory_keys={"instance_id"},
121+
optional_keys=set(),
122+
)
123+
117124
target_summary = _Route(
118125
path_pattern=f"/summary/{target_id_pattern}",
119126
http_methods={HTTPMethod.GET},
@@ -129,6 +136,7 @@ def validate_keys(
129136
get_target,
130137
get_duplicates,
131138
update_target,
139+
generate_instance,
132140
target_summary,
133141
)
134142

src/mock_vws/_services_validators/target_validators.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from mock_vws.database import VuforiaDatabase
1111

1212
_LOGGER = logging.getLogger(name=__name__)
13+
_TARGETS_WITH_INSTANCE_PATH_LENGTH = 4
1314

1415

1516
@beartype
@@ -42,6 +43,12 @@ def validate_target_id_exists(
4243
return
4344

4445
target_id = split_path[-1]
46+
if (
47+
len(split_path) == _TARGETS_WITH_INSTANCE_PATH_LENGTH
48+
and split_path[-3] == "targets"
49+
and split_path[-1] == "instances"
50+
):
51+
target_id = split_path[-2]
4552
database = get_database_matching_server_keys(
4653
request_headers=request_headers,
4754
request_body=request_body,

tests/mock_vws/fixtures/credentials.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Fixtures for credentials for Vuforia databases."""
22

3+
from dataclasses import dataclass, field
34
from pathlib import Path
45

56
import pytest
@@ -35,6 +36,31 @@ class _InactiveVuforiaDatabaseSettings(_VuforiaDatabaseSettings):
3536
)
3637

3738

39+
class _VuMarkVuforiaDatabaseSettings(BaseSettings):
40+
"""Settings for a VuMark Vuforia database."""
41+
42+
target_manager_database_name: str
43+
server_access_key: str
44+
server_secret_key: str
45+
target_id: str = "MockVuMarkTargetID00"
46+
47+
model_config = SettingsConfigDict(
48+
env_prefix="VUMARK_VUFORIA_",
49+
env_file=Path("vuforia_secrets.env"),
50+
extra="allow",
51+
)
52+
53+
54+
@dataclass(frozen=True)
55+
class VuMarkVuforiaDatabase:
56+
"""Credentials for the VuMark generation API."""
57+
58+
target_manager_database_name: str = field(repr=False)
59+
server_access_key: str = field(repr=False)
60+
server_secret_key: str = field(repr=False)
61+
target_id: str = field(repr=False)
62+
63+
3864
@pytest.fixture
3965
def vuforia_database() -> VuforiaDatabase:
4066
"""Return VWS credentials from environment variables."""
@@ -64,3 +90,16 @@ def inactive_database() -> VuforiaDatabase:
6490
client_secret_key=settings.client_secret_key,
6591
state=States.PROJECT_INACTIVE,
6692
)
93+
94+
95+
@pytest.fixture
96+
def vumark_vuforia_database() -> VuMarkVuforiaDatabase:
97+
"""Return VuMark VWS credentials from environment variables."""
98+
settings = _VuMarkVuforiaDatabaseSettings.model_validate(obj={})
99+
100+
return VuMarkVuforiaDatabase(
101+
target_manager_database_name=settings.target_manager_database_name,
102+
server_access_key=settings.server_access_key,
103+
server_secret_key=settings.server_secret_key,
104+
target_id=settings.target_id,
105+
)

0 commit comments

Comments
 (0)