feat: Task 19 — Health-Check Blueprint mit Readiness-Endpunkt (closes #19)

This commit is contained in:
hermes
2026-06-22 11:17:04 +00:00
parent bdae37a4d0
commit f825165596
3 changed files with 123 additions and 5 deletions
+8 -4
View File
@@ -8,6 +8,10 @@ from src.config import settings
from src.error_handler import register_error_handlers from src.error_handler import register_error_handlers
from src.logging_config import setup_logging from src.logging_config import setup_logging
from src.queue.handler import QueueHandler 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: def create_app() -> Flask:
@@ -29,9 +33,9 @@ def create_app() -> Flask:
redis_port=settings.REDIS_PORT, redis_port=settings.REDIS_PORT,
) )
@app.route("/health") app.register_blueprint(telegram_bp, url_prefix="/webhook")
def health() -> dict: app.register_blueprint(whatsapp_bp, url_prefix="/webhook")
"""Health-Check Endpoint für Monitoring.""" app.register_blueprint(admin_bp, url_prefix="/admin")
return {"status": "ok", "queue_size": app.queue_handler.queue_size()} app.register_blueprint(health_bp)
return app return app
+49
View File
@@ -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
+65
View File
@@ -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