Files
BSN-Livesystem/docs/alicia-aufgaben-nacht2.md

7.8 KiB
Raw Permalink Blame History

Alicia — Aufgaben 610: BSN-Livesystem

Repo: git@git.datenhimmel.work:danielkrause/BSN-Livesystem.git Branch: main Deine Basis: Aufgaben 15 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

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

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

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 /healthqueue_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