Files
geointel/backend/tests/test_frontend_contract_test_style.py
Jens faeb58ef6d
GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
Initial public release
2026-08-31 21:56:53 +02:00

121 lines
4.4 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")
FRONTEND_READ = re.compile(
r'(?P<var>\w+)\s*=\s*\(?\s*(?:ROOT|root|REPO_ROOT)\s*/\s*'
r'(?P<path>"[^"]+"(?:\s*/\s*"[^"]+")*)\s*\)?\.read_text\('
)
NEGATIVE_ASSERT = re.compile(r"assert\s+[^\n]*not in\s+(\w+)")
def _feature_owner() -> dict[str, str]:
from tests.frontend_contract import FEATURE_SOURCES
return {source: feature for feature, sources in FEATURE_SOURCES.items() for source in sources}
def test_a_positive_contract_reads_the_feature_not_one_file() -> None:
"""Moving code between sibling modules must not red the suite.
A single-file read is right for a *negative* contract — "this component
performs no transport" is a statement about that file, and widening it
would quietly weaken the check. For a positive contract it pins the
contract to whichever file happens to hold it today.
"""
owner = _feature_owner()
offenders: list[str] = []
for path, source in _test_sources():
if "frontend" not in source:
continue
negatives = set(NEGATIVE_ASSERT.findall(source))
for match in FRONTEND_READ.finditer(source):
if match.group("var") in negatives:
continue
joined = re.sub(r'["\s/]+', "/", match.group("path")).strip("/")
if "frontend/src/" not in joined:
continue
relative = joined.split("frontend/src/", 1)[1]
if relative in owner:
offenders.append(f"{path.name}: {match.group('var')} -> {relative}")
assert not offenders, (
"These read one file of a multi-module feature for a positive contract. "
f"Use read_feature() from tests/frontend_contract.py instead: {sorted(offenders)}"
)