Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d2b383a
Add minimal VuMark API interaction test
adamtheturtle Feb 16, 2026
a133585
Share VuMark credentials across secrets files
adamtheturtle Feb 16, 2026
00a5951
Set VuMark test Accept header
adamtheturtle Feb 16, 2026
f8c083c
Make VuMark test assert successful generation
adamtheturtle Feb 16, 2026
9efcf5e
Use VuMark fixture fields in generation test
adamtheturtle Feb 16, 2026
18cedb6
Update CI workflow and remove VuMark test skip logic
adamtheturtle Feb 16, 2026
6d87404
Generate VuMark instance IDs in test instead of env settings
adamtheturtle Feb 16, 2026
dfd0bde
Refactor secrets generation to initialize VuMark data once
adamtheturtle Feb 17, 2026
d65bccf
Fix typing in VuMark setup refactor
adamtheturtle Feb 17, 2026
a762e69
Remove redundant VuMark None check for pyright
adamtheturtle Feb 17, 2026
ee59318
Skip VuMark provisioning when no secrets files are missing
adamtheturtle Feb 17, 2026
2ea8843
Merge origin/main into adamtheturtle/vumark-api-test
adamtheturtle Feb 17, 2026
9409d2e
Merge origin/main into adamtheturtle/vumark-api-test
adamtheturtle Feb 17, 2026
381878f
Merge remote-tracking branch 'origin/main' into adamtheturtle/vumark-…
adamtheturtle Feb 17, 2026
79bad8d
Merge origin/main into adamtheturtle/vumark-api-test
adamtheturtle Feb 17, 2026
f706b1a
Reset VuMark secrets files to origin/main
adamtheturtle Feb 17, 2026
7dcfeef
Add mock VuMark instance generation support and class-based test
adamtheturtle Feb 17, 2026
9cd8765
Fix generator teardown pattern in backend fixture
adamtheturtle Feb 17, 2026
bf6ed8c
Merge remote-tracking branch 'origin/main' into adamtheturtle/vumark-…
adamtheturtle Feb 17, 2026
43e2ac6
Add VuMark target setup to mock backends
adamtheturtle Feb 17, 2026
bf8224a
Fix BugBot issues for VuMark support
adamtheturtle Feb 17, 2026
fbae231
Fix mypy typing in target validator test
adamtheturtle Feb 17, 2026
6dea443
Resolve pyright typing in validator test
adamtheturtle Feb 17, 2026
941348f
Remove uncovered branches from generate_vumark_instance
adamtheturtle Feb 17, 2026
5442a76
Use valid default target ID for VuMark database settings
adamtheturtle Feb 18, 2026
270f9d9
Use valid target ID placeholder in vuforia_secrets.env.example
adamtheturtle Feb 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion docs/source/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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 \
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ ignore_names = [
exclude = [ ".venv" ]
ignore_decorators = [
"@pytest.fixture",
"@route",
# Flask
"@*APP.route",
"@*APP.after_request",
Expand Down
7 changes: 7 additions & 0 deletions src/mock_vws/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 32 additions & 1 deletion src/mock_vws/_flask_server/vws.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -338,6 +338,37 @@ def delete_target(target_id: str) -> Response:
)


@VWS_FLASK_APP.route(
rule="/targets/<string:target_id>/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:
Expand Down
2 changes: 1 addition & 1 deletion src/mock_vws/_requests_mock_server/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
37 changes: 35 additions & 2 deletions src/mock_vws/_requests_mock_server/mock_web_services_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -38,7 +38,7 @@

_ROUTES: set[Route] = set()

_ResponseType = tuple[int, Mapping[str, str], str]
_ResponseType = tuple[int, Mapping[str, str], str | bytes]
_P = ParamSpec("_P")


Expand Down Expand Up @@ -287,6 +287,39 @@ 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."""
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,
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing ValidatorError handling in VuMark route handler

High Severity

The generate_vumark_instance method in the requests-mock server calls run_services_validators without wrapping it in try/except ValidatorError, unlike every other route handler in this file. When validation fails (e.g., bad auth, unknown target, missing keys), the ValidatorError propagates as an unhandled exception through the responses mock callback instead of being converted into a proper HTTP error response via exc.status_code, exc.headers, exc.response_text.

Fix in Cursor Fix in Web


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.
Expand Down
8 changes: 8 additions & 0 deletions src/mock_vws/_services_validators/key_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -129,6 +136,7 @@ def validate_keys(
get_target,
get_duplicates,
update_target,
generate_instance,
target_summary,
)

Expand Down
7 changes: 7 additions & 0 deletions src/mock_vws/_services_validators/target_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from mock_vws.database import VuforiaDatabase

_LOGGER = logging.getLogger(name=__name__)
_TARGETS_WITH_INSTANCE_PATH_LENGTH = 4


@beartype
Expand Down Expand Up @@ -42,6 +43,12 @@ def validate_target_id_exists(
return

target_id = split_path[-1]
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,
request_body=request_body,
Expand Down
39 changes: 39 additions & 0 deletions tests/mock_vws/fixtures/credentials.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Fixtures for credentials for Vuforia databases."""

from dataclasses import dataclass, field
from pathlib import Path

import pytest
Expand Down Expand Up @@ -35,6 +36,31 @@ 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
target_id: str = "MockVuMarkTargetID00"

model_config = SettingsConfigDict(
env_prefix="VUMARK_VUFORIA_",
env_file=Path("vuforia_secrets.env"),
extra="allow",
)


@dataclass(frozen=True)
class VuMarkVuforiaDatabase:
"""Credentials for the VuMark generation API."""

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
def vuforia_database() -> VuforiaDatabase:
"""Return VWS credentials from environment variables."""
Expand Down Expand Up @@ -64,3 +90,16 @@ def inactive_database() -> VuforiaDatabase:
client_secret_key=settings.client_secret_key,
state=States.PROJECT_INACTIVE,
)


@pytest.fixture
def vumark_vuforia_database() -> VuMarkVuforiaDatabase:
"""Return VuMark VWS credentials from environment variables."""
settings = _VuMarkVuforiaDatabaseSettings.model_validate(obj={})

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,
)
Loading