feat: Task 19 — Health-Check Blueprint mit Readiness-Endpunkt (closes #19)
This commit is contained in:
+9
-5
@@ -8,6 +8,10 @@ from src.config import settings
|
||||
from src.error_handler import register_error_handlers
|
||||
from src.logging_config import setup_logging
|
||||
from src.queue.handler import QueueHandler
|
||||
from src.routes.admin import admin_bp
|
||||
from src.routes.health import health_bp
|
||||
from src.routes.telegram import telegram_bp
|
||||
from src.routes.whatsapp import whatsapp_bp
|
||||
|
||||
|
||||
def create_app() -> Flask:
|
||||
@@ -29,9 +33,9 @@ def create_app() -> Flask:
|
||||
redis_port=settings.REDIS_PORT,
|
||||
)
|
||||
|
||||
@app.route("/health")
|
||||
def health() -> dict:
|
||||
"""Health-Check Endpoint für Monitoring."""
|
||||
return {"status": "ok", "queue_size": app.queue_handler.queue_size()}
|
||||
app.register_blueprint(telegram_bp, url_prefix="/webhook")
|
||||
app.register_blueprint(whatsapp_bp, url_prefix="/webhook")
|
||||
app.register_blueprint(admin_bp, url_prefix="/admin")
|
||||
app.register_blueprint(health_bp)
|
||||
|
||||
return app
|
||||
return app
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
"""Health-Check Route mit Status und Readiness-Endpunkt."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import platform
|
||||
import sys
|
||||
|
||||
from flask import Blueprint, current_app, jsonify
|
||||
|
||||
health_bp = Blueprint("health", __name__, url_prefix="/health")
|
||||
|
||||
|
||||
@health_bp.route("")
|
||||
def health_status() -> tuple:
|
||||
"""GET /health — Liveness: Status, Queue-Size, Python-Version, Plattform."""
|
||||
qh = getattr(current_app, "queue_handler", None)
|
||||
queue_size = qh.queue_size() if qh else None
|
||||
return jsonify(
|
||||
{
|
||||
"status": "ok",
|
||||
"queue_size": queue_size,
|
||||
"python_version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
|
||||
"platform": platform.platform(),
|
||||
}
|
||||
), 200
|
||||
|
||||
|
||||
@health_bp.route("/ready")
|
||||
def health_ready() -> tuple:
|
||||
"""GET /health/ready — Readiness: Redis-Verbindung prüfen."""
|
||||
qh = getattr(current_app, "queue_handler", None)
|
||||
if qh is None:
|
||||
return jsonify(
|
||||
{
|
||||
"ready": False,
|
||||
"reason": "QueueHandler nicht registriert",
|
||||
}
|
||||
), 503
|
||||
|
||||
try:
|
||||
qh.redis.ping()
|
||||
return jsonify({"ready": True}), 200
|
||||
except Exception as exc:
|
||||
return jsonify(
|
||||
{
|
||||
"ready": False,
|
||||
"reason": str(exc),
|
||||
}
|
||||
), 503
|
||||
@@ -0,0 +1,65 @@
|
||||
"""Tests für src.routes.health — Health-Check mit Readiness."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from src.app import create_app
|
||||
|
||||
|
||||
class TestHealthStatus:
|
||||
"""Tests für /health Endpoint."""
|
||||
|
||||
def setup_method(self) -> None:
|
||||
"""Jeder Test bekommt eine frische App mit gemocktem Redis."""
|
||||
with patch("src.queue.handler.redis.Redis"):
|
||||
self.app = create_app()
|
||||
self.client = self.app.test_client()
|
||||
|
||||
def test_health_status_ok(self) -> None:
|
||||
"""GET /health → 200 mit Status-JSON."""
|
||||
self.app.queue_handler = MagicMock(queue_size=MagicMock(return_value=42)) # type: ignore[assignment]
|
||||
response = self.client.get("/health")
|
||||
assert response.status_code == 200
|
||||
data = response.get_json()
|
||||
assert data["status"] == "ok"
|
||||
assert "queue_size" in data
|
||||
assert "python_version" in data
|
||||
assert "platform" in data
|
||||
|
||||
def test_health_queue_size_integer(self) -> None:
|
||||
"""queue_size ist ein Integer."""
|
||||
self.app.queue_handler = MagicMock(queue_size=MagicMock(return_value=7)) # type: ignore[assignment]
|
||||
response = self.client.get("/health")
|
||||
data = response.get_json()
|
||||
assert isinstance(data["queue_size"], int)
|
||||
assert data["queue_size"] == 7
|
||||
|
||||
|
||||
class TestHealthReady:
|
||||
"""Tests für /health/ready Endpoint."""
|
||||
|
||||
def setup_method(self) -> None:
|
||||
"""Jeder Test bekommt eine frische App."""
|
||||
with patch("src.queue.handler.redis.Redis"):
|
||||
self.app = create_app()
|
||||
self.client = self.app.test_client()
|
||||
|
||||
def test_health_ready_redis_alive(self) -> None:
|
||||
"""Redis ping erfolgreich → ready=true."""
|
||||
response = self.client.get("/health/ready")
|
||||
# Redis wird gemockt, ping sollte successful sein
|
||||
assert response.status_code == 200
|
||||
data = response.get_json()
|
||||
assert data["ready"] is True
|
||||
|
||||
def test_health_ready_redis_fails(self) -> None:
|
||||
"""Redis ping fehlschlägt → ready=false, 503."""
|
||||
with patch.object(
|
||||
self.app.queue_handler.redis, "ping", side_effect=Exception("Redis down")
|
||||
):
|
||||
response = self.client.get("/health/ready")
|
||||
assert response.status_code == 503
|
||||
data = response.get_json()
|
||||
assert data["ready"] is False
|
||||
assert "reason" in data
|
||||
Reference in New Issue
Block a user