Spaces:
Build error
Build error
| """Guard against re-introducing the stale `tracker` from-import. | |
| `app.core.usage_tracker.tracker` starts as None and is rebound by | |
| `init_tracker()` during the lifespan. `from app.core.usage_tracker import | |
| tracker` copies the *binding* -- None -- at import time, and the later | |
| rebinding never reaches the importer. Five modules did this: main.py | |
| crashed the Render deploy with "'NoneType' object has no attribute | |
| 'warm_up'", routes_admin's 20 unguarded uses would have 500'd, and | |
| routes_incidents/payments/users each had a `if tracker` guard that could | |
| only ever take the None branch -- so usage went unmetered and signup and | |
| checkout were permanently disabled, silently. | |
| This is a source-level check on purpose. Reproducing the bug at runtime | |
| needs a real UsageTracker (Postgres, pepper) and would only cover the | |
| modules the test happened to import; parsing every module catches the | |
| next one too, and costs nothing. | |
| """ | |
| import ast | |
| import re | |
| import pathlib | |
| import pytest | |
| APP = pathlib.Path(__file__).resolve().parent.parent / "app" | |
| # Names safe to import directly: functions defined in usage_tracker.py | |
| # resolve the module global at call time, so they always see the live | |
| # instance. Only the mutable module-level object itself is unsafe. | |
| UNSAFE_NAMES = {"tracker"} | |
| def _source_files(): | |
| return sorted(p for p in APP.rglob("*.py")) | |
| TESTS = pathlib.Path(__file__).resolve().parent | |
| # `patch("app.api.routes_admin.tracker")` and | |
| # `monkeypatch.setattr(routes_payments, "tracker", ...)` both target an | |
| # attribute that only exists while the broken from-import does. They passed | |
| # for as long as the bug was there and broke the moment it was fixed -- | |
| # and, worse, while the bug was there they were patching a name the route | |
| # code was already reading as None, so they proved nothing. | |
| # | |
| # conftest.py has always done the right thing (`app.core.usage_tracker | |
| # .tracker = MockTracker()`), which is why this is the correct target: one | |
| # patch on the defining module reaches every consumer. | |
| _BAD_PATCH_TARGETS = re.compile( | |
| r"""["']app\.api\.routes_\w+\.tracker["']""" | |
| r"""|setattr\(\s*routes_\w+\s*,\s*["']tracker["']""" | |
| ) | |
| def _test_files(): | |
| """Every test module but this one. | |
| The check is textual, so this file trips it on the comment above that | |
| quotes the bad forms. Excluding self rather than contorting the regex | |
| keeps the pattern readable and the examples literal -- and the cost is | |
| only that this file cannot police itself, which it has no reason to. | |
| """ | |
| here = pathlib.Path(__file__).resolve() | |
| return sorted(p for p in TESTS.glob("test_*.py") if p.resolve() != here) | |
| def test_no_test_patches_tracker_on_a_route_module(path): | |
| src = path.read_text(encoding="utf-8") | |
| for lineno, line in enumerate(src.splitlines(), start=1): | |
| assert not _BAD_PATCH_TARGETS.search(line), ( | |
| f"{path.name}:{lineno} patches `tracker` on a route module. Route " | |
| "modules reference `usage_tracker.tracker` and hold no binding of " | |
| 'their own -- patch "app.core.usage_tracker.tracker" instead, ' | |
| "which is what conftest.py already does." | |
| ) | |
| def test_tracker_is_never_imported_by_name(path): | |
| tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) | |
| for node in ast.walk(tree): | |
| if not isinstance(node, ast.ImportFrom): | |
| continue | |
| if node.module != "app.core.usage_tracker": | |
| continue | |
| offenders = sorted( | |
| {a.name for a in node.names} & UNSAFE_NAMES | |
| ) | |
| assert not offenders, ( | |
| f"{path.name}:{node.lineno} imports {offenders} by name from " | |
| "app.core.usage_tracker. That binding is None at import time and " | |
| "init_tracker() will not update it. Use `from app.core import " | |
| "usage_tracker` and reference `usage_tracker.tracker` instead." | |
| ) | |