"""GET/HEAD /health — uptime-monitor honesty (#uptime-honest-green). GlitchTip PING-мониторы шлют HEAD (или GET без чтения тела). Голый `@app.get("/health")` без явного HEAD-хендлера отдаёт 405 на HEAD — Starlette НЕ добавляет HEAD автоматически к FastAPI `@app.get()` роуту (в отличие от низкоуровневого `Route(methods=["GET"])`). Прод-симптом: `HEAD /health` → 405, монитор либо красный по конструкции, либо (при PING без сверки статуса) зелёный вне зависимости от факта. Тест фиксирует оба метода. """ from __future__ import annotations from fastapi.testclient import TestClient from app.main import app def test_health_get_ok() -> None: client = TestClient(app) resp = client.get("/health") assert resp.status_code == 200 body = resp.json() assert body["status"] == "ok" def test_health_head_ok_no_body() -> None: """HEAD /health — то, что реально шлёт uptime-monitor. Должен быть 200, без тела.""" client = TestClient(app) resp = client.head("/health") assert resp.status_code == 200 assert resp.content == b"" # RFC 9110 §9.3.2 — HEAD должен вернуть те же заголовки представления # (Content-Type), что и GET; Content-Length допустимо не совпадать (payload # header field, MAY быть опущен для HEAD). assert resp.headers["content-type"] == "application/json"