From 6df9b7d3063e81aa08bf8d4f276d5f8244bb99c9 Mon Sep 17 00:00:00 2001 From: Timothy Brown Date: Sun, 28 Jun 2026 20:45:51 -0400 Subject: [PATCH] test(main): assert routes via OpenAPI schema and url_path_for MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FastAPI >=0.138 no longer flattens included routers into app.routes; they become opaque _IncludedRouter wrappers without a .path attribute, so `[r.path for r in app.routes]` raised AttributeError. CI resolves deps fresh from PyPI, so it picked up FastAPI 0.138.x and these two tests started failing (blocking the starlette and pydantic-settings Dependabot PRs). Assert HTTP routes against app.openapi()["paths"] and the WebSocket route via app.url_path_for("websocket_live") — both public, version- stable APIs. Verified passing on FastAPI 0.135.3 (locked) and 0.138.1. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_016xQgiPUb99rkTtZHoFncEh --- backend/tests/test_main.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/backend/tests/test_main.py b/backend/tests/test_main.py index bea48e3..f0017a2 100644 --- a/backend/tests/test_main.py +++ b/backend/tests/test_main.py @@ -5,7 +5,10 @@ class TestAppRoutes: def test_api_routes_registered(self): from app.main import app - paths = [r.path for r in app.routes] + # FastAPI >=0.138 stopped flattening included routers into app.routes + # (they become opaque _IncludedRouter wrappers without a .path), so we + # assert against the OpenAPI schema, which enumerates every HTTP path. + paths = app.openapi()["paths"] assert "/api/v1/observations/latest" in paths assert "/api/v1/observations" in paths assert "/api/v1/stations" in paths @@ -20,8 +23,9 @@ def test_api_routes_registered(self): def test_websocket_route_registered(self): from app.main import app - paths = [r.path for r in app.routes] - assert "/ws/live" in paths + # WebSocket routes are absent from the OpenAPI schema; url_path_for + # resolves the route by name and raises if it is not registered. + assert app.url_path_for("websocket_live") == "/ws/live" def test_cors_middleware_present(self): from app.main import app