Skip to content
This repository was archived by the owner on Oct 30, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
95173b9
Package shared fixtures and database-summary contracts separately
adamtheturtle Oct 28, 2026
0aa33b0
Package target-management API contracts
adamtheturtle Oct 28, 2026
bf286dd
Package the shared image-query contract tests
adamtheturtle Oct 28, 2026
e8c2318
Package recognition-report and VuMark API contracts
adamtheturtle Oct 28, 2026
0ac00ff
Support workspace packages in Docker and documentation builds
adamtheturtle Oct 28, 2026
eb1d93e
Package the shared Model Target API contracts
adamtheturtle Oct 28, 2026
e11c64e
Package the shared HTTP header and invalid-request contracts
adamtheturtle Oct 28, 2026
1aaad50
Use supported wording in package documentation
adamtheturtle Oct 28, 2026
ce5102b
Merge branch 'codex/contract-package-foundation-20261028' into codex/…
adamtheturtle Oct 28, 2026
036245e
Merge branch 'codex/contract-package-foundation-20261028' into codex/…
adamtheturtle Oct 28, 2026
a1043fc
Merge branch 'codex/contract-package-foundation-20261028' into codex/…
adamtheturtle Oct 28, 2026
c326688
Merge branch 'codex/contract-package-foundation-20261028' into codex/…
adamtheturtle Oct 28, 2026
4f379e5
Merge branch 'codex/contract-package-foundation-20261028' into codex/…
adamtheturtle Oct 28, 2026
4f6e2bc
Replace the contract package shell harness with pytest
adamtheturtle Oct 28, 2026
e0a9e44
Merge branch 'codex/contract-package-foundation-20261028' into codex/…
adamtheturtle Oct 28, 2026
8367870
Merge branch 'codex/contract-package-foundation-20261028' into codex/…
adamtheturtle Oct 28, 2026
fdeda0e
Merge branch 'codex/contract-package-foundation-20261028' into codex/…
adamtheturtle Oct 28, 2026
ef68045
Merge branch 'codex/contract-package-foundation-20261028' into codex/…
adamtheturtle Oct 28, 2026
28468c5
Merge branch 'codex/contract-package-foundation-20261028' into codex/…
adamtheturtle Oct 28, 2026
37c1011
Merge pull request #3807 from VWS-Python/codex/package-query-contract…
adamtheturtle Oct 28, 2026
5179b7e
Merge pull request #3808 from VWS-Python/codex/package-target-contrac…
adamtheturtle Oct 28, 2026
1d5ebbc
Merge pull request #3809 from VWS-Python/codex/package-report-vumark-…
adamtheturtle Oct 28, 2026
88b08d5
Merge pull request #3810 from VWS-Python/codex/package-model-target-c…
adamtheturtle Oct 28, 2026
0195b38
Merge pull request #3811 from VWS-Python/codex/package-http-contracts…
adamtheturtle Oct 28, 2026
2fe1760
Document the complete installed API contract suite
adamtheturtle Oct 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
29 changes: 29 additions & 0 deletions .github/workflows/contract-package.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
name: Contract package

on:
push:
branches: [main]
pull_request: {}
workflow_dispatch: {}

permissions:
contents: read

jobs:
consumer:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: Install uv
uses: astral-sh/setup-uv@v10.3.2
- name: Verify installed contracts through the Flask adapter
run: >-
uv run --group=dev pytest
--confcutdir=contract-tests/consumer
-c contract-tests/consumer/pytest.ini
contract-tests/consumer/test_installed_suite.py
env:
UV_PYTHON: '3.15'
5 changes: 5 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
recursive-include docs *.md
include contract-tests/pyproject.toml contract-tests/README.rst contract-tests/LICENSE
recursive-include contract-tests/src *.py py.typed
recursive-include contract-tests/consumer *.py *.ini
include test-backends/pyproject.toml test-backends/README.rst test-backends/LICENSE
recursive-include test-backends/src *.py py.typed
28 changes: 0 additions & 28 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,16 @@

from doctest import ELLIPSIS

import pytest
from beartype import beartype
from sybil import Sybil
from sybil.parsers.rest import (
DocTestParser,
PythonCodeBlockParser,
)

from tests.mock_vws.utils.retries import TRANSIENT_VWS_EXCEPTIONS

pytest_collect_file = Sybil(
parsers=[
DocTestParser(optionflags=ELLIPSIS),
PythonCodeBlockParser(),
],
patterns=["*.rst", "*.py"],
).pytest()


@beartype
@pytest.hookimpl(optionalhook=True)
def pytest_set_filtered_exceptions() -> tuple[type[Exception], ...]:
"""Return exceptions to retry on.

This is for ``pytest-retry``.
The configuration for retries is in ``pyproject.toml``.

``pytest-retry`` treats this as an allowlist: a test which fails
with anything else is not retried, whatever ``retries`` is set to.
That is deliberate, because widening it to ``AssertionError`` would
retry every failing assertion in the suite ten times.

The consequence is that a test which turns a response into an
``AssertionError`` itself, rather than letting an exception out of
the client, is never retried here. The Model Target Web API tests
assert on ``requests.Response`` objects and so are in that
position; they get their own, narrower policy, applied before the
assertion, in
:py:mod:`tests.mock_vws.utils.model_target_retries`.
"""
return TRANSIENT_VWS_EXCEPTIONS
21 changes: 21 additions & 0 deletions contract-tests/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2025 Adam Dangoor

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
54 changes: 54 additions & 0 deletions contract-tests/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
VWS mock contract tests
=======================

This project contains shared fixtures, helpers and contracts for the Vuforia Web Services and Model Target APIs.
It is a development dependency for projects which implement the Vuforia APIs.

Build and install
-----------------

Build all workspace wheels from the repository root:

.. code-block:: console

$ uv build --all-packages --wheel --out-dir dist
$ uv pip install dist/*.whl

The initial package version is ``0.0.0``.
Install the suite, core library and built-in adapter package from the same repository commit.
For Git installations, use the ``contract-tests`` and ``test-backends`` subdirectories at that commit.

Run the installed suite from outside the source checkout:

.. code-block:: console

$ python -m pytest \
-p vws_mock_contract_tests.fixtures.plugin \
-p vws_mock_test_backends.vuforia_backends \
--pyargs vws_mock_contract_tests \
--skip-backend=real

The shared plugin provides clients, prepared requests and the retry allowlist.
The adapter plugin supplies service URLs, credentials, capabilities and setup fixtures.
Public setup fixtures yield ``None`` once the service is ready and keep backend selection internal.
Another repository can supply its own explicitly loaded adapter plugin.
The shared package has no dependency on the mock implementation or Flask.

Core CI compatibility
---------------------

Import-only modules under ``tests/mock_vws`` preserve the existing CI selectors.
The assertions have one source in ``vws_mock_contract_tests``.
Core coverage measures the packaged source and omits the forwarding modules.
The root test configuration avoids collecting the packaged tests a second time.

The consumer integration test builds wheels and checks them in a fresh environment:

.. code-block:: console

$ uv run --group=dev pytest \
--confcutdir=contract-tests/consumer \
-c contract-tests/consumer/pytest.ini \
contract-tests/consumer/test_installed_suite.py

The consumer's dependency and lifecycle checks are explicit pytest plugins.
1 change: 1 addition & 0 deletions contract-tests/consumer/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Integration checks for an independent package consumer."""
20 changes: 20 additions & 0 deletions contract-tests/consumer/contract_checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Check the installed suite's adapter and import boundaries."""

import sys

import pytest

__all__ = ["pytest_sessionfinish"]


@pytest.fixture(autouse=True)
def ready(request: pytest.FixtureRequest) -> None:
"""Check that public setup fixtures yield None."""
for name in ("verify_mock_vuforia", "mock_only_vuforia"):
if name in request.fixturenames:
assert request.getfixturevalue(argname=name) is None


def pytest_sessionfinish() -> None:
"""Keep the core repository's tests outside the consumer process."""
assert not any(name.startswith("tests.mock_vws") for name in sys.modules)
13 changes: 13 additions & 0 deletions contract-tests/consumer/dependency_checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""Check that collecting the shared suite needs no implementation."""

import importlib.util
import sys

__all__ = ["pytest_sessionstart"]


def pytest_sessionstart() -> None:
"""Reject implementation dependencies before collection begins."""
assert importlib.util.find_spec(name="mock_vws") is None
assert importlib.util.find_spec(name="flask") is None
assert not any(name.startswith("tests.mock_vws") for name in sys.modules)
1 change: 1 addition & 0 deletions contract-tests/consumer/pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[pytest]
98 changes: 98 additions & 0 deletions contract-tests/consumer/test_installed_suite.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""Exercise built wheels from a separate consumer environment."""

import os
import shutil
import subprocess
import sys
from pathlib import Path

import pytest


def _run(*, args: list[str | Path], cwd: Path) -> None:
"""Run a local tool, failing the test if it fails."""
# Commands use a local executable and fixed options or built wheel paths.
_ = subprocess.run(args=args, cwd=cwd, check=True) # noqa: S603


def test_installed_suite(
*, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Collect without an implementation, then run the Flask adapter."""
consumer = Path(__file__).resolve().parent
repository = consumer.parent.parent
dist = tmp_path / "dist"
environment = tmp_path / "venv"
uv = shutil.which(cmd="uv")
assert uv is not None
monkeypatch.delenv(name="PYTHONPATH", raising=False)

_run(
args=[uv, "build", "--all-packages", "--wheel", "--out-dir", dist],
cwd=repository,
)
_run(
args=[uv, "venv", "--python", sys.executable, environment],
cwd=tmp_path,
)
python = environment / (
"Scripts/python.exe" if sys.platform == "win32" else "bin/python"
)
suite = next(dist.glob(pattern="vws_mock_contract_tests-*.whl"))
_run(args=[uv, "pip", "install", "--python", python, suite], cwd=tmp_path)
for filename in (
"pytest.ini",
"dependency_checks.py",
"contract_checks.py",
):
_ = shutil.copyfile(src=consumer / filename, dst=tmp_path / filename)

_run(
args=[
python,
"-m",
"pytest",
"-p",
"dependency_checks",
"-p",
"vws_mock_contract_tests.fixtures.plugin",
"--pyargs",
"vws_mock_contract_tests",
"--collect-only",
"-q",
],
cwd=tmp_path,
)
_run(
args=[
uv,
"pip",
"install",
"--python",
python,
*dist.glob(pattern="*.whl"),
],
cwd=tmp_path,
)
_run(
args=[
python,
"-m",
"pytest",
"-p",
"contract_checks",
"-p",
"vws_mock_contract_tests.fixtures.plugin",
"-p",
"vws_mock_test_backends.vuforia_backends",
"--pyargs",
os.environ.get(
key="CONTRACT_TEST_TARGET",
default="vws_mock_contract_tests.database_summary",
),
"--skip-backend=real",
"--skip-backend=mock",
"-q",
],
cwd=tmp_path,
)
38 changes: 38 additions & 0 deletions contract-tests/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[build-system]
build-backend = "setuptools.build_meta"
requires = [ "setuptools" ]

[project]
name = "vws-mock-contract-tests"
version = "0.0.0"
description = "Shared Vuforia Web Services API contract tests."
readme = "README.rst"
license = "MIT"
license-files = [ "LICENSE" ]
authors = [ { name = "Adam Dangoor", email = "adamdangoor@gmail.com" } ]
requires-python = ">=3.15"
classifiers = [
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.15",
]
dependencies = [
"beartype>=0.23.1",
"dirty-equals>=0.11",
"freezegun>=1.5.5",
"pillow>=12.2",
"pydantic>=2.6",
"pytest>=9.1.1",
"pytest-retry>=1.7",
"requests>=2.32.3",
"tenacity>=9.2.1",
"tzdata; sys_platform>='win32'",
"urllib3>=2.8",
"vws-auth-tools>=2024.7.12",
"vws-python>=2026.10.9",
"vws-test-fixtures>=2026.10.8",
]
urls.Source = "https://github.com/VWS-Python/vws-python-mock"

[tool.setuptools]
packages.find.where = [ "src" ]
package-data.vws_mock_contract_tests = [ "py.typed" ]
1 change: 1 addition & 0 deletions contract-tests/src/vws_mock_contract_tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Support for shared Vuforia API contract tests."""
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Add target tests."""
12 changes: 12 additions & 0 deletions contract-tests/src/vws_mock_contract_tests/add_target/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Fixtures scoped to this group of tests."""

import pytest


@pytest.fixture(autouse=True)
def fixture_verify_backend(
*,
verify_mock_vuforia: None,
) -> None:
"""Set up the backend for every contract test in this directory."""
del verify_mock_vuforia
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from vws import VWS
from vws.response import Response

from tests.mock_vws.utils.assertions import (
from vws_mock_contract_tests.utils.assertions import (
assert_vws_response,
)

Expand Down
Loading
Loading