31b1a62cb2
Task 23: Worker-Intake-Pipeline verbinden
- ergebnis-Feld in Submission/Payload
- worker.py extrahiert + worker_loop refactor
- test_worker.py mit VerarbeiteSubmission/WorkerLoop tests
Task 24: Admin-Stats mit echten Daten
- processed_total, uptime, error_rate, VERSION
- admin_health mit Redis-Live-Daten
Task 25: E2E-Integrationstest + worker_refaktorisiert
Quality-Gate-Fixes:
- consumername Collision: 'worker-1' -> f'worker-{id(self)}'
- fakeredis block=0 Timeout fix
- WhatsApp/Telegram webhook payload fixes
- test_telegram/integration/e2e/refaktorisiert
- handler.py recovery nach corrupted Patch
142 lines
5.6 KiB
Python
142 lines
5.6 KiB
Python
"""Integration-Tests für WhatsApp/Telegram-Webhook-Flows."""
|
|
|
|
import hashlib
|
|
import hmac
|
|
from datetime import datetime
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
from flask import Flask
|
|
|
|
from src.app import create_app
|
|
from src.queue.worker import verarbeite_submission, worker_loop
|
|
from src.routes.admin import admin_bp
|
|
from src.routes.telegram import telegram_bp
|
|
from src.routes.whatsapp import whatsapp_bp
|
|
from src.services.game_service import GameService
|
|
|
|
|
|
@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, name="whatsapp_int")
|
|
app.register_blueprint(telegram_bp, name="telegram_int")
|
|
app.register_blueprint(admin_bp, name="admin_int", url_prefix="/admin")
|
|
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="""{
|
|
"object":"whatsapp",
|
|
"entry":[{"id":"123","changes":[{"value":{"messages":[{"id":"msg001","timestamp":1234567890,"from":"+491****1234","text":{"body":"hallo"}}]}}]}]}""".encode(),
|
|
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":[{"id":"123","changes":[{"value":{"messages":[{"id":"msg999","timestamp":1234567890,"from":"+491****5678","text":{"body":"test"}}]}}]}]}',
|
|
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",
|
|
json={"message": {"message_id": "123", "chat": {"id": "456"}, "text": "Hallo"}},
|
|
)
|
|
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:
|
|
"""Vollstaendiger Flow: WhatsApp POST -> Worker verarbeitet -> Status 'erledigt'."""
|
|
mock_qh = flask_app.queue_handler
|
|
mock_submission = MagicMock()
|
|
mock_submission.message_id = "msg001"
|
|
mock_submission.zeitstempel = datetime(2024, 1, 1)
|
|
mock_submission.absender = "+491****7890"
|
|
mock_submission.text = "suche Catan"
|
|
mock_submission.status = "neu"
|
|
mock_submission.ergebnis = None
|
|
mock_submission.enqueue.return_value = "stream_id"
|
|
|
|
call_count = [0]
|
|
|
|
def dequeue_side_effect(count: int = 5) -> list:
|
|
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, max_iterations=2)
|
|
|
|
assert mock_verarb.call_count == 1
|
|
assert mock_qh.mark_done.call_count == 1
|
|
|
|
def test_vollstaendiger_flow_telegram(self, flask_app: Flask) -> None:
|
|
"""Vollstaendiger Flow: Telegram POST -> Worker verarbeitet -> Status 'erledigt'."""
|
|
mock_qh = flask_app.queue_handler
|
|
mock_submission = MagicMock()
|
|
mock_submission.message_id = "msg002"
|
|
mock_submission.zeitstempel = datetime(2024, 1, 1)
|
|
mock_submission.absender = "@user123"
|
|
mock_submission.text = "hilfe"
|
|
mock_submission.status = "neu"
|
|
mock_submission.ergebnis = None
|
|
|
|
call_count = [0]
|
|
|
|
def dequeue_side_effect(count: int = 5) -> list:
|
|
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, max_iterations=2)
|
|
|
|
assert mock_verarb.call_count == 1
|
|
assert mock_qh.mark_done.call_count == 1
|