baton/tests/test_fix_013.py

194 lines
7.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Tests for BATON-FIX-013: CORS allow_methods — добавить GET для /health эндпоинтов.
Acceptance criteria:
1. CORSMiddleware в main.py содержит "GET" в allow_methods.
2. OPTIONS preflight /health с Origin и Access-Control-Request-Method: GET
возвращает 200/204 и содержит GET в Access-Control-Allow-Methods.
3. OPTIONS preflight /api/health — аналогично.
4. GET /health возвращает 200 (regression guard vs. allow_methods=['POST'] only).
"""
from __future__ import annotations
import os
os.environ.setdefault("BOT_TOKEN", "test-bot-token")
os.environ.setdefault("CHAT_ID", "-1001234567890")
os.environ.setdefault("WEBHOOK_SECRET", "test-webhook-secret")
os.environ.setdefault("WEBHOOK_URL", "https://example.com/api/webhook/telegram")
os.environ.setdefault("FRONTEND_ORIGIN", "http://localhost:3000")
os.environ.setdefault("ADMIN_TOKEN", "test-admin-token")
os.environ.setdefault("ADMIN_CHAT_ID", "5694335584")
import pytest
from tests.conftest import make_app_client
_ORIGIN = "http://localhost:3000"
# allow_headers = ["Content-Type", "Authorization"] — X-Custom-Header не разрешён,
# поэтому preflight с X-Custom-Header вернёт 400. Используем Content-Type.
_PREFLIGHT_HEADER = "Content-Type"
# ---------------------------------------------------------------------------
# Criterion 1 — Static: CORSMiddleware.allow_methods must contain "GET"
# ---------------------------------------------------------------------------
def test_cors_middleware_allow_methods_contains_get() -> None:
"""app.user_middleware CORSMiddleware должен содержать 'GET' в allow_methods."""
from fastapi.middleware.cors import CORSMiddleware
from backend.main import app
cors_mw = next(
(m for m in app.user_middleware if m.cls is CORSMiddleware), None
)
assert cors_mw is not None, "CORSMiddleware не найден в app.user_middleware"
allow_methods = cors_mw.kwargs.get("allow_methods", [])
assert "GET" in allow_methods, (
f"CORSMiddleware.allow_methods={allow_methods!r} не содержит 'GET'"
)
def test_cors_middleware_allow_methods_contains_head() -> None:
"""allow_methods должен содержать 'HEAD' для корректной работы preflight."""
from fastapi.middleware.cors import CORSMiddleware
from backend.main import app
cors_mw = next(
(m for m in app.user_middleware if m.cls is CORSMiddleware), None
)
assert cors_mw is not None
allow_methods = cors_mw.kwargs.get("allow_methods", [])
assert "HEAD" in allow_methods, (
f"CORSMiddleware.allow_methods={allow_methods!r} не содержит 'HEAD'"
)
def test_cors_middleware_allow_methods_contains_options() -> None:
"""allow_methods должен содержать 'OPTIONS' для корректной обработки preflight."""
from fastapi.middleware.cors import CORSMiddleware
from backend.main import app
cors_mw = next(
(m for m in app.user_middleware if m.cls is CORSMiddleware), None
)
assert cors_mw is not None
allow_methods = cors_mw.kwargs.get("allow_methods", [])
assert "OPTIONS" in allow_methods, (
f"CORSMiddleware.allow_methods={allow_methods!r} не содержит 'OPTIONS'"
)
# ---------------------------------------------------------------------------
# Criterion 2 — Preflight OPTIONS /health includes GET in Allow-Methods
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_health_preflight_options_returns_success_status() -> None:
"""OPTIONS preflight /health должен вернуть 200 или 204."""
async with make_app_client() as client:
response = await client.options(
"/health",
headers={
"Origin": _ORIGIN,
"Access-Control-Request-Method": "GET",
"Access-Control-Request-Headers": _PREFLIGHT_HEADER,
},
)
assert response.status_code in (200, 204), (
f"OPTIONS /health вернул {response.status_code}, ожидался 200 или 204"
)
@pytest.mark.asyncio
async def test_health_preflight_options_allow_methods_contains_get() -> None:
"""OPTIONS preflight /health: Access-Control-Allow-Methods должен содержать GET."""
async with make_app_client() as client:
response = await client.options(
"/health",
headers={
"Origin": _ORIGIN,
"Access-Control-Request-Method": "GET",
"Access-Control-Request-Headers": _PREFLIGHT_HEADER,
},
)
allow_methods_header = response.headers.get("access-control-allow-methods", "")
assert "GET" in allow_methods_header, (
f"Access-Control-Allow-Methods={allow_methods_header!r} не содержит 'GET'"
)
# ---------------------------------------------------------------------------
# Criterion 3 — Preflight OPTIONS /api/health includes GET in Allow-Methods
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_api_health_preflight_options_returns_success_status() -> None:
"""OPTIONS preflight /api/health должен вернуть 200 или 204."""
async with make_app_client() as client:
response = await client.options(
"/api/health",
headers={
"Origin": _ORIGIN,
"Access-Control-Request-Method": "GET",
"Access-Control-Request-Headers": _PREFLIGHT_HEADER,
},
)
assert response.status_code in (200, 204), (
f"OPTIONS /api/health вернул {response.status_code}, ожидался 200 или 204"
)
@pytest.mark.asyncio
async def test_api_health_preflight_options_allow_methods_contains_get() -> None:
"""OPTIONS preflight /api/health: Access-Control-Allow-Methods должен содержать GET."""
async with make_app_client() as client:
response = await client.options(
"/api/health",
headers={
"Origin": _ORIGIN,
"Access-Control-Request-Method": "GET",
"Access-Control-Request-Headers": _PREFLIGHT_HEADER,
},
)
allow_methods_header = response.headers.get("access-control-allow-methods", "")
assert "GET" in allow_methods_header, (
f"Access-Control-Allow-Methods={allow_methods_header!r} не содержит 'GET'"
)
# ---------------------------------------------------------------------------
# Criterion 4 — GET /health returns 200 (regression guard)
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_health_get_returns_200_regression_guard() -> None:
"""GET /health должен вернуть 200 — regression guard против allow_methods=['POST'] only."""
async with make_app_client() as client:
response = await client.get(
"/health",
headers={"Origin": _ORIGIN},
)
assert response.status_code == 200, (
f"GET /health вернул {response.status_code}, ожидался 200"
)
@pytest.mark.asyncio
async def test_api_health_get_returns_200_regression_guard() -> None:
"""GET /api/health должен вернуть 200 — regression guard против allow_methods=['POST'] only."""
async with make_app_client() as client:
response = await client.get(
"/api/health",
headers={"Origin": _ORIGIN},
)
assert response.status_code == 200, (
f"GET /api/health вернул {response.status_code}, ожидался 200"
)