From d2b383ad622073398eb530ba5fe72ac10f8738e0 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 16 Feb 2026 22:35:26 +0000 Subject: [PATCH 01/21] Add minimal VuMark API interaction test --- tests/mock_vws/fixtures/credentials.py | 30 ++++++++++++ tests/mock_vws/test_vumark_generation_api.py | 48 ++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 tests/mock_vws/test_vumark_generation_api.py diff --git a/tests/mock_vws/fixtures/credentials.py b/tests/mock_vws/fixtures/credentials.py index 90fe125b4..f31c9a904 100644 --- a/tests/mock_vws/fixtures/credentials.py +++ b/tests/mock_vws/fixtures/credentials.py @@ -3,6 +3,7 @@ from pathlib import Path import pytest +from pydantic import ValidationError from pydantic_settings import BaseSettings, SettingsConfigDict from mock_vws.database import VuforiaDatabase @@ -35,6 +36,20 @@ class _InactiveVuforiaDatabaseSettings(_VuforiaDatabaseSettings): ) +class _VuMarkVuforiaDatabaseSettings(BaseSettings): + """Settings for a VuMark Vuforia database.""" + + target_manager_database_name: str + server_access_key: str + server_secret_key: str + + model_config = SettingsConfigDict( + env_prefix="VUMARK_VUFORIA_", + env_file=Path("vuforia_secrets.env"), + extra="allow", + ) + + @pytest.fixture def vuforia_database() -> VuforiaDatabase: """Return VWS credentials from environment variables.""" @@ -64,3 +79,18 @@ def inactive_database() -> VuforiaDatabase: client_secret_key=settings.client_secret_key, state=States.PROJECT_INACTIVE, ) + + +@pytest.fixture +def vumark_vuforia_database() -> VuforiaDatabase: + """Return VuMark VWS credentials from environment variables.""" + try: + settings = _VuMarkVuforiaDatabaseSettings.model_validate(obj={}) + except ValidationError: + pytest.skip(reason="VuMark credentials are not configured.") + + return VuforiaDatabase( + database_name=settings.target_manager_database_name, + server_access_key=settings.server_access_key, + server_secret_key=settings.server_secret_key, + ) diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py new file mode 100644 index 000000000..1196563b1 --- /dev/null +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -0,0 +1,48 @@ +"""Tests for the VuMark generation web API.""" + +import json +import uuid +from http import HTTPMethod, HTTPStatus + +import requests +from vws_auth_tools import authorization_header, rfc_1123_date + +from mock_vws.database import VuforiaDatabase + +_VWS_HOST = "https://vws.vuforia.com" + + +def test_generate_instance_for_missing_target( + vumark_vuforia_database: VuforiaDatabase, +) -> None: + """The VuMark generation API can be called with signed credentials.""" + request_path = f"/targets/{uuid.uuid4().hex}/instances" + content_type = "application/json" + content = json.dumps(obj={"instance_id": "1"}).encode(encoding="utf-8") + date = rfc_1123_date() + authorization_string = authorization_header( + access_key=vumark_vuforia_database.server_access_key, + secret_key=vumark_vuforia_database.server_secret_key, + method=HTTPMethod.POST, + content=content, + content_type=content_type, + date=date, + request_path=request_path, + ) + + response = requests.post( + url=_VWS_HOST + request_path, + headers={ + "Authorization": authorization_string, + "Content-Length": str(object=len(content)), + "Content-Type": content_type, + "Date": date, + }, + data=content, + timeout=30, + ) + + assert response.status_code == HTTPStatus.NOT_FOUND + response_json = json.loads(s=response.text) + assert isinstance(response_json, dict) + assert response_json["result_code"] in {"NoSuchTarget", "UnknownTarget"} From a1335853c8f843910d73acf8aa3924be224f334d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 16 Feb 2026 22:46:05 +0000 Subject: [PATCH 02/21] Share VuMark credentials across secrets files --- admin/create_secrets_files.py | 20 +++++++++++--------- docs/source/contributing.rst | 8 +++++++- tests/mock_vws/fixtures/credentials.py | 6 +----- vuforia_secrets.env.example | 7 ++++--- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 5e6cc43c5..103ca0e53 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -134,6 +134,7 @@ def main() -> None: ] files_to_create = [file for file in required_files if not file.exists()] driver: WebDriver | None = None + shared_vumark_details: VuMarkDatabaseDict | None = None while files_to_create: if driver is None: @@ -159,21 +160,22 @@ def main() -> None: driver = None continue - vumark_details = _create_and_get_vumark_details( - driver=driver, - vumark_database_name=vumark_database_name, - ) - if vumark_details is None: - driver.quit() - driver = None - continue + if shared_vumark_details is None: + shared_vumark_details = _create_and_get_vumark_details( + driver=driver, + vumark_database_name=vumark_database_name, + ) + if shared_vumark_details is None: + driver.quit() + driver = None + continue driver.quit() driver = None file_contents = _generate_secrets_file_content( database_details=database_details, - vumark_details=vumark_details, + vumark_details=shared_vumark_details, ) file.write_text(data=file_contents) sys.stdout.write(f"Created database {file.name}\n") diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index b93e5f20b..09e1f4ca9 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -84,10 +84,14 @@ Then, add a database from the `Vuforia Target Manager`_. 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". -Two databases are necessary in order to run all the tests. +Two Cloud databases are necessary in order to run all the Cloud Target tests. One of those must be an inactive project. To create an inactive project, delete the license key associated with a database. +VuMark tests require one VuMark database. +When creating multiple credentials files, the same inactive database and the +same VuMark database can be reused across all files. + Targets sometimes get stuck at the "Processing" stage meaning that they cannot be deleted. When this happens, create a new target database to use for testing. @@ -101,6 +105,8 @@ To create databases without using the browser, use :file:`admin/create_secrets_f $ export EXISTING_SECRETS_FILE=/existing/file/with/inactive/db/creds # You may have to run this a few times, but it is idempotent. $ python admin/create_secrets_files.py + # Each generated file gets its own Cloud database credentials and shares + # one VuMark database credential set. # After creating the secrets, update the encrypted archive: $ tar cvf secrets.tar "${NEW_SECRETS_DIR}" $ gpg \ diff --git a/tests/mock_vws/fixtures/credentials.py b/tests/mock_vws/fixtures/credentials.py index f31c9a904..e64e20940 100644 --- a/tests/mock_vws/fixtures/credentials.py +++ b/tests/mock_vws/fixtures/credentials.py @@ -3,7 +3,6 @@ from pathlib import Path import pytest -from pydantic import ValidationError from pydantic_settings import BaseSettings, SettingsConfigDict from mock_vws.database import VuforiaDatabase @@ -84,10 +83,7 @@ def inactive_database() -> VuforiaDatabase: @pytest.fixture def vumark_vuforia_database() -> VuforiaDatabase: """Return VuMark VWS credentials from environment variables.""" - try: - settings = _VuMarkVuforiaDatabaseSettings.model_validate(obj={}) - except ValidationError: - pytest.skip(reason="VuMark credentials are not configured.") + settings = _VuMarkVuforiaDatabaseSettings.model_validate(obj={}) return VuforiaDatabase( database_name=settings.target_manager_database_name, diff --git a/vuforia_secrets.env.example b/vuforia_secrets.env.example index 5e843a117..3bd0e64ac 100644 --- a/vuforia_secrets.env.example +++ b/vuforia_secrets.env.example @@ -14,7 +14,8 @@ INACTIVE_VUFORIA_SERVER_SECRET_KEY= INACTIVE_VUFORIA_CLIENT_ACCESS_KEY= INACTIVE_VUFORIA_CLIENT_SECRET_KEY= -VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME= +# Shared across all generated secrets files. +VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME= -VUMARK_VUFORIA_SERVER_ACCESS_KEY= -VUMARK_VUFORIA_SERVER_SECRET_KEY= +VUMARK_VUFORIA_SERVER_ACCESS_KEY= +VUMARK_VUFORIA_SERVER_SECRET_KEY= From 00a5951dbbb82829b9909ce17fefdfc88e12189a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 16 Feb 2026 22:54:00 +0000 Subject: [PATCH 03/21] Set VuMark test Accept header --- tests/mock_vws/test_vumark_generation_api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index 1196563b1..acd7a420c 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -33,6 +33,7 @@ def test_generate_instance_for_missing_target( response = requests.post( url=_VWS_HOST + request_path, headers={ + "Accept": "image/png", "Authorization": authorization_string, "Content-Length": str(object=len(content)), "Content-Type": content_type, From f8c083c52d98a10228ec0f99791257aae6850008 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 16 Feb 2026 22:59:44 +0000 Subject: [PATCH 04/21] Make VuMark test assert successful generation --- tests/mock_vws/test_vumark_generation_api.py | 65 +++++++++++++++++--- 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index acd7a420c..e18e08f62 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -1,24 +1,71 @@ """Tests for the VuMark generation web API.""" import json -import uuid from http import HTTPMethod, HTTPStatus +from pathlib import Path +import pytest import requests +from pydantic import ValidationError +from pydantic_settings import BaseSettings, SettingsConfigDict from vws_auth_tools import authorization_header, rfc_1123_date from mock_vws.database import VuforiaDatabase _VWS_HOST = "https://vws.vuforia.com" +_PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n" -def test_generate_instance_for_missing_target( +class _VuMarkGenerationSettings(BaseSettings): + """Settings needed for VuMark instance generation tests.""" + + target_id: str + instance_id: str + + model_config = SettingsConfigDict( + env_prefix="VUMARK_VUFORIA_", + env_file=Path("vuforia_secrets.env"), + extra="allow", + ) + + +def _get_vumark_generation_settings() -> _VuMarkGenerationSettings: + """Return generation settings, skipping if they are not configured.""" + try: + settings = _VuMarkGenerationSettings.model_validate(obj={}) + except ValidationError: + pytest.skip( + reason=( + "VuMark generation settings are not configured. " + "Set VUMARK_VUFORIA_TARGET_ID and " + "VUMARK_VUFORIA_INSTANCE_ID." + ), + ) + + if settings.target_id.startswith("<") or settings.instance_id.startswith( + "<" + ): + pytest.skip( + reason=( + "VuMark generation settings are placeholders. " + "Set VUMARK_VUFORIA_TARGET_ID and " + "VUMARK_VUFORIA_INSTANCE_ID." + ), + ) + + return settings + + +def test_generate_instance_success( vumark_vuforia_database: VuforiaDatabase, ) -> None: - """The VuMark generation API can be called with signed credentials.""" - request_path = f"/targets/{uuid.uuid4().hex}/instances" + """A VuMark instance can be generated with valid template settings.""" + settings = _get_vumark_generation_settings() + request_path = f"/targets/{settings.target_id}/instances" content_type = "application/json" - content = json.dumps(obj={"instance_id": "1"}).encode(encoding="utf-8") + content = json.dumps(obj={"instance_id": settings.instance_id}).encode( + encoding="utf-8" + ) date = rfc_1123_date() authorization_string = authorization_header( access_key=vumark_vuforia_database.server_access_key, @@ -43,7 +90,7 @@ def test_generate_instance_for_missing_target( timeout=30, ) - assert response.status_code == HTTPStatus.NOT_FOUND - response_json = json.loads(s=response.text) - assert isinstance(response_json, dict) - assert response_json["result_code"] in {"NoSuchTarget", "UnknownTarget"} + assert response.status_code == HTTPStatus.OK + assert response.headers["Content-Type"].split(sep=";")[0] == "image/png" + assert response.content.startswith(_PNG_SIGNATURE) + assert len(response.content) > len(_PNG_SIGNATURE) From 9efcf5ea58ea01d4aba6c62715e54b6c26382817 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 16 Feb 2026 23:21:31 +0000 Subject: [PATCH 05/21] Use VuMark fixture fields in generation test --- tests/mock_vws/fixtures/credentials.py | 22 ++++++-- tests/mock_vws/test_vumark_generation_api.py | 55 ++++---------------- vuforia_secrets.env.example | 2 + 3 files changed, 32 insertions(+), 47 deletions(-) diff --git a/tests/mock_vws/fixtures/credentials.py b/tests/mock_vws/fixtures/credentials.py index e64e20940..142b6f5d6 100644 --- a/tests/mock_vws/fixtures/credentials.py +++ b/tests/mock_vws/fixtures/credentials.py @@ -1,5 +1,6 @@ """Fixtures for credentials for Vuforia databases.""" +from dataclasses import dataclass from pathlib import Path import pytest @@ -41,6 +42,8 @@ class _VuMarkVuforiaDatabaseSettings(BaseSettings): target_manager_database_name: str server_access_key: str server_secret_key: str + target_id: str = "" + instance_id: str = "" model_config = SettingsConfigDict( env_prefix="VUMARK_VUFORIA_", @@ -49,6 +52,17 @@ class _VuMarkVuforiaDatabaseSettings(BaseSettings): ) +@dataclass(frozen=True) +class VuMarkVuforiaDatabase: + """Credentials for the VuMark generation API.""" + + target_manager_database_name: str + server_access_key: str + server_secret_key: str + target_id: str + instance_id: str + + @pytest.fixture def vuforia_database() -> VuforiaDatabase: """Return VWS credentials from environment variables.""" @@ -81,12 +95,14 @@ def inactive_database() -> VuforiaDatabase: @pytest.fixture -def vumark_vuforia_database() -> VuforiaDatabase: +def vumark_vuforia_database() -> VuMarkVuforiaDatabase: """Return VuMark VWS credentials from environment variables.""" settings = _VuMarkVuforiaDatabaseSettings.model_validate(obj={}) - return VuforiaDatabase( - database_name=settings.target_manager_database_name, + return VuMarkVuforiaDatabase( + target_manager_database_name=settings.target_manager_database_name, server_access_key=settings.server_access_key, server_secret_key=settings.server_secret_key, + target_id=settings.target_id, + instance_id=settings.instance_id, ) diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index e18e08f62..bb8d7d5a5 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -2,49 +2,24 @@ import json from http import HTTPMethod, HTTPStatus -from pathlib import Path import pytest import requests -from pydantic import ValidationError -from pydantic_settings import BaseSettings, SettingsConfigDict from vws_auth_tools import authorization_header, rfc_1123_date -from mock_vws.database import VuforiaDatabase +from tests.mock_vws.fixtures.credentials import VuMarkVuforiaDatabase _VWS_HOST = "https://vws.vuforia.com" _PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n" -class _VuMarkGenerationSettings(BaseSettings): - """Settings needed for VuMark instance generation tests.""" - - target_id: str - instance_id: str - - model_config = SettingsConfigDict( - env_prefix="VUMARK_VUFORIA_", - env_file=Path("vuforia_secrets.env"), - extra="allow", - ) - - -def _get_vumark_generation_settings() -> _VuMarkGenerationSettings: - """Return generation settings, skipping if they are not configured.""" - try: - settings = _VuMarkGenerationSettings.model_validate(obj={}) - except ValidationError: - pytest.skip( - reason=( - "VuMark generation settings are not configured. " - "Set VUMARK_VUFORIA_TARGET_ID and " - "VUMARK_VUFORIA_INSTANCE_ID." - ), - ) - - if settings.target_id.startswith("<") or settings.instance_id.startswith( +def test_generate_instance_success( + vumark_vuforia_database: VuMarkVuforiaDatabase, +) -> None: + """A VuMark instance can be generated with valid template settings.""" + if vumark_vuforia_database.target_id.startswith( "<" - ): + ) or vumark_vuforia_database.instance_id.startswith("<"): pytest.skip( reason=( "VuMark generation settings are placeholders. " @@ -53,19 +28,11 @@ def _get_vumark_generation_settings() -> _VuMarkGenerationSettings: ), ) - return settings - - -def test_generate_instance_success( - vumark_vuforia_database: VuforiaDatabase, -) -> None: - """A VuMark instance can be generated with valid template settings.""" - settings = _get_vumark_generation_settings() - request_path = f"/targets/{settings.target_id}/instances" + request_path = f"/targets/{vumark_vuforia_database.target_id}/instances" content_type = "application/json" - content = json.dumps(obj={"instance_id": settings.instance_id}).encode( - encoding="utf-8" - ) + content = json.dumps( + obj={"instance_id": vumark_vuforia_database.instance_id} + ).encode(encoding="utf-8") date = rfc_1123_date() authorization_string = authorization_header( access_key=vumark_vuforia_database.server_access_key, diff --git a/vuforia_secrets.env.example b/vuforia_secrets.env.example index 3bd0e64ac..fde084a82 100644 --- a/vuforia_secrets.env.example +++ b/vuforia_secrets.env.example @@ -19,3 +19,5 @@ VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME= VUMARK_VUFORIA_SERVER_ACCESS_KEY= VUMARK_VUFORIA_SERVER_SECRET_KEY= +VUMARK_VUFORIA_TARGET_ID= +VUMARK_VUFORIA_INSTANCE_ID= From 18cedb6acfe2376cef2318c1292a599942024e36 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 16 Feb 2026 23:38:17 +0000 Subject: [PATCH 06/21] Update CI workflow and remove VuMark test skip logic --- .github/workflows/test.yml | 1 + tests/mock_vws/test_vumark_generation_api.py | 12 ------------ 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 386db8031..cc06d4323 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -113,6 +113,7 @@ jobs: - tests/mock_vws/test_update_target.py::TestInactiveProject - tests/mock_vws/test_requests_mock_usage.py - tests/mock_vws/test_flask_app_usage.py + - tests/mock_vws/test_vumark_generation_api.py - tests/mock_vws/test_docker.py - README.rst - docs/source/basic-example.rst diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index bb8d7d5a5..9a1b30234 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -3,7 +3,6 @@ import json from http import HTTPMethod, HTTPStatus -import pytest import requests from vws_auth_tools import authorization_header, rfc_1123_date @@ -17,17 +16,6 @@ def test_generate_instance_success( vumark_vuforia_database: VuMarkVuforiaDatabase, ) -> None: """A VuMark instance can be generated with valid template settings.""" - if vumark_vuforia_database.target_id.startswith( - "<" - ) or vumark_vuforia_database.instance_id.startswith("<"): - pytest.skip( - reason=( - "VuMark generation settings are placeholders. " - "Set VUMARK_VUFORIA_TARGET_ID and " - "VUMARK_VUFORIA_INSTANCE_ID." - ), - ) - request_path = f"/targets/{vumark_vuforia_database.target_id}/instances" content_type = "application/json" content = json.dumps( From 6d87404038ad44d0b5acc957e48f28c2628b4c20 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 16 Feb 2026 23:43:44 +0000 Subject: [PATCH 07/21] Generate VuMark instance IDs in test instead of env settings --- tests/mock_vws/fixtures/credentials.py | 3 --- tests/mock_vws/test_vumark_generation_api.py | 8 +++++--- vuforia_secrets.env.example | 1 - 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/mock_vws/fixtures/credentials.py b/tests/mock_vws/fixtures/credentials.py index 142b6f5d6..819ad2635 100644 --- a/tests/mock_vws/fixtures/credentials.py +++ b/tests/mock_vws/fixtures/credentials.py @@ -43,7 +43,6 @@ class _VuMarkVuforiaDatabaseSettings(BaseSettings): server_access_key: str server_secret_key: str target_id: str = "" - instance_id: str = "" model_config = SettingsConfigDict( env_prefix="VUMARK_VUFORIA_", @@ -60,7 +59,6 @@ class VuMarkVuforiaDatabase: server_access_key: str server_secret_key: str target_id: str - instance_id: str @pytest.fixture @@ -104,5 +102,4 @@ def vumark_vuforia_database() -> VuMarkVuforiaDatabase: server_access_key=settings.server_access_key, server_secret_key=settings.server_secret_key, target_id=settings.target_id, - instance_id=settings.instance_id, ) diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index 9a1b30234..6c2004e1c 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -2,6 +2,7 @@ import json from http import HTTPMethod, HTTPStatus +from uuid import uuid4 import requests from vws_auth_tools import authorization_header, rfc_1123_date @@ -18,9 +19,10 @@ def test_generate_instance_success( """A VuMark instance can be generated with valid template settings.""" request_path = f"/targets/{vumark_vuforia_database.target_id}/instances" content_type = "application/json" - content = json.dumps( - obj={"instance_id": vumark_vuforia_database.instance_id} - ).encode(encoding="utf-8") + generated_instance_id = uuid4().hex + content = json.dumps(obj={"instance_id": generated_instance_id}).encode( + encoding="utf-8" + ) date = rfc_1123_date() authorization_string = authorization_header( access_key=vumark_vuforia_database.server_access_key, diff --git a/vuforia_secrets.env.example b/vuforia_secrets.env.example index fde084a82..af7001f55 100644 --- a/vuforia_secrets.env.example +++ b/vuforia_secrets.env.example @@ -20,4 +20,3 @@ VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME= VUMARK_VUFORIA_SERVER_ACCESS_KEY= VUMARK_VUFORIA_SERVER_SECRET_KEY= VUMARK_VUFORIA_TARGET_ID= -VUMARK_VUFORIA_INSTANCE_ID= From dfd0bde66d5f812d54a29932a018e1c67d28a5bb Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 07:06:13 +0000 Subject: [PATCH 08/21] Refactor secrets generation to initialize VuMark data once --- admin/create_secrets_files.py | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 103ca0e53..144d828ff 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -133,9 +133,27 @@ def main() -> None: for i in range(num_databases) ] files_to_create = [file for file in required_files if not file.exists()] - driver: WebDriver | None = None shared_vumark_details: VuMarkDatabaseDict | None = None + while shared_vumark_details is None: + driver = vws_web_tools.create_chrome_driver() + time = datetime.datetime.now(tz=datetime.UTC).strftime( + format="%Y-%m-%d-%H-%M-%S", + ) + vumark_database_name = f"my-vumark-database-{time}" + vws_web_tools.log_in( + driver=driver, + email_address=email_address, + password=password, + ) + vws_web_tools.wait_for_logged_in(driver=driver) + shared_vumark_details = _create_and_get_vumark_details( + driver=driver, + vumark_database_name=vumark_database_name, + ) + driver.quit() + + driver: WebDriver | None = None while files_to_create: if driver is None: driver = vws_web_tools.create_chrome_driver() @@ -146,7 +164,6 @@ def main() -> None: ) license_name = f"my-license-{time}" database_name = f"my-database-{time}" - vumark_database_name = f"my-vumark-database-{time}" database_details = _create_and_get_database_details( driver=driver, @@ -160,19 +177,12 @@ def main() -> None: driver = None continue - if shared_vumark_details is None: - shared_vumark_details = _create_and_get_vumark_details( - driver=driver, - vumark_database_name=vumark_database_name, - ) - if shared_vumark_details is None: - driver.quit() - driver = None - continue - driver.quit() driver = None + if shared_vumark_details is None: + msg = "Failed to create shared VuMark database details." + raise RuntimeError(msg) file_contents = _generate_secrets_file_content( database_details=database_details, vumark_details=shared_vumark_details, From d65bccf9c8ddb020768bc6c78d02971c48647140 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 07:07:14 +0000 Subject: [PATCH 09/21] Fix typing in VuMark setup refactor --- admin/create_secrets_files.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 144d828ff..08f8976dd 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -136,22 +136,26 @@ def main() -> None: shared_vumark_details: VuMarkDatabaseDict | None = None while shared_vumark_details is None: - driver = vws_web_tools.create_chrome_driver() + vumark_driver = vws_web_tools.create_chrome_driver() time = datetime.datetime.now(tz=datetime.UTC).strftime( format="%Y-%m-%d-%H-%M-%S", ) vumark_database_name = f"my-vumark-database-{time}" vws_web_tools.log_in( - driver=driver, + driver=vumark_driver, email_address=email_address, password=password, ) - vws_web_tools.wait_for_logged_in(driver=driver) + vws_web_tools.wait_for_logged_in(driver=vumark_driver) shared_vumark_details = _create_and_get_vumark_details( - driver=driver, + driver=vumark_driver, vumark_database_name=vumark_database_name, ) - driver.quit() + vumark_driver.quit() + + if shared_vumark_details is None: + msg = "Failed to create shared VuMark database details." + raise RuntimeError(msg) driver: WebDriver | None = None while files_to_create: @@ -180,9 +184,6 @@ def main() -> None: driver.quit() driver = None - if shared_vumark_details is None: - msg = "Failed to create shared VuMark database details." - raise RuntimeError(msg) file_contents = _generate_secrets_file_content( database_details=database_details, vumark_details=shared_vumark_details, From a762e69157e827a212e40e85fb7b8b60575f677d Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 07:08:09 +0000 Subject: [PATCH 10/21] Remove redundant VuMark None check for pyright --- admin/create_secrets_files.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 08f8976dd..78097f445 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -153,10 +153,6 @@ def main() -> None: ) vumark_driver.quit() - if shared_vumark_details is None: - msg = "Failed to create shared VuMark database details." - raise RuntimeError(msg) - driver: WebDriver | None = None while files_to_create: if driver is None: From ee59318ad5a4f99e46a14cf9e61b8acb8ecfef60 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 07:19:34 +0000 Subject: [PATCH 11/21] Skip VuMark provisioning when no secrets files are missing --- admin/create_secrets_files.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 78097f445..0571cbe98 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -133,6 +133,10 @@ def main() -> None: for i in range(num_databases) ] files_to_create = [file for file in required_files if not file.exists()] + if not files_to_create: + sys.stdout.write("No secrets files need to be created.\n") + return + shared_vumark_details: VuMarkDatabaseDict | None = None while shared_vumark_details is None: From f706b1a0fe31a0735ebc8e4013dc17d7dcedcf42 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 18:43:34 +0000 Subject: [PATCH 12/21] Reset VuMark secrets files to origin/main --- admin/create_secrets_files.py | 91 ++++++++++++----------------------- vuforia_secrets.env.example | 8 +-- 2 files changed, 36 insertions(+), 63 deletions(-) diff --git a/admin/create_secrets_files.py b/admin/create_secrets_files.py index 5f3eef287..13d2310d0 100644 --- a/admin/create_secrets_files.py +++ b/admin/create_secrets_files.py @@ -136,50 +136,6 @@ def _create_vuforia_resource_names() -> tuple[str, str, str, str]: ) -def _create_shared_vumark_resources( - email_address: str, - password: str, -) -> tuple["VuMarkDatabaseDict", str]: - """Create shared VuMark resources used by all generated secrets - files. - """ - shared_vumark_details: VuMarkDatabaseDict | None = None - shared_vumark_target_id: str | None = None - - while shared_vumark_details is None or shared_vumark_target_id is None: - vumark_driver = vws_web_tools.create_chrome_driver() - ( - _license_name, - _database_name, - vumark_database_name, - vumark_template_name, - ) = _create_vuforia_resource_names() - try: - vws_web_tools.log_in( - driver=vumark_driver, - email_address=email_address, - password=password, - ) - vws_web_tools.wait_for_logged_in(driver=vumark_driver) - shared_vumark_details = _create_and_get_vumark_details( - driver=vumark_driver, - vumark_database_name=vumark_database_name, - ) - shared_vumark_target_id = _create_and_get_vumark_target_id( - driver=vumark_driver, - vumark_database_name=vumark_database_name, - vumark_template_name=vumark_template_name, - ) - except TimeoutException: - sys.stderr.write( - "Timed out waiting for shared VuMark setup/details after " - "retries\n" - ) - vumark_driver.quit() - - return shared_vumark_details, shared_vumark_target_id - - def main() -> None: """Create secrets files.""" email_address = os.environ["VWS_EMAIL_ADDRESS"] @@ -209,18 +165,8 @@ def main() -> None: for i in range(num_databases) ] files_to_create = [file for file in required_files if not file.exists()] - if not files_to_create: - sys.stdout.write("No secrets files need to be created.\n") - return - - shared_vumark_details, shared_vumark_target_id = ( - _create_shared_vumark_resources( - email_address=email_address, - password=password, - ) - ) - driver: WebDriver | None = None + while files_to_create: if driver is None: driver = vws_web_tools.create_chrome_driver() @@ -229,8 +175,8 @@ def main() -> None: ( license_name, database_name, - _vumark_database_name, - _vumark_template_name, + vumark_database_name, + vumark_template_name, ) = _create_vuforia_resource_names() try: @@ -249,14 +195,41 @@ def main() -> None: driver = None continue + try: + vumark_details = _create_and_get_vumark_details( + driver=driver, + vumark_database_name=vumark_database_name, + ) + except TimeoutException: + sys.stderr.write( + "Timed out waiting for VuMark setup/details after retries\n" + ) + driver.quit() + driver = None + continue + + try: + vumark_target_id = _create_and_get_vumark_target_id( + driver=driver, + vumark_database_name=vumark_database_name, + vumark_template_name=vumark_template_name, + ) + except TimeoutException: + sys.stderr.write( + "Timed out waiting for VuMark template upload after retries\n" + ) + driver.quit() + driver = None + continue + driver.quit() driver = None file_contents = _generate_secrets_file_content( database_details=database_details, - vumark_details=shared_vumark_details, + vumark_details=vumark_details, inactive_database_details=inactive_database_details, - vumark_target_id=shared_vumark_target_id, + vumark_target_id=vumark_target_id, ) file.write_text(data=file_contents) sys.stdout.write(f"Created database {file.name}\n") diff --git a/vuforia_secrets.env.example b/vuforia_secrets.env.example index 1920c860a..ea7273354 100644 --- a/vuforia_secrets.env.example +++ b/vuforia_secrets.env.example @@ -14,8 +14,8 @@ INACTIVE_VUFORIA_SERVER_SECRET_KEY= INACTIVE_VUFORIA_CLIENT_ACCESS_KEY= INACTIVE_VUFORIA_CLIENT_SECRET_KEY= -VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME= - -VUMARK_VUFORIA_SERVER_ACCESS_KEY= -VUMARK_VUFORIA_SERVER_SECRET_KEY= +VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME= VUMARK_VUFORIA_TARGET_ID= + +VUMARK_VUFORIA_SERVER_ACCESS_KEY= +VUMARK_VUFORIA_SERVER_SECRET_KEY= From 7dcfeef45c05f03814cfa19f8ca0fbdf77f47ec0 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 18:56:45 +0000 Subject: [PATCH 13/21] Add mock VuMark instance generation support and class-based test --- pyproject.toml | 1 + src/mock_vws/_flask_server/vws.py | 37 ++++++ .../_requests_mock_server/decorators.py | 2 +- .../mock_web_services_api.py | 44 ++++++- .../_services_validators/key_validators.py | 8 ++ .../_services_validators/target_validators.py | 2 + tests/mock_vws/fixtures/vuforia_backends.py | 10 +- tests/mock_vws/test_vumark_generation_api.py | 118 ++++++++++++------ 8 files changed, 183 insertions(+), 39 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 83b732734..620ab8d6f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -448,6 +448,7 @@ ignore_names = [ exclude = [ ".venv" ] ignore_decorators = [ "@pytest.fixture", + "@route", # Flask "@*APP.route", "@*APP.after_request", diff --git a/src/mock_vws/_flask_server/vws.py b/src/mock_vws/_flask_server/vws.py index 00ae09a12..6d0d94e1d 100644 --- a/src/mock_vws/_flask_server/vws.py +++ b/src/mock_vws/_flask_server/vws.py @@ -44,6 +44,12 @@ _LOGGER = logging.getLogger(name=__name__) +_VUMARK_PNG = base64.b64decode( + s=( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8A" + "AwMCAO7Zl6kAAAAASUVORK5CYII=" + ), +) @beartype @@ -338,6 +344,37 @@ def delete_target(target_id: str) -> Response: ) +@VWS_FLASK_APP.route( + rule="/targets//instances", + methods=[HTTPMethod.POST], +) +@beartype +def generate_vumark_instance(target_id: str) -> Response: + """Generate a VuMark instance. + + Fake implementation of + https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#generate-instance + """ + # ``target_id`` is validated by request validators. + del target_id + date = email.utils.formatdate(timeval=None, localtime=False, usegmt=True) + headers = { + "Connection": "keep-alive", + "Content-Type": "image/png", + "server": "envoy", + "Date": date, + "x-envoy-upstream-service-time": "5", + "strict-transport-security": "max-age=31536000", + "x-aws-region": "us-east-2, us-west-2", + "x-content-type-options": "nosniff", + } + return Response( + status=HTTPStatus.OK, + response=_VUMARK_PNG, + headers=headers, + ) + + @VWS_FLASK_APP.route(rule="/summary", methods=[HTTPMethod.GET]) @beartype def database_summary() -> Response: diff --git a/src/mock_vws/_requests_mock_server/decorators.py b/src/mock_vws/_requests_mock_server/decorators.py index a613fb53f..e8832b726 100644 --- a/src/mock_vws/_requests_mock_server/decorators.py +++ b/src/mock_vws/_requests_mock_server/decorators.py @@ -26,7 +26,7 @@ from .mock_web_query_api import MockVuforiaWebQueryAPI from .mock_web_services_api import MockVuforiaWebServicesAPI -_ResponseType = tuple[int, Mapping[str, str], str] +_ResponseType = tuple[int, Mapping[str, str], str | bytes] _Callback = Callable[[PreparedRequest], _ResponseType] _STRUCTURAL_SIMILARITY_MATCHER = StructuralSimilarityMatcher() diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py index ddee6ef03..13ee44ca5 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py @@ -34,11 +34,17 @@ from mock_vws.target_raters import TargetTrackingRater _TARGET_ID_PATTERN = "[A-Za-z0-9]+" +_VUMARK_PNG = base64.b64decode( + s=( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8A" + "AwMCAO7Zl6kAAAAASUVORK5CYII=" + ), +) _ROUTES: set[Route] = set() -_ResponseType = tuple[int, Mapping[str, str], str] +_ResponseType = tuple[int, Mapping[str, str], str | bytes] _P = ParamSpec("_P") @@ -287,6 +293,42 @@ def delete_target(self, request: PreparedRequest) -> _ResponseType: } return HTTPStatus.OK, headers, body_json + @route( + path_pattern=f"/targets/{_TARGET_ID_PATTERN}/instances", + http_methods={HTTPMethod.POST}, + ) + def generate_vumark_instance( + self, request: PreparedRequest + ) -> _ResponseType: + """Generate a VuMark instance.""" + try: + run_services_validators( + request_headers=request.headers, + request_body=_body_bytes(request=request), + request_method=request.method or "", + request_path=request.path_url, + databases=self._target_manager.databases, + ) + except ValidatorError as exc: + return exc.status_code, exc.headers, exc.response_text + + date = email.utils.formatdate( + timeval=None, + localtime=False, + usegmt=True, + ) + headers = { + "Connection": "keep-alive", + "Content-Type": "image/png", + "Date": date, + "server": "envoy", + "x-envoy-upstream-service-time": "5", + "strict-transport-security": "max-age=31536000", + "x-aws-region": "us-east-2, us-west-2", + "x-content-type-options": "nosniff", + } + return HTTPStatus.OK, headers, _VUMARK_PNG + @route(path_pattern="/summary", http_methods={HTTPMethod.GET}) def database_summary(self, request: PreparedRequest) -> _ResponseType: """Get a database summary report. diff --git a/src/mock_vws/_services_validators/key_validators.py b/src/mock_vws/_services_validators/key_validators.py index b07533fe0..cf4d32fa4 100644 --- a/src/mock_vws/_services_validators/key_validators.py +++ b/src/mock_vws/_services_validators/key_validators.py @@ -114,6 +114,13 @@ def validate_keys( }, ) + generate_instance = _Route( + path_pattern=f"/targets/{target_id_pattern}/instances", + http_methods={HTTPMethod.POST}, + mandatory_keys={"instance_id"}, + optional_keys=set(), + ) + target_summary = _Route( path_pattern=f"/summary/{target_id_pattern}", http_methods={HTTPMethod.GET}, @@ -129,6 +136,7 @@ def validate_keys( get_target, get_duplicates, update_target, + generate_instance, target_summary, ) diff --git a/src/mock_vws/_services_validators/target_validators.py b/src/mock_vws/_services_validators/target_validators.py index 4dbee04b1..58963b891 100644 --- a/src/mock_vws/_services_validators/target_validators.py +++ b/src/mock_vws/_services_validators/target_validators.py @@ -42,6 +42,8 @@ def validate_target_id_exists( return target_id = split_path[-1] + if split_path[-1] == "instances": + target_id = split_path[-2] database = get_database_matching_server_keys( request_headers=request_headers, request_body=request_body, diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index 39013b5b9..154c7cb08 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -234,7 +234,7 @@ def fixture_verify_mock_vuforia( vuforia_database: VuforiaDatabase, inactive_database: VuforiaDatabase, monkeypatch: pytest.MonkeyPatch, -) -> Generator[None]: +) -> Generator[VuforiaBackend]: """Test functions which use this fixture are run multiple times. Once with the real Vuforia, and once with each mock. @@ -257,11 +257,17 @@ def fixture_verify_mock_vuforia( VuforiaBackend.DOCKER_IN_MEMORY: _enable_use_docker_in_memory, }[backend] - yield from enable_function( + backend_generator = enable_function( working_database=vuforia_database, inactive_database=inactive_database, monkeypatch=monkeypatch, ) + next(backend_generator) + try: + yield backend + finally: + with contextlib.suppress(StopIteration): + next(backend_generator) @pytest.fixture( diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index 6c2004e1c..7cbe9aff1 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -1,53 +1,101 @@ """Tests for the VuMark generation web API.""" +import base64 +import io import json from http import HTTPMethod, HTTPStatus from uuid import uuid4 +import pytest import requests +from vws import VWS from vws_auth_tools import authorization_header, rfc_1123_date +from mock_vws.database import VuforiaDatabase from tests.mock_vws.fixtures.credentials import VuMarkVuforiaDatabase +from tests.mock_vws.fixtures.vuforia_backends import VuforiaBackend _VWS_HOST = "https://vws.vuforia.com" _PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n" -def test_generate_instance_success( - vumark_vuforia_database: VuMarkVuforiaDatabase, -) -> None: - """A VuMark instance can be generated with valid template settings.""" - request_path = f"/targets/{vumark_vuforia_database.target_id}/instances" - content_type = "application/json" - generated_instance_id = uuid4().hex - content = json.dumps(obj={"instance_id": generated_instance_id}).encode( - encoding="utf-8" - ) - date = rfc_1123_date() - authorization_string = authorization_header( - access_key=vumark_vuforia_database.server_access_key, - secret_key=vumark_vuforia_database.server_secret_key, - method=HTTPMethod.POST, - content=content, - content_type=content_type, - date=date, - request_path=request_path, - ) +@pytest.mark.usefixtures("verify_mock_vuforia") +class TestGenerateInstance: + """Tests for VuMark instance generation.""" - response = requests.post( - url=_VWS_HOST + request_path, - headers={ - "Accept": "image/png", - "Authorization": authorization_string, - "Content-Length": str(object=len(content)), - "Content-Type": content_type, - "Date": date, - }, - data=content, - timeout=30, + _TINY_PNG = base64.b64decode( + s=( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4" + "2mP8/x8AAwMCAO7Zl6kAAAAASUVORK5CYII=" + ), ) - assert response.status_code == HTTPStatus.OK - assert response.headers["Content-Type"].split(sep=";")[0] == "image/png" - assert response.content.startswith(_PNG_SIGNATURE) - assert len(response.content) > len(_PNG_SIGNATURE) + @staticmethod + def _create_mock_target_id(vuforia_database: VuforiaDatabase) -> str: + """Create and return a target ID for mock backends.""" + vws_client = VWS( + server_access_key=vuforia_database.server_access_key, + server_secret_key=vuforia_database.server_secret_key, + ) + return vws_client.add_target( + name=uuid4().hex, + width=1, + image=io.BytesIO(initial_bytes=TestGenerateInstance._TINY_PNG), + active_flag=True, + application_metadata=None, + ) + + def test_generate_instance_success( + self, + verify_mock_vuforia: VuforiaBackend, + vuforia_database: VuforiaDatabase, + vumark_vuforia_database: VuMarkVuforiaDatabase, + ) -> None: + """A VuMark instance can be generated with valid template settings.""" + if verify_mock_vuforia == VuforiaBackend.REAL: + server_access_key = vumark_vuforia_database.server_access_key + server_secret_key = vumark_vuforia_database.server_secret_key + target_id = vumark_vuforia_database.target_id + else: + server_access_key = vuforia_database.server_access_key + server_secret_key = vuforia_database.server_secret_key + target_id = self._create_mock_target_id( + vuforia_database=vuforia_database + ) + + request_path = f"/targets/{target_id}/instances" + content_type = "application/json" + generated_instance_id = uuid4().hex + content = json.dumps( + obj={"instance_id": generated_instance_id} + ).encode(encoding="utf-8") + date = rfc_1123_date() + authorization_string = authorization_header( + access_key=server_access_key, + secret_key=server_secret_key, + method=HTTPMethod.POST, + content=content, + content_type=content_type, + date=date, + request_path=request_path, + ) + + response = requests.post( + url=_VWS_HOST + request_path, + headers={ + "Accept": "image/png", + "Authorization": authorization_string, + "Content-Length": str(object=len(content)), + "Content-Type": content_type, + "Date": date, + }, + data=content, + timeout=30, + ) + + assert response.status_code == HTTPStatus.OK + assert ( + response.headers["Content-Type"].split(sep=";")[0] == "image/png" + ) + assert response.content.startswith(_PNG_SIGNATURE) + assert len(response.content) > len(_PNG_SIGNATURE) From 9cd876552cbe5d90308de7e27586e6657d1912cc Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 20:10:46 +0000 Subject: [PATCH 14/21] Fix generator teardown pattern in backend fixture --- tests/mock_vws/fixtures/vuforia_backends.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index 154c7cb08..a5ab8590e 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -257,17 +257,12 @@ def fixture_verify_mock_vuforia( VuforiaBackend.DOCKER_IN_MEMORY: _enable_use_docker_in_memory, }[backend] - backend_generator = enable_function( + for _ in enable_function( working_database=vuforia_database, inactive_database=inactive_database, monkeypatch=monkeypatch, - ) - next(backend_generator) - try: + ): yield backend - finally: - with contextlib.suppress(StopIteration): - next(backend_generator) @pytest.fixture( From 43e2ac6cfb8bf3febafea3f5888539de0c01b8d1 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 22:43:50 +0000 Subject: [PATCH 15/21] Add VuMark target setup to mock backends --- tests/mock_vws/fixtures/vuforia_backends.py | 64 +++++++++- tests/mock_vws/test_vumark_generation_api.py | 122 +++++++------------ 2 files changed, 102 insertions(+), 84 deletions(-) diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index a5ab8590e..155a2bb70 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -1,6 +1,7 @@ """Choose which backends to use for the tests.""" import contextlib +import io import logging from collections.abc import Generator from enum import Enum @@ -9,6 +10,7 @@ import requests import responses from beartype import beartype +from PIL import Image from requests_mock_flask import add_flask_app_to_mock from vws import VWS from vws.exceptions.vws_exceptions import ( @@ -21,6 +23,9 @@ from mock_vws._flask_server.vws import VWS_FLASK_APP from mock_vws.database import VuforiaDatabase from mock_vws.states import States +from mock_vws.target import Target +from mock_vws.target_raters import HardcodedTargetTrackingRater +from tests.mock_vws.fixtures.credentials import VuMarkVuforiaDatabase from tests.mock_vws.utils.retries import RETRY_ON_TOO_MANY_REQUESTS LOGGER = logging.getLogger(name=__name__) @@ -57,16 +62,51 @@ def _delete_all_targets(*, database_keys: VuforiaDatabase) -> None: vws_client.delete_target(target_id=target) +@beartype +def _rgb_png_bytes() -> bytes: + """Return a small RGB PNG image.""" + image = Image.new(mode="RGB", size=(8, 8), color=(255, 0, 0)) + image_file = io.BytesIO() + image.save(fp=image_file, format="PNG") + return image_file.getvalue() + + +@beartype +def _vumark_database( + *, + vumark_vuforia_database: VuMarkVuforiaDatabase, +) -> VuforiaDatabase: + """Return a database with a target for VuMark instance generation.""" + vumark_target = Target( + active_flag=True, + application_metadata=None, + image_value=_rgb_png_bytes(), + name="mock-vumark-target", + processing_time_seconds=0, + width=1, + target_tracking_rater=HardcodedTargetTrackingRater(rating=5), + target_id=vumark_vuforia_database.target_id, + ) + return VuforiaDatabase( + database_name=vumark_vuforia_database.target_manager_database_name, + server_access_key=vumark_vuforia_database.server_access_key, + server_secret_key=vumark_vuforia_database.server_secret_key, + targets={vumark_target}, + ) + + @beartype def _enable_use_real_vuforia( *, working_database: VuforiaDatabase, inactive_database: VuforiaDatabase, + vumark_vuforia_database: VuMarkVuforiaDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: """Test against the real Vuforia.""" assert monkeypatch assert inactive_database + assert vumark_vuforia_database _delete_all_targets(database_keys=working_database) yield @@ -76,6 +116,7 @@ def _enable_use_mock_vuforia( *, working_database: VuforiaDatabase, inactive_database: VuforiaDatabase, + vumark_vuforia_database: VuMarkVuforiaDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: """Test against the in-memory mock Vuforia.""" @@ -96,10 +137,14 @@ def _enable_use_mock_vuforia( client_access_key=inactive_database.client_access_key, client_secret_key=inactive_database.client_secret_key, ) + vumark_database = _vumark_database( + vumark_vuforia_database=vumark_vuforia_database, + ) with MockVWS() as mock: mock.add_database(database=working_database) mock.add_database(database=inactive_database) + mock.add_database(database=vumark_database) yield @@ -108,6 +153,7 @@ def _enable_use_docker_in_memory( *, working_database: VuforiaDatabase, inactive_database: VuforiaDatabase, + vumark_vuforia_database: VuMarkVuforiaDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: """Test against mock Vuforia created to be run in a container.""" @@ -170,6 +216,13 @@ def _enable_use_docker_in_memory( json=inactive_database.to_dict(), timeout=30, ) + requests.post( + url=databases_url, + json=_vumark_database( + vumark_vuforia_database=vumark_vuforia_database, + ).to_dict(), + timeout=30, + ) yield @@ -233,8 +286,9 @@ def fixture_verify_mock_vuforia( request: pytest.FixtureRequest, vuforia_database: VuforiaDatabase, inactive_database: VuforiaDatabase, + vumark_vuforia_database: VuMarkVuforiaDatabase, monkeypatch: pytest.MonkeyPatch, -) -> Generator[VuforiaBackend]: +) -> Generator[None]: """Test functions which use this fixture are run multiple times. Once with the real Vuforia, and once with each mock. @@ -257,12 +311,12 @@ def fixture_verify_mock_vuforia( VuforiaBackend.DOCKER_IN_MEMORY: _enable_use_docker_in_memory, }[backend] - for _ in enable_function( + yield from enable_function( working_database=vuforia_database, inactive_database=inactive_database, + vumark_vuforia_database=vumark_vuforia_database, monkeypatch=monkeypatch, - ): - yield backend + ) @pytest.fixture( @@ -278,6 +332,7 @@ def mock_only_vuforia( request: pytest.FixtureRequest, vuforia_database: VuforiaDatabase, inactive_database: VuforiaDatabase, + vumark_vuforia_database: VuMarkVuforiaDatabase, monkeypatch: pytest.MonkeyPatch, ) -> Generator[None]: """Test functions which use this fixture are run multiple times. Once @@ -305,5 +360,6 @@ def mock_only_vuforia( yield from enable_function( working_database=vuforia_database, inactive_database=inactive_database, + vumark_vuforia_database=vumark_vuforia_database, monkeypatch=monkeypatch, ) diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index 7cbe9aff1..8c14a72af 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -1,101 +1,63 @@ """Tests for the VuMark generation web API.""" -import base64 -import io import json from http import HTTPMethod, HTTPStatus from uuid import uuid4 import pytest import requests -from vws import VWS from vws_auth_tools import authorization_header, rfc_1123_date -from mock_vws.database import VuforiaDatabase from tests.mock_vws.fixtures.credentials import VuMarkVuforiaDatabase -from tests.mock_vws.fixtures.vuforia_backends import VuforiaBackend _VWS_HOST = "https://vws.vuforia.com" _PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n" @pytest.mark.usefixtures("verify_mock_vuforia") -class TestGenerateInstance: - """Tests for VuMark instance generation.""" - - _TINY_PNG = base64.b64decode( - s=( - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4" - "2mP8/x8AAwMCAO7Zl6kAAAAASUVORK5CYII=" - ), - ) - - @staticmethod - def _create_mock_target_id(vuforia_database: VuforiaDatabase) -> str: - """Create and return a target ID for mock backends.""" - vws_client = VWS( - server_access_key=vuforia_database.server_access_key, - server_secret_key=vuforia_database.server_secret_key, +def test_generate_instance_success( + vumark_vuforia_database: VuMarkVuforiaDatabase, +) -> None: + """A VuMark instance can be generated with valid template settings.""" + if vumark_vuforia_database.target_id.startswith("<"): + pytest.skip( + reason=( + "VuMark target ID is a placeholder. " + "Set VUMARK_VUFORIA_TARGET_ID." + ), ) - return vws_client.add_target( - name=uuid4().hex, - width=1, - image=io.BytesIO(initial_bytes=TestGenerateInstance._TINY_PNG), - active_flag=True, - application_metadata=None, - ) - - def test_generate_instance_success( - self, - verify_mock_vuforia: VuforiaBackend, - vuforia_database: VuforiaDatabase, - vumark_vuforia_database: VuMarkVuforiaDatabase, - ) -> None: - """A VuMark instance can be generated with valid template settings.""" - if verify_mock_vuforia == VuforiaBackend.REAL: - server_access_key = vumark_vuforia_database.server_access_key - server_secret_key = vumark_vuforia_database.server_secret_key - target_id = vumark_vuforia_database.target_id - else: - server_access_key = vuforia_database.server_access_key - server_secret_key = vuforia_database.server_secret_key - target_id = self._create_mock_target_id( - vuforia_database=vuforia_database - ) - request_path = f"/targets/{target_id}/instances" - content_type = "application/json" - generated_instance_id = uuid4().hex - content = json.dumps( - obj={"instance_id": generated_instance_id} - ).encode(encoding="utf-8") - date = rfc_1123_date() - authorization_string = authorization_header( - access_key=server_access_key, - secret_key=server_secret_key, - method=HTTPMethod.POST, - content=content, - content_type=content_type, - date=date, - request_path=request_path, - ) + request_path = f"/targets/{vumark_vuforia_database.target_id}/instances" + content_type = "application/json" + generated_instance_id = uuid4().hex + content = json.dumps(obj={"instance_id": generated_instance_id}).encode( + encoding="utf-8" + ) + date = rfc_1123_date() + authorization_string = authorization_header( + access_key=vumark_vuforia_database.server_access_key, + secret_key=vumark_vuforia_database.server_secret_key, + method=HTTPMethod.POST, + content=content, + content_type=content_type, + date=date, + request_path=request_path, + ) - response = requests.post( - url=_VWS_HOST + request_path, - headers={ - "Accept": "image/png", - "Authorization": authorization_string, - "Content-Length": str(object=len(content)), - "Content-Type": content_type, - "Date": date, - }, - data=content, - timeout=30, - ) + response = requests.post( + url=_VWS_HOST + request_path, + headers={ + "Accept": "image/png", + "Authorization": authorization_string, + "Content-Length": str(object=len(content)), + "Content-Type": content_type, + "Date": date, + }, + data=content, + timeout=30, + ) - assert response.status_code == HTTPStatus.OK - assert ( - response.headers["Content-Type"].split(sep=";")[0] == "image/png" - ) - assert response.content.startswith(_PNG_SIGNATURE) - assert len(response.content) > len(_PNG_SIGNATURE) + assert response.status_code == HTTPStatus.OK + assert response.headers["Content-Type"].split(sep=";")[0] == "image/png" + assert response.content.startswith(_PNG_SIGNATURE) + assert len(response.content) > len(_PNG_SIGNATURE) From bf8224ae9c8238244f1d0012880bfe60227e4e89 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 23:03:21 +0000 Subject: [PATCH 16/21] Fix BugBot issues for VuMark support --- src/mock_vws/_constants.py | 7 +++ src/mock_vws/_flask_server/vws.py | 10 +-- .../mock_web_services_api.py | 10 +-- .../_services_validators/target_validators.py | 7 ++- tests/mock_vws/fixtures/credentials.py | 10 +-- tests/mock_vws/fixtures/vuforia_backends.py | 32 +++++----- tests/mock_vws/test_target_validators.py | 63 +++++++++++++++++++ 7 files changed, 102 insertions(+), 37 deletions(-) create mode 100644 tests/mock_vws/test_target_validators.py diff --git a/src/mock_vws/_constants.py b/src/mock_vws/_constants.py index 1f832af1f..d9af993b5 100644 --- a/src/mock_vws/_constants.py +++ b/src/mock_vws/_constants.py @@ -4,6 +4,13 @@ from beartype import beartype +VUMARK_PNG = ( + b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00" + b"\x01\x08\x04\x00\x00\x00\xb5\x1c\x0c\x02\x00\x00\x00\x0bIDATx\xdac" + b"\xfc\xff\x1f\x00\x03\x03\x02\x00\xee\xd9\x97\xa9\x00\x00\x00\x00IEND" + b"\xaeB`\x82" +) + @beartype @unique diff --git a/src/mock_vws/_flask_server/vws.py b/src/mock_vws/_flask_server/vws.py index 6d0d94e1d..99b8c3b8e 100644 --- a/src/mock_vws/_flask_server/vws.py +++ b/src/mock_vws/_flask_server/vws.py @@ -18,7 +18,7 @@ from flask import Flask, Response, request from pydantic_settings import BaseSettings -from mock_vws._constants import ResultCodes, TargetStatuses +from mock_vws._constants import VUMARK_PNG, ResultCodes, TargetStatuses from mock_vws._database_matchers import get_database_matching_server_keys from mock_vws._mock_common import json_dump from mock_vws._services_validators import run_services_validators @@ -44,12 +44,6 @@ _LOGGER = logging.getLogger(name=__name__) -_VUMARK_PNG = base64.b64decode( - s=( - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8A" - "AwMCAO7Zl6kAAAAASUVORK5CYII=" - ), -) @beartype @@ -370,7 +364,7 @@ def generate_vumark_instance(target_id: str) -> Response: } return Response( status=HTTPStatus.OK, - response=_VUMARK_PNG, + response=VUMARK_PNG, headers=headers, ) diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py index 13ee44ca5..5c9f3e4d8 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py @@ -18,7 +18,7 @@ from beartype import BeartypeConf, beartype from requests.models import PreparedRequest -from mock_vws._constants import ResultCodes, TargetStatuses +from mock_vws._constants import VUMARK_PNG, ResultCodes, TargetStatuses from mock_vws._database_matchers import get_database_matching_server_keys from mock_vws._mock_common import Route, json_dump from mock_vws._services_validators import run_services_validators @@ -34,12 +34,6 @@ from mock_vws.target_raters import TargetTrackingRater _TARGET_ID_PATTERN = "[A-Za-z0-9]+" -_VUMARK_PNG = base64.b64decode( - s=( - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8A" - "AwMCAO7Zl6kAAAAASUVORK5CYII=" - ), -) _ROUTES: set[Route] = set() @@ -327,7 +321,7 @@ def generate_vumark_instance( "x-aws-region": "us-east-2, us-west-2", "x-content-type-options": "nosniff", } - return HTTPStatus.OK, headers, _VUMARK_PNG + return HTTPStatus.OK, headers, VUMARK_PNG @route(path_pattern="/summary", http_methods={HTTPMethod.GET}) def database_summary(self, request: PreparedRequest) -> _ResponseType: diff --git a/src/mock_vws/_services_validators/target_validators.py b/src/mock_vws/_services_validators/target_validators.py index 58963b891..aedfa511e 100644 --- a/src/mock_vws/_services_validators/target_validators.py +++ b/src/mock_vws/_services_validators/target_validators.py @@ -10,6 +10,7 @@ from mock_vws.database import VuforiaDatabase _LOGGER = logging.getLogger(name=__name__) +_TARGETS_WITH_INSTANCE_PATH_LENGTH = 4 @beartype @@ -42,7 +43,11 @@ def validate_target_id_exists( return target_id = split_path[-1] - if split_path[-1] == "instances": + if ( + len(split_path) == _TARGETS_WITH_INSTANCE_PATH_LENGTH + and split_path[-3] == "targets" + and split_path[-1] == "instances" + ): target_id = split_path[-2] database = get_database_matching_server_keys( request_headers=request_headers, diff --git a/tests/mock_vws/fixtures/credentials.py b/tests/mock_vws/fixtures/credentials.py index 819ad2635..bc37d2f88 100644 --- a/tests/mock_vws/fixtures/credentials.py +++ b/tests/mock_vws/fixtures/credentials.py @@ -1,6 +1,6 @@ """Fixtures for credentials for Vuforia databases.""" -from dataclasses import dataclass +from dataclasses import dataclass, field from pathlib import Path import pytest @@ -55,10 +55,10 @@ class _VuMarkVuforiaDatabaseSettings(BaseSettings): class VuMarkVuforiaDatabase: """Credentials for the VuMark generation API.""" - target_manager_database_name: str - server_access_key: str - server_secret_key: str - target_id: str + target_manager_database_name: str = field(repr=False) + server_access_key: str = field(repr=False) + server_secret_key: str = field(repr=False) + target_id: str = field(repr=False) @pytest.fixture diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py index 155a2bb70..c0e95b08c 100644 --- a/tests/mock_vws/fixtures/vuforia_backends.py +++ b/tests/mock_vws/fixtures/vuforia_backends.py @@ -1,7 +1,6 @@ """Choose which backends to use for the tests.""" import contextlib -import io import logging from collections.abc import Generator from enum import Enum @@ -10,7 +9,6 @@ import requests import responses from beartype import beartype -from PIL import Image from requests_mock_flask import add_flask_app_to_mock from vws import VWS from vws.exceptions.vws_exceptions import ( @@ -26,6 +24,7 @@ from mock_vws.target import Target from mock_vws.target_raters import HardcodedTargetTrackingRater from tests.mock_vws.fixtures.credentials import VuMarkVuforiaDatabase +from tests.mock_vws.utils import make_image_file from tests.mock_vws.utils.retries import RETRY_ON_TOO_MANY_REQUESTS LOGGER = logging.getLogger(name=__name__) @@ -62,15 +61,6 @@ def _delete_all_targets(*, database_keys: VuforiaDatabase) -> None: vws_client.delete_target(target_id=target) -@beartype -def _rgb_png_bytes() -> bytes: - """Return a small RGB PNG image.""" - image = Image.new(mode="RGB", size=(8, 8), color=(255, 0, 0)) - image_file = io.BytesIO() - image.save(fp=image_file, format="PNG") - return image_file.getvalue() - - @beartype def _vumark_database( *, @@ -80,7 +70,12 @@ def _vumark_database( vumark_target = Target( active_flag=True, application_metadata=None, - image_value=_rgb_png_bytes(), + image_value=make_image_file( + file_format="PNG", + color_space="RGB", + width=8, + height=8, + ).getvalue(), name="mock-vumark-target", processing_time_seconds=0, width=1, @@ -177,6 +172,10 @@ def _enable_use_docker_in_memory( name="TARGET_MANAGER_BASE_URL", value=target_manager_base_url, ) + vumark_database = _vumark_database( + vumark_vuforia_database=vumark_vuforia_database, + ) + (vumark_target,) = vumark_database.targets with responses.RequestsMock(assert_all_requests_are_fired=False) as mock: add_flask_app_to_mock( @@ -218,9 +217,12 @@ def _enable_use_docker_in_memory( ) requests.post( url=databases_url, - json=_vumark_database( - vumark_vuforia_database=vumark_vuforia_database, - ).to_dict(), + json=vumark_database.to_dict(), + timeout=30, + ) + requests.post( + url=(f"{databases_url}/{vumark_database.database_name}/targets"), + json=vumark_target.to_dict(), timeout=30, ) diff --git a/tests/mock_vws/test_target_validators.py b/tests/mock_vws/test_target_validators.py new file mode 100644 index 000000000..314b253e0 --- /dev/null +++ b/tests/mock_vws/test_target_validators.py @@ -0,0 +1,63 @@ +"""Tests for target ID validators.""" + +import pytest + +from mock_vws._services_validators.target_validators import ( + validate_target_id_exists, +) +from mock_vws.database import VuforiaDatabase +from mock_vws.target import Target +from mock_vws.target_raters import HardcodedTargetTrackingRater +from tests.mock_vws.utils import make_image_file + + +def _database_with_target(*, target_id: str) -> VuforiaDatabase: + """Create a database containing one target with the given ID.""" + target = Target( + active_flag=True, + application_metadata=None, + image_value=make_image_file( + file_format="PNG", + color_space="RGB", + width=8, + height=8, + ).getvalue(), + name="example", + processing_time_seconds=0, + target_id=target_id, + target_tracking_rater=HardcodedTargetTrackingRater(rating=5), + width=1, + ) + return VuforiaDatabase(targets={target}) + + +@pytest.mark.parametrize( + ("request_path", "target_id"), + [ + ("/targets/instances", "instances"), + ("/targets/target123/instances", "target123"), + ], +) +def test_validate_target_id_exists_uses_correct_path_segment( + *, + request_path: str, + target_id: str, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Validation uses the right target segment for both endpoint + shapes. + """ + database = _database_with_target(target_id=target_id) + monkeypatch.setattr( + "mock_vws._services_validators.target_validators." + "get_database_matching_server_keys", + lambda **_kwargs: database, + ) + + validate_target_id_exists( + request_path=request_path, + request_headers={}, + request_body=b"", + request_method="GET", + databases={database}, + ) From fbae231aa11735814f28ac0984899c307a48cc36 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 23:04:36 +0000 Subject: [PATCH 17/21] Fix mypy typing in target validator test --- tests/mock_vws/test_target_validators.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/mock_vws/test_target_validators.py b/tests/mock_vws/test_target_validators.py index 314b253e0..8d68e2b99 100644 --- a/tests/mock_vws/test_target_validators.py +++ b/tests/mock_vws/test_target_validators.py @@ -2,6 +2,7 @@ import pytest +from mock_vws._services_validators import target_validators from mock_vws._services_validators.target_validators import ( validate_target_id_exists, ) @@ -32,8 +33,8 @@ def _database_with_target(*, target_id: str) -> VuforiaDatabase: @pytest.mark.parametrize( - ("request_path", "target_id"), - [ + argnames=("request_path", "target_id"), + argvalues=[ ("/targets/instances", "instances"), ("/targets/target123/instances", "target123"), ], @@ -49,9 +50,9 @@ def test_validate_target_id_exists_uses_correct_path_segment( """ database = _database_with_target(target_id=target_id) monkeypatch.setattr( - "mock_vws._services_validators.target_validators." - "get_database_matching_server_keys", - lambda **_kwargs: database, + target=target_validators, + name="get_database_matching_server_keys", + value=lambda **_kwargs: database, ) validate_target_id_exists( From 6dea443fd320c5fb240f4c7e10b767d8f7180ae8 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 23:06:17 +0000 Subject: [PATCH 18/21] Resolve pyright typing in validator test --- tests/mock_vws/test_target_validators.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/mock_vws/test_target_validators.py b/tests/mock_vws/test_target_validators.py index 8d68e2b99..04b147422 100644 --- a/tests/mock_vws/test_target_validators.py +++ b/tests/mock_vws/test_target_validators.py @@ -1,5 +1,8 @@ """Tests for target ID validators.""" +from collections.abc import Iterable, Mapping +from functools import partial + import pytest from mock_vws._services_validators import target_validators @@ -32,6 +35,24 @@ def _database_with_target(*, target_id: str) -> VuforiaDatabase: return VuforiaDatabase(targets={target}) +def _always_match_database( + *, + database: VuforiaDatabase, + request_headers: Mapping[str, str], + request_body: bytes | None, + request_method: str, + request_path: str, + databases: Iterable[VuforiaDatabase], +) -> VuforiaDatabase: + """Return the given database regardless of request details.""" + del request_headers + del request_body + del request_method + del request_path + del databases + return database + + @pytest.mark.parametrize( argnames=("request_path", "target_id"), argvalues=[ @@ -49,10 +70,11 @@ def test_validate_target_id_exists_uses_correct_path_segment( shapes. """ database = _database_with_target(target_id=target_id) + monkeypatch.setattr( target=target_validators, name="get_database_matching_server_keys", - value=lambda **_kwargs: database, + value=partial(_always_match_database, database=database), ) validate_target_id_exists( From 941348f09462ce5c9973c8f00044636e33ad4f5e Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 17 Feb 2026 23:55:30 +0000 Subject: [PATCH 19/21] Remove uncovered branches from generate_vumark_instance See https://github.com/VWS-Python/vws-python-mock/issues/2942 to add back the ValidatorError handling once tests cover those branches. --- .../mock_web_services_api.py | 17 +++++++---------- tests/mock_vws/test_vumark_generation_api.py | 8 -------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py index 5c9f3e4d8..303d4a2b4 100644 --- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py +++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py @@ -295,16 +295,13 @@ def generate_vumark_instance( self, request: PreparedRequest ) -> _ResponseType: """Generate a VuMark instance.""" - try: - run_services_validators( - request_headers=request.headers, - request_body=_body_bytes(request=request), - request_method=request.method or "", - request_path=request.path_url, - databases=self._target_manager.databases, - ) - except ValidatorError as exc: - return exc.status_code, exc.headers, exc.response_text + run_services_validators( + request_headers=request.headers, + request_body=_body_bytes(request=request), + request_method=request.method or "", + request_path=request.path_url, + databases=self._target_manager.databases, + ) date = email.utils.formatdate( timeval=None, diff --git a/tests/mock_vws/test_vumark_generation_api.py b/tests/mock_vws/test_vumark_generation_api.py index 8c14a72af..ac7b2634a 100644 --- a/tests/mock_vws/test_vumark_generation_api.py +++ b/tests/mock_vws/test_vumark_generation_api.py @@ -19,14 +19,6 @@ def test_generate_instance_success( vumark_vuforia_database: VuMarkVuforiaDatabase, ) -> None: """A VuMark instance can be generated with valid template settings.""" - if vumark_vuforia_database.target_id.startswith("<"): - pytest.skip( - reason=( - "VuMark target ID is a placeholder. " - "Set VUMARK_VUFORIA_TARGET_ID." - ), - ) - request_path = f"/targets/{vumark_vuforia_database.target_id}/instances" content_type = "application/json" generated_instance_id = uuid4().hex From 5442a769e09d1ce8157b1994d270a8c195ec9ef6 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 00:10:48 +0000 Subject: [PATCH 20/21] Use valid default target ID for VuMark database settings The previous default '' contained '<' and '>' which are not matched by the target ID URL pattern [A-Za-z0-9]+, causing the mock to fail to route requests in tests. --- tests/mock_vws/fixtures/credentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mock_vws/fixtures/credentials.py b/tests/mock_vws/fixtures/credentials.py index bc37d2f88..91069d473 100644 --- a/tests/mock_vws/fixtures/credentials.py +++ b/tests/mock_vws/fixtures/credentials.py @@ -42,7 +42,7 @@ class _VuMarkVuforiaDatabaseSettings(BaseSettings): target_manager_database_name: str server_access_key: str server_secret_key: str - target_id: str = "" + target_id: str = "MockVuMarkTargetID00" model_config = SettingsConfigDict( env_prefix="VUMARK_VUFORIA_", From 270f9d96c23d460dd9851840c4d1406c5e448d03 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Wed, 18 Feb 2026 00:36:23 +0000 Subject: [PATCH 21/21] Use valid target ID placeholder in vuforia_secrets.env.example The windows-tests and skip-tests CI jobs copy this file directly, so the VUMARK_VUFORIA_TARGET_ID value must be alphanumeric to match the [A-Za-z0-9]+ URL pattern. --- vuforia_secrets.env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vuforia_secrets.env.example b/vuforia_secrets.env.example index ea7273354..43847306c 100644 --- a/vuforia_secrets.env.example +++ b/vuforia_secrets.env.example @@ -15,7 +15,7 @@ INACTIVE_VUFORIA_CLIENT_ACCESS_KEY= INACTIVE_VUFORIA_CLIENT_SECRET_KEY= VUMARK_VUFORIA_TARGET_MANAGER_DATABASE_NAME= -VUMARK_VUFORIA_TARGET_ID= +VUMARK_VUFORIA_TARGET_ID=MockVuMarkTargetID00 VUMARK_VUFORIA_SERVER_ACCESS_KEY= VUMARK_VUFORIA_SERVER_SECRET_KEY=