import ast from pathlib import Path def test_wndb_editor_routes_are_synchronous() -> None: """Sync psycopg-backed routes must let FastAPI schedule them in its thread pool.""" endpoints = Path(__file__).resolve().parents[2] / "app" / "api" / "v1" / "endpoints" route_roots = [endpoints / "network", endpoints / "components"] async_routes: list[str] = [] for root in route_roots: for path in root.glob("*.py"): tree = ast.parse(path.read_text(encoding="utf-8")) for node in tree.body: if not isinstance(node, ast.AsyncFunctionDef): continue is_route = any( isinstance(decorator, ast.Call) and isinstance(decorator.func, ast.Attribute) and isinstance(decorator.func.value, ast.Name) and decorator.func.value.id == "router" for decorator in node.decorator_list ) if is_route: async_routes.append(f"{path.name}:{node.lineno}:{node.name}") assert async_routes == [] def test_simulation_routes_are_synchronous() -> None: """EPANET and synchronous WNDB work must run in FastAPI's thread pool.""" path = ( Path(__file__).resolve().parents[2] / "app" / "api" / "v1" / "endpoints" / "simulation.py" ) tree = ast.parse(path.read_text(encoding="utf-8")) async_routes = [ node.name for node in tree.body if isinstance(node, ast.AsyncFunctionDef) and any( isinstance(decorator, ast.Call) and isinstance(decorator.func, ast.Attribute) and isinstance(decorator.func.value, ast.Name) and decorator.func.value.id == "router" for decorator in node.decorator_list ) ] assert async_routes == []