You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

139 lines
4.3 KiB

"""
Tests for issue #10180: mounting sub-applications under APIRouter.
Sub-apps mounted on an APIRouter (with a prefix) must be reachable via the
combined prefix + mount-path after include_router().
"""
from fastapi import APIRouter, FastAPI
from fastapi.testclient import TestClient
def make_sub_app() -> FastAPI:
sub = FastAPI()
@sub.get("/sub")
def read_sub():
return {"message": "Hello World from sub API"}
return sub
# ---------------------------------------------------------------------------
# Router prefix baked into APIRouter(prefix=...) itself
# ---------------------------------------------------------------------------
def test_router_prefix_mount_routing():
"""Request to /api/subapi/sub must return 200 (issue #10180 exact repro)."""
router = APIRouter(prefix="/api")
@router.get("/app")
def read_app():
return {"message": "Hello World from main app"}
router.mount("/subapi", make_sub_app())
app = FastAPI()
app.include_router(router)
client = TestClient(app)
assert client.get("/api/app").status_code == 200
assert client.get("/api/subapi/sub").status_code == 200
assert client.get("/api/subapi/sub").json() == {
"message": "Hello World from sub API"
}
# ---------------------------------------------------------------------------
# Prefix supplied via include_router(prefix=...)
# ---------------------------------------------------------------------------
def test_include_router_prefix_mount_routing():
"""Prefix from include_router() is applied to mounted sub-apps."""
router = APIRouter()
router.mount("/subapi", make_sub_app())
app = FastAPI()
app.include_router(router, prefix="/api")
client = TestClient(app)
assert client.get("/api/subapi/sub").status_code == 200
assert client.get("/api/subapi/sub").json() == {
"message": "Hello World from sub API"
}
# ---------------------------------------------------------------------------
# Combination: both router prefix AND include_router prefix
# ---------------------------------------------------------------------------
def test_combined_prefix_mount_routing():
"""Router prefix and include_router prefix are both applied to mounts."""
router = APIRouter(prefix="/api")
router.mount("/subapi", make_sub_app())
app = FastAPI()
app.include_router(router, prefix="/v1")
client = TestClient(app)
assert client.get("/v1/api/subapi/sub").status_code == 200
# ---------------------------------------------------------------------------
# Nested routers
# ---------------------------------------------------------------------------
def test_nested_router_mount_routing():
"""Mounts survive two levels of include_router nesting."""
inner = APIRouter(prefix="/inner")
inner.mount("/subapi", make_sub_app())
outer = APIRouter(prefix="/outer")
outer.include_router(inner)
app = FastAPI()
app.include_router(outer)
client = TestClient(app)
assert client.get("/outer/inner/subapi/sub").status_code == 200
# ---------------------------------------------------------------------------
# Sub-app openapi.json is reachable at the correct path
# ---------------------------------------------------------------------------
def test_router_prefix_mount_openapi():
"""Sub-app's OpenAPI spec is accessible at the combined prefix path."""
router = APIRouter(prefix="/api")
router.mount("/subapi", make_sub_app())
app = FastAPI()
app.include_router(router)
client = TestClient(app)
response = client.get("/api/subapi/openapi.json")
assert response.status_code == 200
data = response.json()
assert data["openapi"] == "3.1.0"
assert "/sub" in data["paths"]
# root_path is reflected in the servers list
assert any("/api/subapi" in s["url"] for s in data.get("servers", []))
# ---------------------------------------------------------------------------
# Mounts directly on FastAPI app are unaffected
# ---------------------------------------------------------------------------
def test_direct_app_mount_unaffected():
"""Existing behaviour: mounting directly on FastAPI still works."""
app = FastAPI()
app.mount("/subapi", make_sub_app())
client = TestClient(app)
assert client.get("/subapi/sub").status_code == 200