fix(tradein): HEAD /health отвечает 200 вместо 405
@app.get("/health") в FastAPI/Starlette не добавляет HEAD-обработчик
автоматически (в отличие от низкоуровневого Route(methods=["GET"])) —
внешний uptime-monитор (GlitchTip PING-тип шлёт HEAD) получал 405 и не
мог отличить "жив" от "мёртв" по статусу. Добавлен явный
@app.head("/health") — 200 без тела (RFC 9110 §9.3.2), GET не тронут.
Тест test_health_endpoint.py фиксирует оба метода; RED до фикса
(HEAD → 405), GREEN после (проверено git stash + повторный прогон).
This commit is contained in:
parent
e8fe9faa13
commit
24b70e5c58
2 changed files with 43 additions and 1 deletions
|
|
@ -12,7 +12,7 @@ from collections.abc import AsyncGenerator
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
import sentry_sdk
|
import sentry_sdk
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI, Response
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from sentry_sdk.integrations.fastapi import FastApiIntegration
|
from sentry_sdk.integrations.fastapi import FastApiIntegration
|
||||||
from sentry_sdk.integrations.httpx import HttpxIntegration
|
from sentry_sdk.integrations.httpx import HttpxIntegration
|
||||||
|
|
@ -210,6 +210,17 @@ def health() -> dict[str, str]:
|
||||||
return {"status": "ok", "environment": settings.environment}
|
return {"status": "ok", "environment": settings.environment}
|
||||||
|
|
||||||
|
|
||||||
|
# FastAPI/Starlette НЕ добавляет HEAD автоматически к @app.get() (в отличие от
|
||||||
|
# raw Starlette Route с methods=["GET"]) — без явного handler'а HEAD /health
|
||||||
|
# отдаёт 405, и внешний uptime-monitor (GlitchTip PING-тип, HEAD-запрос) не
|
||||||
|
# может отличить "жив" от "мёртв" по статусу. Тело для HEAD не отдаём — так
|
||||||
|
# требует HTTP-спека (RFC 9110 §9.3.2): у ответа те же заголовки, что у GET,
|
||||||
|
# но без body.
|
||||||
|
@app.head("/health")
|
||||||
|
def health_head() -> Response:
|
||||||
|
return Response(status_code=200)
|
||||||
|
|
||||||
|
|
||||||
app.include_router(auth.router, prefix="/api/v1/auth", tags=["auth"])
|
app.include_router(auth.router, prefix="/api/v1/auth", tags=["auth"])
|
||||||
app.include_router(geocode.router, prefix="/api/v1/geocode", tags=["geocode"])
|
app.include_router(geocode.router, prefix="/api/v1/geocode", tags=["geocode"])
|
||||||
app.include_router(admin.router, prefix="/api/v1/admin", tags=["admin"])
|
app.include_router(admin.router, prefix="/api/v1/admin", tags=["admin"])
|
||||||
|
|
|
||||||
31
tradein-mvp/backend/tests/test_health_endpoint.py
Normal file
31
tradein-mvp/backend/tests/test_health_endpoint.py
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
"""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""
|
||||||
Loading…
Add table
Reference in a new issue