feat: Telegram-Webhook mit JSON-Validierung und Queue-Enqueue
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
"""Telegram-Webhook Blueprint."""
|
||||
|
||||
from flask import Blueprint, current_app, request
|
||||
from pydantic import ValidationError
|
||||
|
||||
from src.models.submission import Submission
|
||||
|
||||
telegram = Blueprint("telegram", __name__)
|
||||
|
||||
|
||||
@telegram.route("/webhook/telegram", methods=["POST"])
|
||||
def webhook_telegram() -> tuple[dict[str, str], int]:
|
||||
"""Empfängt Telegram-Updates via Webhook und reiht sie in die Queue ein."""
|
||||
data = request.get_json(silent=True)
|
||||
if data is None:
|
||||
return {"error": "Ungültiges oder fehlendes JSON"}, 400
|
||||
|
||||
message = data.get("message")
|
||||
if not isinstance(message, dict):
|
||||
return {"error": "Feld 'message' fehlt oder ist ungültig"}, 400
|
||||
|
||||
message_id = message.get("message_id")
|
||||
if not message_id:
|
||||
return {"error": "Feld 'message_id' fehlt"}, 400
|
||||
|
||||
text = message.get("text")
|
||||
if not text:
|
||||
return {"error": "Feld 'text' fehlt oder ist leer"}, 400
|
||||
|
||||
chat = message.get("chat", {})
|
||||
chat_id = chat.get("id") if isinstance(chat, dict) else None
|
||||
|
||||
try:
|
||||
submission = Submission(
|
||||
message_id=str(message_id),
|
||||
plattform="telegram",
|
||||
absender=str(chat_id) if chat_id else "unknown",
|
||||
text=str(text),
|
||||
)
|
||||
except ValidationError:
|
||||
return {"error": "Validierungsfehler"}, 400
|
||||
|
||||
current_app.queue_handler.enqueue(submission) # type: ignore[attr-defined]
|
||||
return {"status": "ok"}, 200
|
||||
@@ -0,0 +1,111 @@
|
||||
"""Tests für den Telegram-Webhook Blueprint."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import fakeredis
|
||||
from flask import Flask
|
||||
from flask.testing import FlaskClient
|
||||
|
||||
from src.app import create_app
|
||||
from src.queue.handler import QueueHandler
|
||||
from src.routes.telegram import telegram
|
||||
|
||||
|
||||
def _setup_app_with_fakeredis() -> Flask:
|
||||
"""Erstellt eine Flask-App mit fakeredis-QueueHandler und registriert den Telegram-Blueprint."""
|
||||
app = create_app()
|
||||
|
||||
fake_redis = fakeredis.FakeRedis(decode_responses=True)
|
||||
qh = QueueHandler.__new__(QueueHandler)
|
||||
qh.redis = fake_redis
|
||||
qh.stream_key = "bsn:submissions"
|
||||
qh.processed_set = "bsn:processed"
|
||||
qh.stream_ids_hash = "bsn:stream_ids"
|
||||
qh.group_name = "bsn-workers"
|
||||
qh._ensure_consumer_group()
|
||||
app.queue_handler = qh # type: ignore[attr-defined]
|
||||
|
||||
app.register_blueprint(telegram)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def test_valid_telegram_payload_returns_200() -> None:
|
||||
"""POST /webhook/telegram mit gültigem Payload → 200."""
|
||||
app = _setup_app_with_fakeredis()
|
||||
client: FlaskClient = app.test_client()
|
||||
|
||||
payload = {
|
||||
"update_id": 123456789,
|
||||
"message": {
|
||||
"message_id": 100,
|
||||
"from": {
|
||||
"id": 987654321,
|
||||
"first_name": "TestUser",
|
||||
},
|
||||
"chat": {
|
||||
"id": 987654321,
|
||||
"first_name": "TestUser",
|
||||
"type": "private",
|
||||
},
|
||||
"date": 1719000000,
|
||||
"text": "Hallo BSN-Bot!",
|
||||
},
|
||||
}
|
||||
|
||||
response = client.post("/webhook/telegram", json=payload)
|
||||
assert response.status_code == 200
|
||||
data = response.get_json()
|
||||
assert data is not None
|
||||
assert data["status"] == "ok"
|
||||
|
||||
|
||||
def test_missing_json_returns_400() -> None:
|
||||
"""POST /webhook/telegram ohne JSON-Body → 400."""
|
||||
app = _setup_app_with_fakeredis()
|
||||
client: FlaskClient = app.test_client()
|
||||
|
||||
response = client.post("/webhook/telegram", data="not json", content_type="text/plain")
|
||||
assert response.status_code == 400
|
||||
data = response.get_json()
|
||||
assert data is not None
|
||||
assert "error" in data
|
||||
|
||||
|
||||
def test_missing_message_id_returns_400() -> None:
|
||||
"""POST /webhook/telegram ohne message_id → 400."""
|
||||
app = _setup_app_with_fakeredis()
|
||||
client: FlaskClient = app.test_client()
|
||||
|
||||
payload = {
|
||||
"message": {
|
||||
"chat": {"id": 123},
|
||||
"text": "Hallo",
|
||||
}
|
||||
}
|
||||
|
||||
response = client.post("/webhook/telegram", json=payload)
|
||||
assert response.status_code == 400
|
||||
data = response.get_json()
|
||||
assert data is not None
|
||||
assert "message_id" in data["error"]
|
||||
|
||||
|
||||
def test_empty_text_returns_400() -> None:
|
||||
"""POST /webhook/telegram mit leerem text → 400 (min_length=1 ValidationError)."""
|
||||
app = _setup_app_with_fakeredis()
|
||||
client: FlaskClient = app.test_client()
|
||||
|
||||
payload = {
|
||||
"message": {
|
||||
"message_id": 200,
|
||||
"chat": {"id": 456},
|
||||
"text": "",
|
||||
}
|
||||
}
|
||||
|
||||
response = client.post("/webhook/telegram", json=payload)
|
||||
assert response.status_code == 400
|
||||
data = response.get_json()
|
||||
assert data is not None
|
||||
assert "error" in data
|
||||
Reference in New Issue
Block a user