Introduce consumer-driven contract testing between nextcloud-mcp-server and the astrolabe Nextcloud app, published to the homelab Pact Broker and verified in CI. - pact-python dev dep + `contract` pytest marker - tests/contract/test_astrolabe_credentials_consumer.py: consumer pact for the background-sync *status* call (provisioned -> has_background_access:true, sync_type:"app_password", integer provisioned_at; unprovisioned -> false/null) - tests/contract/test_mcp_provider_verification.py: env-gated Verifier harness for this server's /api/v1/* provider role (provider-state handlers stubbed pending astrolabe's published pacts) - .github/workflows/pact.yml: join tailnet -> publish pacts -> provider verify -> can-i-deploy; broker steps skip when PACT_BROKER is unset (forks) - docs/ADR-029-pact-contract-testing.md Fix astrolabe_client.get_background_sync_status: it previously read a non-existent `app_password` field (always reporting no-access). Rewrite it to read the real status contract (has_background_access / sync_type / provisioned_at) and drop the unsatisfiable get_user_app_password. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
49 lines
1.4 KiB
Python
49 lines
1.4 KiB
Python
"""Fixtures shared by the Pact contract tests (ADR-029).
|
|
|
|
The consumer tests build a fresh ``Pact`` per test and merge their interaction
|
|
into a single pact file under ``tests/contract/pacts/``. A session-scoped
|
|
autouse fixture wipes that directory once at the start of a run so merged pacts
|
|
never accumulate stale interactions across runs (``write_file(overwrite=False)``
|
|
merges into whatever is already on disk).
|
|
"""
|
|
|
|
import shutil
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from pact import Pact
|
|
|
|
# Pact participant names. These MUST match the names used on the astrolabe side
|
|
# and in the broker, so keep them in sync with the astrolabe repo's pact tests.
|
|
CONSUMER = "nextcloud-mcp-server"
|
|
PROVIDER = "astrolabe"
|
|
|
|
PACT_DIR = Path(__file__).parent / "pacts"
|
|
|
|
|
|
@pytest.fixture(scope="session", autouse=True)
|
|
def _clean_pact_dir():
|
|
"""Start each session from an empty pacts directory."""
|
|
if PACT_DIR.exists():
|
|
shutil.rmtree(PACT_DIR)
|
|
PACT_DIR.mkdir(parents=True, exist_ok=True)
|
|
yield
|
|
|
|
|
|
@pytest.fixture
|
|
def pact_dir() -> Path:
|
|
"""Directory the generated pact files are written to."""
|
|
return PACT_DIR
|
|
|
|
|
|
@pytest.fixture
|
|
def consumer_pact():
|
|
"""A fresh Pact (consumer=nextcloud-mcp-server, provider=astrolabe).
|
|
|
|
The interaction added by each test is merged into the shared pact file on
|
|
teardown.
|
|
"""
|
|
pact = Pact(CONSUMER, PROVIDER).with_specification("V4")
|
|
yield pact
|
|
pact.write_file(PACT_DIR, overwrite=False)
|