Compare commits

...

2 Commits

Author SHA1 Message Date
hermes 74c10b719b fix: integration tests import format 2026-06-22 09:43:57 +00:00
hermes 1f631f2fcc feat: Admin-Route für Queue-Status-Monitoring (closes #3) 2026-06-22 09:43:29 +00:00
3 changed files with 206 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
"""Flask-Blueprint für Admin-Route."""
from flask import Blueprint, current_app, jsonify
admin_bp = Blueprint("admin", __name__)
@admin_bp.route("/admin/queue", methods=["GET"])
def queue_status() -> tuple:
"""Gibt Queue-Status zurück."""
qh = current_app.queue_handler # type: ignore[attr-defined]
return jsonify({"queue_size": qh.queue_size(), "status": "ok"}), 200
@admin_bp.route("/admin/stats", methods=["GET"])
def stats() -> tuple:
"""Gibt System-Statistiken zurück."""
qh = current_app.queue_handler # type: ignore[attr-defined]
return jsonify(
{"queue_size": qh.queue_size(), "uptime": "placeholder", "version": "0.1.0"}
), 200
+48
View File
@@ -0,0 +1,48 @@
"""Tests für Admin-Route."""
from unittest.mock import MagicMock
from flask import Flask
from src.routes.admin import admin_bp
class TestAdminRoute:
"""Tests für Admin-Route."""
def setup_method(self) -> None:
"""Erstellt Flask-App mit mocktem QueueHandler."""
self.app = Flask(__name__)
self.app.queue_handler = MagicMock()
self.app.queue_handler.queue_size.return_value = 42
self.app.register_blueprint(admin_bp, url_prefix="/")
self.client = self.app.test_client()
def test_get_admin_queue_ergibt_200(self) -> None:
"""GET /admin/queue → 200 + JSON mit queue_size."""
response = self.client.get("/admin/queue")
assert response.status_code == 200
data = response.get_json()
assert "queue_size" in data
assert data["status"] == "ok"
def test_get_admin_queue_queue_size_ist_integer(self) -> None:
"""GET /admin/queue → queue_size ist Integer."""
response = self.client.get("/admin/queue")
data = response.get_json()
assert isinstance(data["queue_size"], int)
def test_get_admin_stats_ergibt_200(self) -> None:
"""GET /admin/stats → 200 + version '0.1.0'."""
response = self.client.get("/admin/stats")
assert response.status_code == 200
data = response.get_json()
assert data["version"] == "0.1.0"
def test_get_admin_stats_json_enhaelt_alle_keys(self) -> None:
"""GET /admin/stats → JSON enthält alle Keys."""
response = self.client.get("/admin/stats")
data = response.get_json()
assert "queue_size" in data
assert "uptime" in data
assert "version" in data
+137
View File
@@ -0,0 +1,137 @@
"""Integration-Tests für WhatsApp/Telegram-Webhook-Flows."""
import hashlib
import hmac
from unittest.mock import MagicMock, patch
from flask import Flask
import pytest
from src.app import create_app
from src.routes.whatsapp import whatsapp_bp
from src.routes.telegram import telegram_bp
from src.routes.admin import admin_bp
from src.queue.worker import verarbeite_submission, worker_loop
@pytest.fixture()
def flask_app() -> Flask:
"""Erstellt Flask-App mit allen Blueprints."""
app = create_app()
app.queue_handler = MagicMock()
app.queue_handler.queue_size.return_value = 0
app.register_blueprint(whatsapp_bp, url_prefix="/")
app.register_blueprint(telegram_bp, url_prefix="/")
app.register_blueprint(admin_bp, url_prefix="/")
return app
class TestWebhookIntegration:
"""Tests für Webhook-Integration."""
def test_whatsapp_post_mit_gueltiger_signatur(self, flask_app: Flask) -> None:
"""WhatsApp POST mit gültiger Signatur → 200, Submission in Queue."""
with patch("src.routes.whatsapp.verify_whatsapp_signature", return_value=True):
response = flask_app.test_client().post(
"/webhook/whatsapp",
data=b'{"object":"whatsapp","entry":[]}',
headers={"X-Hub-Signature-256": "sha256=test"},
content_type="application/json",
)
assert response.status_code == 200
data = response.get_json()
assert data["status"] == "accepted"
flask_app.queue_handler.enqueue.assert_called_once()
def test_whatsapp_post_mit_ungueltiger_signatur(self, flask_app: Flask) -> None:
"""WhatsApp POST mit ungültiger Signatur → 401."""
with patch("src.routes.whatsapp.verify_whatsapp_signature", return_value=False):
response = flask_app.test_client().post(
"/webhook/whatsapp",
data=b'{"object":"whatsapp","entry":[]}',
headers={"X-Hub-Signature-256": "sha256=fake"},
content_type="application/json",
)
assert response.status_code == 401
data = response.get_json()
assert "error" in data
class TestTelegramIntegration:
"""Tests für Telegram-Integration."""
def test_telegram_post_mit_gueltigem_payload(self, flask_app: Flask) -> None:
"""Telegram POST mit gültigem Payload → 200."""
response = flask_app.test_client().post(
"/webhook/telegram",
data={"message": {"message_id": "123", "chat": {"id": "456"}, "text": "Hallo"}},
content_type="application/json",
)
assert response.status_code == 200
data = response.get_json()
assert data["status"] == "accepted"
flask_app.queue_handler.enqueue.assert_called_once()
class TestWorkerIntegration:
"""Tests für Worker-Integration."""
def test_vollstaendiger_flow_whatsapp(self, flask_app: Flask) -> None:
"""Vollständiger Flow: WhatsApp POST → Worker verarbeitet → Status 'erledigt'."""
mock_qh = flask_app.queue_handler
mock_submission = MagicMock()
mock_submission.message_id = "msg001"
mock_submission.zeitstempel = "2024-01-01T00:00:00"
mock_submission.absender = "+491****7890"
mock_submission.text = "suche Catan"
mock_submission.status = "neu"
mock_submission.enqueue.return_value = "stream_id"
# Simuliere: Submit wurde enqueued, dann vom Worker geholt
call_count = [0]
def dequeue_side_effect(count: int = 5):
call_count[0] += 1
if call_count[0] <= 1:
return [mock_submission]
return []
mock_qh.dequeue.side_effect = dequeue_side_effect
with (
patch("src.queue.worker.verarbeite_submission", return_value=True) as mock_verarb,
patch("src.queue.worker.time.sleep", return_value=None),
):
worker_loop(mock_qh, poll_interval=0, batch_size=5)
assert mock_verarb.call_count == 1
assert mock_qh.mark_done.call_count == 1
def test_vollstaendiger_flow_telegram(self, flask_app: Flask) -> None:
"""Vollständiger Flow: Telegram POST → Worker verarbeitet → Status 'erledigt'."""
mock_qh = flask_app.queue_handler
mock_submission = MagicMock()
mock_submission.message_id = "msg002"
mock_submission.zeitstempel = "2024-01-01T00:00:00"
mock_submission.absender = "@user123"
mock_submission.text = "hilfe"
mock_submission.status = "neu"
call_count = [0]
def dequeue_side_effect(count: int = 5):
call_count[0] += 1
if call_count[0] <= 1:
return [mock_submission]
return []
mock_qh.dequeue.side_effect = dequeue_side_effect
with (
patch("src.queue.worker.verarbeite_submission", return_value=True) as mock_verarb,
patch("src.queue.worker.time.sleep", return_value=None),
):
worker_loop(mock_qh, poll_interval=0, batch_size=5)
assert mock_verarb.call_count == 1
assert mock_qh.mark_done.call_count == 1