"""Auto-discover all routes and verify they're covered in permission_config. This test runs without a server — it inspects the FastAPI app directly. When a new route is added, this test will FAIL until it's added to permission_config.ROUTE_PERMISSIONS. """ from tests.permission_config import ROUTE_PERMISSIONS, DYNAMIC_ROUTES, EXCLUDED_ROUTES def collect_app_routes(): """Return set of (method, path) from the main app and all routers.""" from app.main import app routes = set() for route in app.routes: if hasattr(route, "methods") and hasattr(route, "path"): for method in route.methods: method = method.upper() if method in ("GET", "POST", "PUT", "DELETE", "PATCH"): routes.add((method, route.path)) return routes def test_all_routes_covered(): """Every app route must have a permission entry.""" app_routes = collect_app_routes() - EXCLUDED_ROUTES config_routes = set(ROUTE_PERMISSIONS.keys()) missing = app_routes - config_routes assert not missing, ( f"Routes missing from permission_config.ROUTE_PERMISSIONS:\n" + "\n".join(f" {m} {p}" for m, p in sorted(missing)) + "\n\nAdd them to py_service/tests/permission_config.py" ) def test_no_stale_permissions(): """No stale entries in permission config.""" app_routes = collect_app_routes() - EXCLUDED_ROUTES config_routes = set(ROUTE_PERMISSIONS.keys()) stale = config_routes - app_routes assert not stale, ( f"ROUTE_PERMISSIONS has stale entries (route no longer exists):\n" + "\n".join(f" {m} {p}" for m, p in sorted(stale)) ) def test_dynamic_routes_also_covered(): """All DYNAMIC_ROUTES must also be in ROUTE_PERMISSIONS.""" for entry in DYNAMIC_ROUTES: assert entry in ROUTE_PERMISSIONS, ( f"{entry[0]} {entry[1]} is in DYNAMIC_ROUTES but missing from ROUTE_PERMISSIONS" ) def test_all_roles_valid(): """Allowed roles must be one of: owner, engineer, technician, anonymous, authenticated.""" valid_roles = {"owner", "engineer", "technician", "anonymous", "authenticated"} for (method, path), allowed_roles in ROUTE_PERMISSIONS.items(): for role in allowed_roles: assert role in valid_roles, ( f"{method} {path}: invalid role '{role}' (valid: {valid_roles})" )