Files
geointel/backend/tests/test_frontend_contract_test_style.py
T
JensandClaude Opus 5 3e4e211fad test frontend wiring instead of frontend formatting
19 tests were failing on main. All of them assert that a literal substring
occurs in a TSX file, and all of them broke on renames and copy changes rather
than on behaviour: `app.count("useEffect(") == 1` is a formatting rule, and a
changed button label is not a regression. 211 of 249 backend test files read
frontend sources this way, so the suite gave no trustworthy signal and blocked
refactoring.

tests/frontend_contract.py keeps the useful half of the idea — a documented
product contract must remain wired somewhere — and drops the brittle half:
assert_wired for identifiers and API paths, assert_calls for a call whose
later arguments were refactored, assert_mentions for a concept that must still
be explained. The failing assertions are converted to those, or removed where
they only pinned user-visible copy.

test_frontend_contract_test_style.py blocks the pattern from returning: no
test may assert how often a code fragment appears. Counting list values or
network calls is unaffected.

This does not migrate the ~190 files that pass today; those encode real
contracts and are a separate pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 14:34:13 +02:00

76 lines
2.8 KiB
Python

"""Keep the frontend-contract tests from drifting back to formatting checks.
211 of the backend test files read frontend sources and assert on literal
substrings. That reds the suite on renames and copy changes without proving
anything about behaviour, and it is why the suite was failing on main.
This guard blocks the two patterns that caused the failures: counting
occurrences of a code fragment (a formatting rule) and asserting an exact
number of hook calls. New wiring assertions belong in
``tests/frontend_contract.py``; anything about what a user sees belongs in a
frontend component test, where the rendered output can be checked.
"""
from __future__ import annotations
import re
from pathlib import Path
TESTS_DIR = Path(__file__).resolve().parent
# Counting a quoted *code* fragment — one containing a bracket, an arrow or a
# semicolon — is the pattern to block. Counting values in a list, or counting
# how often a URL was requested, are ordinary behavioural assertions.
COUNT_ASSERTION = re.compile(
r"""assert\s+\w+\.count\(\s*["'][^"']*[(){}=;][^"']*["']\s*\)\s*[=!<>]=""",
)
# Tests that predate the guard and still count call sites. The list must only
# ever shrink; a new entry means a new formatting test was written.
KNOWN_COUNT_ASSERTIONS: set[str] = set()
def _test_sources() -> list[tuple[Path, str]]:
return [
(path, path.read_text(encoding="utf-8"))
for path in sorted(TESTS_DIR.glob("test_*.py"))
if path.name != Path(__file__).name
]
def test_no_test_counts_occurrences_of_a_source_fragment() -> None:
offenders = {
path.name
for path, source in _test_sources()
if COUNT_ASSERTION.search(source)
}
unexpected = sorted(offenders - KNOWN_COUNT_ASSERTIONS)
assert not unexpected, (
"These tests assert on how often a code fragment appears, which is a "
f"formatting rule rather than a contract: {unexpected}. Use "
"tests/frontend_contract.py to assert on wiring instead."
)
def test_the_known_offender_list_does_not_grow_stale() -> None:
"""A file listed as an exception must still exist and still offend."""
offenders = {
path.name
for path, source in _test_sources()
if COUNT_ASSERTION.search(source)
}
stale = sorted(KNOWN_COUNT_ASSERTIONS - offenders)
assert not stale, f"Remove these from KNOWN_COUNT_ASSERTIONS; they are clean now: {stale}"
def test_frontend_contract_helpers_are_available() -> None:
from tests.frontend_contract import assert_calls, assert_mentions, assert_wired
source = "const total = analyzeSelection(bbox, areaIdForSelection(bbox))"
assert_wired(source, "analyzeSelection")
assert_calls(source, "analyzeSelection", first_argument="bbox")
assert_mentions(source, "AREAIDFORSELECTION")