@ -1,11 +1,17 @@
""" Tests for mounting sub-applications under APIRouter (issue #10180). """
""" Tests for mounting StaticFiles under APIRouter (issue #10180). """
from pathlib import Path
import pytest
from fastapi import APIRouter , FastAPI
from starlette . staticfiles import StaticFiles
from starlette . testclient import TestClient
from fastapi . staticfiles import StaticFiles
from fastapi . testclient import TestClient
def test_mount_staticfiles_under_router_with_prefix ( tmp_path : Path ) - > None :
""" StaticFiles mounted on APIRouter with prefix should be accessible. """
static_file = tmp_path / " hello.txt "
static_file . write_text ( " hello world " )
def test_mount_subapp_under_router_with_prefix ( ) - > None :
""" APIRouter with prefix should propagate Mount to the main app. """
app = FastAPI ( )
api_router = APIRouter ( prefix = " /api " )
@ -13,13 +19,7 @@ def test_mount_subapp_under_router_with_prefix() -> None:
def read_main ( ) - > dict :
return { " message " : " Hello World from main app " }
subapi = FastAPI ( )
@subapi . get ( " /sub " )
def read_sub ( ) - > dict :
return { " message " : " Hello World from sub API " }
api_router . mount ( " /subapi " , subapi )
api_router . mount ( " /static " , StaticFiles ( directory = tmp_path ) , name = " static " )
app . include_router ( api_router )
client = TestClient ( app )
@ -27,80 +27,68 @@ def test_mount_subapp_under_router_with_prefix() -> None:
# Regular route should still work
r = client . get ( " /api/app " )
assert r . status_code == 200
assert r . json ( ) == { " message " : " Hello World from main app " }
# Mounted sub-app should be accessible under router prefix
r = client . get ( " /api/subapi/sub " )
# StaticFiles should be accessible under router prefix
r = client . get ( " /api/static/hello.txt " )
assert r . status_code == 200
assert r . json ( ) == { " message " : " Hello World from sub API " }
assert r . text == " hello world "
def test_mount_staticfiles_under_router_without_prefix ( tmp_path : Path ) - > None :
""" StaticFiles mounted on APIRouter without prefix should be accessible. """
static_file = tmp_path / " test.txt "
static_file . write_text ( " test content " )
def test_mount_subapp_under_router_without_prefix ( ) - > None :
""" APIRouter without prefix should propagate Mount correctly. """
app = FastAPI ( )
router = APIRouter ( )
subapi = FastAPI ( )
@subapi . get ( " /hello " )
def hello ( ) - > dict :
return { " msg " : " hello " }
router . mount ( " /sub " , subapi )
router . mount ( " /static " , StaticFiles ( directory = tmp_path ) , name = " static " )
app . include_router ( router )
client = TestClient ( app )
r = client . get ( " /sub/hello " )
r = client . get ( " /static/test.txt " )
assert r . status_code == 200
assert r . json ( ) == { " msg " : " hello " }
assert r . text == " test content "
def test_mount_staticfiles_with_include_router_prefix ( tmp_path : Path ) - > None :
""" include_router prefix + router prefix + mount path should all combine. """
static_file = tmp_path / " file.txt "
static_file . write_text ( " combined prefix " )
def test_mount_subapp_with_include_router_prefix ( ) - > None :
""" include_router prefix should be combined with router prefix and mount path. """
app = FastAPI ( )
router = APIRouter ( prefix = " /api " )
subapi = FastAPI ( )
@subapi . get ( " /test " )
def test_endpoint ( ) - > dict :
return { " msg " : " test " }
router . mount ( " /sub " , subapi )
router . mount ( " /static " , StaticFiles ( directory = tmp_path ) , name = " static " )
app . include_router ( router , prefix = " /v1 " )
client = TestClient ( app )
r = client . get ( " /v1/api/sub/tes t " )
r = client . get ( " /v1/api/static/file.txt " )
assert r . status_code == 200
assert r . json ( ) == { " msg " : " test " }
assert r . text == " combined prefix "
def test_mount_preserves_regular_routes ( ) - > None :
""" Adding Mount support should not break existing APIRoute handling. """
def test_mount_non_staticfiles_app_is_ignored ( ) - > None :
""" Mounting a non-StaticFiles ASGI app on APIRouter should be silently ignored
( not propagated to the main app ) to preserve existing behavior . """
app = FastAPI ( )
api_router = APIRouter ( prefix = " /api " )
@api_router . get ( " /items " )
def list_items ( ) - > dict :
return { " items " : [ 1 , 2 , 3 ] }
@api_router . post ( " /items " )
def create_item ( ) - > dict :
return { " created " : True }
subapi = FastAPI ( )
@subapi . get ( " /status " )
def status ( ) - > dict :
return { " status " : " ok " }
@subapi . get ( " /sub " )
def read_sub ( ) - > dict :
return { " message " : " sub " }
api_router . mount ( " /health " , subapi )
api_router . mount ( " /subapi " , subapi )
app . include_router ( api_router )
client = TestClient ( app )
assert client . get ( " /api/items " ) . status_code == 200
assert client . post ( " /api/items " ) . status_code == 200
assert client . get ( " /api/health/status " ) . status_code == 200
# Non-StaticFiles sub-app mount is not propagated — returns 404
r = client . get ( " /api/subapi/sub " )
assert r . status_code == 404
# Regular routes are unaffected
# (no routes defined here, but include_router itself should not crash)
assert app is not None