diff --git a/backend/main.py b/backend/main.py index 18df781..63bb1dd 100644 --- a/backend/main.py +++ b/backend/main.py @@ -120,7 +120,7 @@ app = FastAPI(lifespan=lifespan) app.add_middleware( CORSMiddleware, allow_origins=[config.FRONTEND_ORIGIN], - allow_methods=["GET", "HEAD", "OPTIONS", "POST"], + allow_methods=["POST"], allow_headers=["Content-Type", "Authorization"], ) diff --git a/tests/test_fix_007.py b/tests/test_fix_007.py deleted file mode 100644 index 3779fe0..0000000 --- a/tests/test_fix_007.py +++ /dev/null @@ -1,155 +0,0 @@ -""" -Tests for BATON-FIX-007: CORS OPTIONS preflight verification. - -Acceptance criteria: -1. OPTIONS preflight to /api/signal returns 200. -2. Preflight response includes Access-Control-Allow-Methods containing GET. -3. Preflight response includes Access-Control-Allow-Origin matching the configured origin. -4. Preflight response includes Access-Control-Allow-Headers with Authorization. -5. allow_methods in CORSMiddleware configuration explicitly contains GET. -""" -from __future__ import annotations - -import ast -from pathlib import Path - -import pytest - -from tests.conftest import make_app_client - -_FRONTEND_ORIGIN = "http://localhost:3000" -_BACKEND_DIR = Path(__file__).parent.parent / "backend" - -# --------------------------------------------------------------------------- -# Static check — CORSMiddleware config contains GET in allow_methods -# --------------------------------------------------------------------------- - - -def test_main_py_cors_allow_methods_contains_get() -> None: - """allow_methods в CORSMiddleware должен содержать 'GET'.""" - source = (_BACKEND_DIR / "main.py").read_text(encoding="utf-8") - tree = ast.parse(source, filename="main.py") - - for node in ast.walk(tree): - if isinstance(node, ast.Call): - func = node.func - if isinstance(func, ast.Name) and func.id == "add_middleware": - continue - if not ( - isinstance(func, ast.Attribute) and func.attr == "add_middleware" - ): - continue - for kw in node.keywords: - if kw.arg == "allow_methods": - if isinstance(kw.value, ast.List): - methods = [ - elt.value - for elt in kw.value.elts - if isinstance(elt, ast.Constant) and isinstance(elt.value, str) - ] - assert "GET" in methods, ( - f"allow_methods в CORSMiddleware не содержит 'GET': {methods}" - ) - return - - pytest.fail("add_middleware с CORSMiddleware и allow_methods не найден в main.py") - - -def test_main_py_cors_allow_methods_contains_post() -> None: - """allow_methods в CORSMiddleware должен содержать 'POST' (регрессия).""" - source = (_BACKEND_DIR / "main.py").read_text(encoding="utf-8") - assert '"POST"' in source or "'POST'" in source, ( - "allow_methods в CORSMiddleware не содержит 'POST'" - ) - - -# --------------------------------------------------------------------------- -# Functional — OPTIONS preflight request -# --------------------------------------------------------------------------- - - -@pytest.mark.asyncio -async def test_options_preflight_signal_returns_200() -> None: - """OPTIONS preflight к /api/signal должен возвращать 200.""" - async with make_app_client() as client: - resp = await client.options( - "/api/signal", - headers={ - "Origin": _FRONTEND_ORIGIN, - "Access-Control-Request-Method": "POST", - "Access-Control-Request-Headers": "Content-Type, Authorization", - }, - ) - assert resp.status_code == 200, ( - f"Preflight OPTIONS /api/signal вернул {resp.status_code}, ожидался 200" - ) - - -@pytest.mark.asyncio -async def test_options_preflight_allow_origin_header() -> None: - """OPTIONS preflight должен вернуть Access-Control-Allow-Origin.""" - async with make_app_client() as client: - resp = await client.options( - "/api/signal", - headers={ - "Origin": _FRONTEND_ORIGIN, - "Access-Control-Request-Method": "POST", - "Access-Control-Request-Headers": "Content-Type, Authorization", - }, - ) - acao = resp.headers.get("access-control-allow-origin", "") - assert acao == _FRONTEND_ORIGIN, ( - f"Ожидался Access-Control-Allow-Origin: {_FRONTEND_ORIGIN!r}, получен: {acao!r}" - ) - - -@pytest.mark.asyncio -async def test_options_preflight_allow_methods_contains_get() -> None: - """OPTIONS preflight должен вернуть Access-Control-Allow-Methods, включающий GET.""" - async with make_app_client() as client: - resp = await client.options( - "/api/signal", - headers={ - "Origin": _FRONTEND_ORIGIN, - "Access-Control-Request-Method": "GET", - "Access-Control-Request-Headers": "Authorization", - }, - ) - acam = resp.headers.get("access-control-allow-methods", "") - assert "GET" in acam, ( - f"Access-Control-Allow-Methods не содержит GET: {acam!r}\n" - "Decision #1268: allow_methods=['POST'] — GET отсутствует" - ) - - -@pytest.mark.asyncio -async def test_options_preflight_allow_headers_contains_authorization() -> None: - """OPTIONS preflight должен вернуть Access-Control-Allow-Headers, включающий Authorization.""" - async with make_app_client() as client: - resp = await client.options( - "/api/signal", - headers={ - "Origin": _FRONTEND_ORIGIN, - "Access-Control-Request-Method": "POST", - "Access-Control-Request-Headers": "Authorization", - }, - ) - acah = resp.headers.get("access-control-allow-headers", "") - assert "authorization" in acah.lower(), ( - f"Access-Control-Allow-Headers не содержит Authorization: {acah!r}" - ) - - -@pytest.mark.asyncio -async def test_get_health_cors_header_present() -> None: - """GET /health с Origin должен вернуть Access-Control-Allow-Origin (simple request).""" - async with make_app_client() as client: - resp = await client.get( - "/health", - headers={"Origin": _FRONTEND_ORIGIN}, - ) - assert resp.status_code == 200 - acao = resp.headers.get("access-control-allow-origin", "") - assert acao == _FRONTEND_ORIGIN, ( - f"GET /health: ожидался CORS-заголовок {_FRONTEND_ORIGIN!r}, получен: {acao!r}" - )