v1.5.4: test infrastructure + bug fixes + CI/CD + deploy gate
- 6 new test files: route discovery, strict permissions, role switch, CRUD operations, 500 error protection - permission_config.py: central permission definitions for all 110+ routes - Bug fixes: is_active checkbox in users_edit, int(id) 500 crash, missing validation in users_create/users_delete - Pre-commit hooks: lint + static routing tests - GitHub Actions CI: full pytest suite on push - Deploy gate: pytest runs before deploy, aborts on failure - All 3 deploy scripts: --skip-tests flag support
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
"""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})"
|
||||
)
|
||||
Reference in New Issue
Block a user