11from __future__ import annotations
22
33import os
4+ import re
45from pathlib import Path
56
67import pytest
@@ -21,6 +22,20 @@ def _read_planning(path: str) -> str:
2122 return (_planning_root () / path ).read_text (encoding = "utf-8" )
2223
2324
25+ def _workflow_job_block (workflow : str , job_name : str ) -> str :
26+ match = re .search (
27+ rf"^ { re .escape (job_name )} :\n(.*?)(?=^ [A-Za-z0-9_-]+:\n|\Z)" ,
28+ workflow ,
29+ flags = re .MULTILINE | re .DOTALL ,
30+ )
31+ assert match is not None , f"missing workflow job { job_name } "
32+ return match .group (1 )
33+
34+
35+ def _workflow_run_steps (job_block : str ) -> list [str ]:
36+ return re .findall (r"^\s+- run: (.+)$" , job_block , flags = re .MULTILINE )
37+
38+
2439def test_planning_contract_uses_repo_fixture_fallback_by_default (
2540 monkeypatch : pytest .MonkeyPatch ,
2641) -> None :
@@ -112,7 +127,9 @@ def test_technical_and_ux_docs_match_current_cli_and_workflow_contracts() -> Non
112127 technical = _read_repo ("analysis/technical/overview.md" )
113128 ux = _read_repo ("analysis/ux/cli-reference.md" )
114129
115- assert "CI (`.github/workflows/ci.yml`) runs `cargo fmt --check`, `cargo clippy -- -D warnings`, and `cargo test`." in technical
130+ assert "CI (`.github/workflows/ci.yml`) runs two parallel jobs" in technical
131+ assert "`check` (`cargo fmt --check`, `cargo clippy -- -D warnings`, `cargo test`)" in technical
132+ assert '`contracts` (`uv sync --extra dev`, `uv run pytest tests/ -v --mcp-cmd "biomcp serve"`, `uv run mkdocs build --strict`)' in technical
116133 assert "The spec suite is repo-local executable documentation; no GitHub workflow currently runs `make spec`." in technical
117134 assert "Contract smoke checks run in `.github/workflows/contracts.yml`" in technical
118135 assert "release validation runs `pytest tests/` and `mkdocs build --strict`" in technical
@@ -136,6 +153,31 @@ def test_technical_and_ux_docs_match_current_cli_and_workflow_contracts() -> Non
136153 assert "biomcp serve-sse → removed compatibility command; use `biomcp serve-http`" in ux
137154
138155
156+ def test_pull_request_contract_gate_matches_release_validation () -> None :
157+ ci = _read_repo (".github/workflows/ci.yml" )
158+ release = _read_repo (".github/workflows/release.yml" )
159+ contracts_smoke = _read_repo (".github/workflows/contracts.yml" )
160+ expected_contract_runs = [
161+ "uv sync --extra dev" ,
162+ 'uv run pytest tests/ -v --mcp-cmd "biomcp serve"' ,
163+ "uv run mkdocs build --strict" ,
164+ ]
165+
166+ ci_contracts = _workflow_job_block (ci , "contracts" )
167+ release_validate = _workflow_job_block (release , "validate" )
168+
169+ assert 'python-version: "3.12"' in ci_contracts
170+ assert 'python-version: "3.12"' in release_validate
171+ assert _workflow_run_steps (ci_contracts ) == expected_contract_runs
172+ assert _workflow_run_steps (release_validate )[- 3 :] == expected_contract_runs
173+
174+ assert "name: Contract Smoke Tests" in contracts_smoke
175+ assert 'cron: "0 6 * * *"' in contracts_smoke
176+ assert "workflow_dispatch:" in contracts_smoke
177+ assert "continue-on-error: true" in contracts_smoke
178+ assert "- run: bash scripts/contract-smoke.sh" in contracts_smoke
179+
180+
139181def test_runtime_contract_docs_and_scripts_align_on_release_target () -> None :
140182 staging_demo = _read_repo ("analysis/technical/staging-demo.md" )
141183 runbook = _read_repo ("RUN.md" )
@@ -172,6 +214,7 @@ def test_runtime_contract_docs_and_scripts_align_on_release_target() -> None:
172214 assert "tests/test_mcp_http_surface.py" in runbook
173215 assert "tests/test_mcp_http_transport.py" in runbook
174216 assert "make spec" in runbook
217+ assert "make test-contracts" in runbook
175218 assert "docs/user-guide/cli-reference.md" in runbook
176219 assert "docs/reference/mcp-server.md" in runbook
177220
0 commit comments