# Alicia — Aufgaben 6–10: BSN-Livesystem > **Repo:** `git@git.datenhimmel.work:danielkrause/BSN-Livesystem.git` > **Branch:** `main` > **Deine Basis:** Aufgaben 1–5 sind fertig und gepusht. > **Qualität:** Vor JEDEM Commit: `ruff check . && ruff format . && mypy --config-file pyproject.toml . && pytest -v` > **Fehler?** Code korrigieren, Checks wiederholen. Nicht aufgeben bis ALLE grün. --- ## Aufgabe 6 — Webhook-Signatur-Prüfung ### Datei: `src/queue/webhook.py` ```python import hashlib import hmac import os from src.queue.handler import QueueHandler from src.models.submission import Submission def verify_whatsapp_signature( payload: bytes, signature: str, app_secret: str | None = None ) -> bool: """Verifiziert die WhatsApp X-Hub-Signature-256. Args: payload: Der rohe Request-Body. signature: Der X-Hub-Signature-256 Header-Wert. app_secret: Der WhatsApp App-Secret (aus Env wenn None). Returns: True wenn Signatur gültig. """ secret = app_secret or os.environ.get("WHATSAPP_APP_SECRET", "") if not secret: return False expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest() return hmac.compare_digest(f"sha256={expected}", signature) def verify_telegram_signature(token: str | None = None) -> bool: """Verifiziert den Telegram Bot-Token. Args: token: Bot-Token (aus Env wenn None). Returns: True wenn Token gültig. """ expected = token or os.environ.get("TELEGRAM_BOT_TOKEN", "") if not expected: return False return len(expected) > 20 def validate_and_enqueue( payload: bytes, signature: str, plattform: str, qh: QueueHandler ) -> bool: """Validiert plattformspezifische Signatur und legt Submission in Queue. Args: payload: Der rohe Request-Body. signature: Signatur-Header. plattform: "whatsapp" oder "telegram". qh: QueueHandler-Instanz. Returns: True wenn validiert und erfolgreich enqueued. """ if plattform == "whatsapp": if not verify_whatsapp_signature(payload, signature): return False elif plattform == "telegram": if not verify_telegram_signature(): return False else: return False import json data = json.loads(payload) submission = Submission( message_id=str(data.get("message_id", "")), plattform=plattform, absender=str(data.get("absender", "")), text=str(data.get("text", "")), ) return qh.enqueue(submission) is not None ``` ### Test-Datei: `tests/test_webhook.py` 5 Tests: 1. Korrekte WhatsApp-Signatur → True 2. Falsche WhatsApp-Signatur → False 3. Fehlender App-Secret → False 4. Korrekter Telegram-Token → True (setze `TELEGRAM_BOT_TOKEN` mit ≥21 Zeichen) 5. Zu kurzer Telegram-Token → False **Commit:** `feat: Webhook-Signatur-Prüfung für WhatsApp + Telegram` --- ## Aufgabe 7 — Queue-Worker ### Datei: `src/queue/worker.py` ```python import time from src.queue.handler import QueueHandler from src.models.submission import Submission def verarbeite_submission(submission: Submission) -> bool: """Verarbeitet eine einzelne Submission (Platzhalter für KI-Pipeline). Args: submission: Die zu verarbeitende Submission. Returns: Immer True im Erfolgsfall. """ submission.status = "erledigt" return True def worker_loop( qh: QueueHandler, poll_interval: int = 2, batch_size: int = 5 ) -> None: """Endlos-Schleife: Pollt Queue, verarbeitet Batches. Args: qh: QueueHandler-Instanz. poll_interval: Sekunden zwischen Polls wenn Queue leer. batch_size: Maximale Submissions pro Batch. """ while True: submissions = qh.dequeue(batch_size) if not submissions: time.sleep(poll_interval) continue for submission in submissions: try: if verarbeite_submission(submission): qh.mark_done(submission) except Exception: pass # Fehler in einer Submission → weitermachen ``` ### Test-Datei: `tests/test_worker.py` 3 Tests mit fakeredis (MOCKE `time.sleep` mit `unittest.mock.patch`): 1. `verarbeite_submission` setzt Status auf "erledigt" 2. `worker_loop` verarbeitet alle Submissions in der Queue (nach Batch prüfen) 3. `worker_loop` crashed nicht bei leerer Queue **Commit:** `feat: Queue-Worker mit Batch-Verarbeitung und Fehlertoleranz` --- ## Aufgabe 8 — Flask-App-Factory ### Datei: `src/app.py` ```python from flask import Flask from src.queue.handler import QueueHandler def create_app() -> Flask: """Erstellt und konfiguriert die Flask-App. Returns: Flask-App-Instanz mit QueueHandler. """ app = Flask(__name__) app.config.from_mapping( REDIS_HOST="localhost", REDIS_PORT=6379, ) app.queue_handler = QueueHandler( redis_host=app.config["REDIS_HOST"], redis_port=app.config["REDIS_PORT"], ) @app.route("/health") def health() -> dict[str, object]: return { "status": "ok", "queue_size": app.queue_handler.queue_size(), } return app if __name__ == "__main__": app = create_app() app.run(host="0.0.0.0", port=5002, debug=False) ``` ### Test-Datei: `tests/test_app.py` 3 Tests mit Flask-Test-Client (`app.test_client()`): 1. `create_app()` gibt Flask-App zurück 2. GET `/health` → 200, JSON mit `"status": "ok"` 3. GET `/health` → `queue_size` ist Integer **Commit:** `feat: Flask-App-Factory mit Health-Check und QueueHandler` --- ## Aufgabe 9 — WhatsApp-Webhook (Flask-Blueprint) ### Datei 1: `src/routes/__init__.py` Leer lassen. ### Datei 2: `src/routes/whatsapp.py` Blueprint `"whatsapp"` mit `/webhook/whatsapp` (GET + POST): - **GET:** Hub-Challenge — prüft `hub.mode == "subscribe"` und `hub.verify_token == "bsn_verify_token"` → gibt `hub.challenge` zurück (200), sonst 403 - **POST:** Signatur via `verify_whatsapp_signature()` prüfen → Submission bauen → `current_app.queue_handler.enqueue()` → 200 - Ungültige Signatur → 401 - `WHATSAPP_APP_SECRET` aus `os.environ` ### Test-Datei: `tests/test_whatsapp.py` 4 Tests mit `app.test_client()` (Blueprint temporär registrieren): 1. GET mit korrektem verify_token → 200 + Challenge 2. GET mit falschem verify_token → 403 3. POST mit korrekter Signatur → 200 4. POST mit falscher Signatur → 401 **Setze** `os.environ["WHATSAPP_APP_SECRET"] = "test"` und `os.environ["WHATSAPP_VERIFY_TOKEN"] = "test-token"` im Test-Setup. **Commit:** `feat: WhatsApp-Webhook mit Signatur-Prüfung und Queue-Enqueue` --- ## Aufgabe 10 — Telegram-Webhook (Flask-Blueprint) ### Datei: `src/routes/telegram.py` Blueprint `"telegram"` mit `/webhook/telegram` (NUR POST): - `request.get_json(silent=True)` mit None-Check → 400 - `message_id`, `chat_id`, `text` aus `data["message"]` extrahieren - Fehlende `message_id` oder `text` → 400 - `Submission` bauen (`plattform="telegram"`) → `current_app.queue_handler.enqueue()` → 200 - Pydantic `ValidationError` abfangen → 400 ### Test-Datei: `tests/test_telegram.py` 4 Tests mit `app.test_client()`: 1. POST mit gültigem Payload → 200 2. POST ohne JSON → 400 3. POST ohne `message_id` → 400 4. POST mit leerem `text` → 400 **Commit:** `feat: Telegram-Webhook mit JSON-Validierung und Queue-Enqueue` --- ## ⚠️ WICHTIG - **Vor JEDEM Commit:** `ruff check . && ruff format . && mypy --config-file pyproject.toml . && pytest -v` - **Nicht pushen bevor ALLE 4 Checks grün sind!** - **Nach jeder Aufgabe:** Commit + Push, dann nächste. - **Falls ein Test fehlschlägt:** Code korrigieren, nicht aufgeben. - **Repo:** Arbeite in `/tmp/BSN-Livesystem` (oder wo du geklont hast) - **Täglich vor Start:** `git pull origin main`