Files
plm-lottery/tests/unit/test_docs_current.py
T
davideandClaude Opus 5 162ceed40f Describe the code the docs actually ship with (B-68)
CLAUDE.md and README still asserted a state the code had moved past:

- JWT "no revocation (B-34)" — token_version implements exactly that
  revocation, and the tv-claim behaviour (including why the deploy did not
  log everyone out) is worth stating instead of denying;
- /report-bug "a placeholder" — it shipped fully implemented and
  translated, with an admin triage section, a reporter-side status view and
  its own audit event; only /guida is still a stub, and /admin has six
  sections now, not five;
- three stale test counts (CLAUDE.md twice, README once);
- a code map missing app/auth/rate_limit.py, app/api/client_ip.py and
  app/api/routes/bug_reports.py;
- README linking flowchart.mmd (the diagrams live in flowchart/), the
  anchor CLAUDE.md#tech-stack-mvp (gone), and describing
  docs/running-the-server.md as "local venv vs. Docker" after B-44 made
  Docker the only supported way to run the server.

The rate-limiting bullet the audit also flagged already reads correctly.

tests/unit/test_docs_current.py pins all of it: the documented counts must
equal what the suite actually collects, the retired claims must stay
retired, the code map must name those modules, and every relative README
link and CLAUDE.md anchor must resolve. None of this is catchable by
reading the code, which is how it drifted in the first place.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 14:33:32 +02:00

92 lines
3.0 KiB
Python

"""B-68: CLAUDE.md and README must describe the code as it is now.
The audit found both files still asserting things the code had moved past —
JWT "no revocation" after token_version implemented exactly that, /report-bug
"a placeholder" after it shipped with admin triage, three stale test counts, a
code map missing three modules, and README links to a file and an anchor that
no longer exist. None of that is catchable by reading the code, so it is
pinned here instead.
"""
import re
import subprocess
import sys
from pathlib import Path
import pytest
_ROOT = Path(__file__).resolve().parents[2]
CLAUDE_MD = (_ROOT / "CLAUDE.md").read_text(encoding="utf-8")
README = (_ROOT / "README.md").read_text(encoding="utf-8")
def _collected_test_count() -> int:
result = subprocess.run(
[sys.executable, "-m", "pytest", "--collect-only", "-q"],
cwd=_ROOT,
capture_output=True,
text=True,
timeout=300,
)
match = re.search(r"(\d+) tests? collected", result.stdout)
assert match, f"could not parse the collection summary:\n{result.stdout[-2000:]}"
return int(match.group(1))
def test_documented_test_counts_match_reality():
actual = _collected_test_count()
documented = [int(n) for n in re.findall(r"(\d+) tests\b", CLAUDE_MD)]
documented += [int(n) for n in re.findall(r"(\d+) unit tests\b", README)]
assert documented, "no test count found in CLAUDE.md or README — did the wording change?"
for count in documented:
assert count == actual, (
f"docs claim {count} tests, the suite collects {actual} — "
"update the counts in CLAUDE.md (twice) and README.md"
)
@pytest.mark.parametrize(
"stale",
[
"no revocation", # token_version implements it (app/auth/dependencies.py)
"`/report-bug` are placeholders", # /report-bug shipped, only /guida is a stub
],
)
def test_claude_md_has_no_stale_claims(stale):
assert stale not in CLAUDE_MD
@pytest.mark.parametrize(
"module",
["rate_limit.py", "client_ip.py", "bug_reports"],
)
def test_code_map_covers_every_package_member(module):
code_map = CLAUDE_MD.split("## Code map", 1)[1].split("## Background tasks", 1)[0]
assert module in code_map
@pytest.mark.parametrize(
"link",
["(flowchart.mmd)", "CLAUDE.md#tech-stack-mvp"],
)
def test_readme_has_no_dead_links(link):
assert link not in README
def test_readme_relative_links_resolve():
for target in re.findall(r"\]\(([^)#]+)(?:#[^)]*)?\)", README):
if target.startswith(("http://", "https://", "mailto:")):
continue
assert (_ROOT / target).exists(), f"README links {target}, which does not exist"
def test_claude_md_anchors_into_itself_resolve():
headings = {
re.sub(r"[^a-z0-9 -]", "", line.lstrip("# ").lower()).replace(" ", "-")
for line in CLAUDE_MD.splitlines()
if line.startswith("#")
}
for anchor in re.findall(r"\(CLAUDE\.md#([a-z0-9-]+)\)", README + CLAUDE_MD):
assert anchor in headings, f"anchor #{anchor} matches no CLAUDE.md heading"