diff --git a/api/src/app.py b/api/src/app.py index f3ff93e..370099a 100644 --- a/api/src/app.py +++ b/api/src/app.py @@ -27,7 +27,7 @@ from nats.aio.client import Client as NATS from starlette.middleware.gzip import GZipMiddleware from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware -from src.api.router import router +from src.api.router import API_PREFIX, router from src.core.clients import S3Storage from src.core.config import NATSSettings, get_settings from src.core.exception_handlers import register_exception_handlers @@ -211,8 +211,15 @@ def sep_static(path: str, stat_dir: str) -> str: return "/".join(sep_path[index:]) +API_STATIC_PATH = API_PREFIX.strip("/") + + class SPAStaticFiles(StaticFiles): async def get_response(self, path: str, scope): + # Несуществующий маршрут API — честный 404 (в т.ч. для POST), а не index.html SPA и не 405 от StaticFiles: + # иначе dev-маршруты, которых при выключенных флагах нет, выглядели бы как «существующие» + if path == API_STATIC_PATH or path.startswith(API_STATIC_PATH + "/"): + raise HTTPException(status_code=HTTPStatus.NOT_FOUND) try: return await super().get_response(path, scope) except (HTTPException, StarletteHTTPException) as ex: diff --git a/api/tests/test_me_and_dev_token.py b/api/tests/test_me_and_dev_token.py index e85c9b6..b773e8e 100644 --- a/api/tests/test_me_and_dev_token.py +++ b/api/tests/test_me_and_dev_token.py @@ -362,3 +362,12 @@ def test_module_router_matches_settings() -> None: """Модульный `router` собран по настройкам процесса (`.env.test`: DEBUG и AUTH_DEV_ISSUER включены).""" paths = {getattr(r, "path", None) for r in router.routes} assert {"/api/v1/me", "/api/v1/dev/token"} <= paths + + +@pytest.mark.parametrize("method", ["GET", "POST"]) +async def test_unknown_api_path_is_404_not_spa(method: str) -> None: + """Под `/api/v1` SPA-заглушка не отвечает: выключенный dev-маршрут — 404 (не 405, не index.html).""" + app_module = importlib.import_module("src.app") + async with _client(app_module.app) as client: + response = await client.request(method, "/api/v1/dev/no-such-route", json={}) + assert response.status_code == HTTP_NOT_FOUND