diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 4fea5f2c..76e26058 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -17,6 +17,10 @@ class Settings(BaseSettings): glitchtip_dsn: str | None = None glitchtip_traces_sample_rate: float = 0.05 environment: str = "dev" + # Test-mode flag (env TESTING=1). СТРОГО default False — в проде RBAC-гейт + # (app/main.py rbac_guard) активен. True только в pytest (tests/conftest.py), + # где запросы идут по app мимо Caddy и не несут X-Authenticated-User. + testing: bool = False @model_validator(mode="after") def _promote_legacy_sentry_dsn(self) -> "Settings": diff --git a/backend/app/main.py b/backend/app/main.py index 1648033b..234afe43 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -97,6 +97,12 @@ async def rbac_guard( request: Request, call_next: Callable[[Request], Awaitable[Response]], ) -> Response: + # Test-mode bypass: pytest бьёт по app мимо Caddy → нет X-Authenticated-User. + # СТРОГО gated на settings.testing (default False) — прод RBAC не затронут. + # RBAC-логика покрыта отдельно в tests/test_rbac.py (своя копия middleware). + if settings.testing: + return await call_next(request) + path = request.url.path if path in _PUBLIC_PATHS: return await call_next(request) diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 7b19365f..4a5fb6f2 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -9,8 +9,21 @@ NB: RBAC-гейт (app/main.py `rbac_guard`) требует заголовок ` from __future__ import annotations +import os + import pytest +# Включаем test-mode ДО любого импорта app: rbac_guard (app/main.py) пропускает +# запросы при settings.testing=True (юнит-тесты бьют по app мимо Caddy и не несут +# X-Authenticated-User → иначе 401 на всём api/v1). Ставим и env (на случай +# ре-инстанцирования Settings), и атрибут синглтона напрямую (надёжно независимо +# от порядка импортов). RBAC-логика (401/403) покрыта в test_rbac.py — там своя +# копия middleware, на settings.testing НЕ смотрит, поэтому остаётся валидной. +os.environ.setdefault("TESTING", "1") +from app.core.config import settings + +settings.testing = True + @pytest.fixture(autouse=True) def _clear_dependency_overrides():