nas-burnin/tests/test_routes_resolution.py
Brandon Walter 8033161efb
Some checks are pending
Security scan / pip-audit (push) Waiting to run
Security scan / bandit (push) Waiting to run
Security scan / gitleaks (push) Waiting to run
Security scan / mypy (push) Waiting to run
fix: address Codex routes-split follow-up review (1.0.0-39)
Three low-severity findings from Codex on the 1.0.0-37 split:

1. Trim dead package-level imports in routes/__init__.py — only
   `poller` was actually used; auth/burnin/mailer/settings_store
   were the exact shadowing footgun the absolute sub-router
   imports work around. Reword the comment block to match.

2. Thread `operator` through smart_start + smart_cancel.
   Previously the JS client sent it but the server ignored it;
   add audit_events rows (smart_test_start / smart_test_cancel)
   so the field is actually meaningful.

3. New tests/test_routes_resolution.py — guards two historical
   regressions: /api/v1/burnin/export.csv must register before
   /{job_id} (FastAPI int-coerce 422 trap) and the mailer
   back-compat shim `from app.routes import _fetch_drives_for_template`
   must keep importing. Plus a sub-router enumeration test that
   catches missed include_router calls in future splits.
2026-05-03 15:04:38 -05:00

79 lines
3 KiB
Python

"""Route-resolution invariants for the routes/ package.
Guards two historical regressions Codex flagged were untested:
1. /api/v1/burnin/export.csv must resolve to the CSV export, not to
/api/v1/burnin/{job_id} with int("export.csv") → 422. FastAPI's
path matching tries declarations in registration order, so the
literal must be declared before the parameterized route.
2. app.mailer reaches into app.routes for _fetch_drives_for_template
(back-compat from before the routes/ split). The shim re-export
in app/routes/__init__.py must remain importable.
Run inside the container image so app deps are present.
"""
from __future__ import annotations
import unittest
class TestRouteResolution(unittest.TestCase):
def test_export_csv_declared_before_job_id(self):
"""Route order in burnin.py: /export.csv must come before
/{job_id} or FastAPI will int-coerce 'export.csv' and 422.
"""
from app.routes import burnin as burnin_routes
paths = [r.path for r in burnin_routes.router.routes]
self.assertIn("/api/v1/burnin/export.csv", paths)
self.assertIn("/api/v1/burnin/{job_id}", paths)
self.assertLess(
paths.index("/api/v1/burnin/export.csv"),
paths.index("/api/v1/burnin/{job_id}"),
"/export.csv must be registered before /{job_id} or FastAPI "
"will try to int-coerce 'export.csv' and return 422",
)
def test_mailer_backcompat_shim(self):
"""app.mailer imports _fetch_drives_for_template from app.routes
(NOT app.routes._drives_helpers) — the shim re-export in
routes/__init__.py keeps that working post-split.
"""
from app.routes import _fetch_drives_for_template
self.assertTrue(callable(_fetch_drives_for_template))
def test_all_subrouters_included(self):
"""Sanity check: every sub-router in app.routes.* is wired into
the package-level router.include_router calls. If a future split
adds a new file but forgets the include, this catches it.
"""
import importlib
import pkgutil
import app.routes as routes_pkg
sub_modules = [
name for _, name, _ in pkgutil.iter_modules(routes_pkg.__path__)
if not name.startswith("_") # skip _helpers, _drives_helpers
]
registered_paths = {r.path for r in routes_pkg.router.routes}
for mod_name in sub_modules:
mod = importlib.import_module(f"app.routes.{mod_name}")
sub_router = getattr(mod, "router", None)
self.assertIsNotNone(
sub_router,
f"app.routes.{mod_name} has no `router` attribute",
)
for r in sub_router.routes:
self.assertIn(
r.path, registered_paths,
f"{mod_name}.router has {r.path} but the package "
"router didn't include it",
)
if __name__ == "__main__":
unittest.main()