diff --git a/pyproject.toml b/pyproject.toml index 2cbf274..9433f84 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,10 +30,7 @@ dependencies = [ "python-dotenv>=1.0.0", "sqlalchemy>=2.0.0", "alembic>=1.12.0", - # 0.137.0 regressed include_router: routed endpoints no longer appear in - # app.routes (they still serve, but introspection breaks). Pin below it - # until a fixed release lands. See test_app_factory metrics route checks. - "fastapi>=0.100.0,<0.137.0", + "fastapi>=0.100.0", "starlette>=0.40.0,<1.0.0", "uvicorn[standard]>=0.23.0", "paho-mqtt>=2.0.0", diff --git a/tests/test_api/test_app_factory.py b/tests/test_api/test_app_factory.py index 3d37157..e75a098 100644 --- a/tests/test_api/test_app_factory.py +++ b/tests/test_api/test_app_factory.py @@ -27,6 +27,18 @@ def clean_env(monkeypatch): return monkeypatch +def _served_paths(app): + """Return the set of paths the app actually serves. + + FastAPI 0.137 refactored ``include_router`` to keep included routers as + nested objects instead of flattening their routes into ``app.routes``, so + iterating ``app.routes`` no longer surfaces routed endpoints like + ``/metrics``. The OpenAPI schema is the stable, version-independent way to + introspect mounted paths (and it resolves router prefixes correctly). + """ + return set(app.openapi()["paths"]) + + def test_factory_reads_database_and_redis_from_env(clean_env): """Workers must pick up the real DB/Redis config from env, not the hardcoded create_app defaults.""" @@ -71,13 +83,11 @@ def test_factory_metrics_enabled_via_env(clean_env): """METRICS_ENABLED=true mounts the /metrics endpoint.""" clean_env.setenv("METRICS_ENABLED", "true") app = create_app_from_env() - paths = {getattr(route, "path", None) for route in app.routes} - assert "/metrics" in paths + assert "/metrics" in _served_paths(app) def test_factory_metrics_disabled_via_env(clean_env): """METRICS_ENABLED=false omits the /metrics endpoint.""" clean_env.setenv("METRICS_ENABLED", "false") app = create_app_from_env() - paths = {getattr(route, "path", None) for route in app.routes} - assert "/metrics" not in paths + assert "/metrics" not in _served_paths(app)