Skip to content

Commit bb822e0

Browse files
committed
tests/interaction: pin SPEC_BASE_URL and harden unit tests for SPEC_VERSIONS growth
SPEC_BASE_URL was derived from SPEC_VERSIONS[-1], so appending 2026-07-28 to the active axis would silently repoint all 276 source=f"{SPEC_BASE_URL}/..." URLs to the 2026 spec -- including the 85 removed_in entries whose pages no longer exist there. SPEC_BASE_URL is now a pinned literal for 2025-11-25; SPEC_2026_BASE_URL is added for new entries; SPEC_REVISION is dropped. Six compute_cells/cell_id unit tests in test_coverage.py inherited the default SPEC_VERSIONS and would break when it grows; they now pass spec_versions=("2025-11-25",) explicitly. A new invariant pins both base-URL constants. Verified by temporarily flipping SPEC_VERSIONS to dual: test_coverage.py is 30/30 green and SPEC_BASE_URL stays pinned. 504 connect cells unchanged; 100% coverage; pyright/ruff clean.
1 parent ceb48d1 commit bb822e0

3 files changed

Lines changed: 33 additions & 12 deletions

File tree

tests/interaction/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,10 @@ it does not (the rewrite broke something that was correct — fix the rewrite).
137137
### Spec versions and the era axis
138138

139139
`SPEC_VERSIONS` in `_requirements.py` is the ordered tuple of protocol revisions the suite
140-
exercises; `SPEC_REVISION = SPEC_VERSIONS[-1]` is the newest. The `connect` fixture fans out over
141-
`CONNECTABLE_TRANSPORTS × SPEC_VERSIONS`, but the grid is filtered per test:
140+
exercises. `SPEC_BASE_URL` (and `SPEC_2026_BASE_URL`) are pinned literals — not derived from
141+
`SPEC_VERSIONS` — so growing the active axis never repoints existing `source` links. The
142+
`connect` fixture fans out over `CONNECTABLE_TRANSPORTS × SPEC_VERSIONS`, but the grid is
143+
filtered per test:
142144
`pytest_generate_tests` reads the test's stacked `@requirement` marks and calls `compute_cells()`,
143145
which intersects the admissible cells across every cited requirement — a cell survives only if
144146
**all** of the test's requirements admit it.

tests/interaction/_requirements.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,13 @@
4848
SPEC_VERSIONS: tuple[SpecVersion, ...] = ("2025-11-25",)
4949
"""The active spec-version matrix axis, ordered oldest to newest. Every entry must be in KNOWN_PROTOCOL_VERSIONS."""
5050

51-
SPEC_REVISION = SPEC_VERSIONS[-1]
52-
SPEC_BASE_URL = f"https://modelcontextprotocol.io/specification/{SPEC_REVISION}"
51+
SPEC_BASE_URL = "https://modelcontextprotocol.io/specification/2025-11-25"
52+
"""Deep-link base for entries citing the 2025-11-25 revision (the bulk of the manifest). Pinned --
53+
not derived from SPEC_VERSIONS -- so adding a newer revision to the active axis does not silently
54+
repoint existing source URLs."""
55+
56+
SPEC_2026_BASE_URL = "https://modelcontextprotocol.io/specification/2026-07-28"
57+
"""Deep-link base for entries citing the 2026-07-28 revision."""
5358

5459
Transport = Literal["in-memory", "stdio", "streamable-http", "streamable-http-stateless", "sse"]
5560

tests/interaction/test_coverage.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
from tests.interaction._requirements import (
2121
CONNECTABLE_TRANSPORTS,
2222
REQUIREMENTS,
23+
SPEC_2026_BASE_URL,
24+
SPEC_BASE_URL,
2325
SPEC_VERSIONS,
2426
ArmExclusion,
2527
KnownFailure,
@@ -116,6 +118,12 @@ def test_spec_versions_are_known_and_include_latest() -> None:
116118
assert LATEST_PROTOCOL_VERSION in SPEC_VERSIONS
117119

118120

121+
def test_spec_base_urls_are_pinned_to_their_revision() -> None:
122+
"""SPEC_BASE_URL constants are pinned literals, so growing SPEC_VERSIONS cannot repoint existing source links."""
123+
assert SPEC_BASE_URL == "https://modelcontextprotocol.io/specification/2025-11-25"
124+
assert SPEC_2026_BASE_URL == "https://modelcontextprotocol.io/specification/2026-07-28"
125+
126+
119127
def test_connectable_transports_match_connect_factories() -> None:
120128
"""CONNECTABLE_TRANSPORTS and the conftest factory map name exactly the same transports."""
121129
assert set(CONNECTABLE_TRANSPORTS) == set(_FACTORIES)
@@ -260,8 +268,8 @@ def _req(
260268

261269

262270
def test_compute_cells_with_no_requirements_yields_full_grid() -> None:
263-
"""An empty requirement list yields one cell per connectable transport at the single active spec version."""
264-
cells = compute_cells([])
271+
"""With a single-version axis, an empty requirement list yields one cell per connectable transport."""
272+
cells = compute_cells([], spec_versions=("2025-11-25",))
265273
assert [c.id for c in cells] == ["in-memory", "sse", "streamable-http", "streamable-http-stateless"]
266274
assert [c.values for c in cells] == [
267275
(("in-memory", "2025-11-25"),),
@@ -301,7 +309,10 @@ def test_compute_cells_drops_era_locked_transport_outside_its_versions() -> None
301309

302310
def test_compute_cells_honours_arm_exclusion_from_any_stacked_requirement() -> None:
303311
"""An arm exclusion on any stacked requirement drops the matching cell even when other requirements have none."""
304-
cells = compute_cells([_req(), _req(arm_exclusions=(ArmExclusion(reason="requires-session", transport="sse"),))])
312+
cells = compute_cells(
313+
[_req(), _req(arm_exclusions=(ArmExclusion(reason="requires-session", transport="sse"),))],
314+
spec_versions=("2025-11-25",),
315+
)
305316
assert [c.id for c in cells] == ["in-memory", "streamable-http", "streamable-http-stateless"]
306317

307318

@@ -313,7 +324,10 @@ def test_compute_cells_wildcard_arm_exclusion_drops_every_cell() -> None:
313324

314325
def test_compute_cells_marks_known_failure_as_strict_xfail() -> None:
315326
"""A known failure attaches a strict xfail mark to exactly the matching cell and leaves others unmarked."""
316-
cells = compute_cells([_req(known_failures=(KnownFailure(note="broken on sse", transport="sse"),))])
327+
cells = compute_cells(
328+
[_req(known_failures=(KnownFailure(note="broken on sse", transport="sse"),))],
329+
spec_versions=("2025-11-25",),
330+
)
317331
by_id = {c.id: c for c in cells}
318332
assert set(by_id) == {"in-memory", "sse", "streamable-http", "streamable-http-stateless"}
319333
assert by_id["sse"].marks[0].name == "xfail"
@@ -325,21 +339,21 @@ def test_compute_cells_marks_known_failure_as_strict_xfail() -> None:
325339

326340
def test_compute_cells_wildcard_known_failure_marks_every_cell() -> None:
327341
"""A known failure with both transport and spec_version unset marks every emitted cell as strict xfail."""
328-
cells = compute_cells([_req(known_failures=(KnownFailure(note="all broken"),))])
342+
cells = compute_cells([_req(known_failures=(KnownFailure(note="all broken"),))], spec_versions=("2025-11-25",))
329343
assert len(cells) == 4
330344
assert all(c.marks[0].name == "xfail" for c in cells)
331345
assert all(c.marks[0].kwargs == {"reason": "all broken", "strict": True} for c in cells)
332346

333347

334348
def test_compute_cells_ignores_transports_field() -> None:
335349
"""Requirement.transports is descriptive metadata only and does not filter the cell grid."""
336-
cells = compute_cells([_req(transports=("stdio",))])
350+
cells = compute_cells([_req(transports=("stdio",))], spec_versions=("2025-11-25",))
337351
assert [c.id for c in cells] == list(CONNECTABLE_TRANSPORTS)
338352

339353

340354
def test_cell_id_omits_version_when_single_spec_version() -> None:
341-
"""With one active spec version the cell id is just the transport name, keeping today's node ids byte-identical."""
342-
assert cell_id("sse", "2025-11-25") == "sse"
355+
"""With a single-version axis the cell id is just the transport name."""
356+
assert cell_id("sse", "2025-11-25", spec_versions=("2025-11-25",)) == "sse"
343357

344358

345359
def test_cell_id_appends_version_when_multiple_spec_versions() -> None:

0 commit comments

Comments
 (0)