pythonasyncioapiasyncfastapiframeworkjsonjson-schemaopenapiopenapi3pydanticpython-typespython3redocreststarletteswaggerswagger-uiuvicornweb
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
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
|
|
|