feat: Queue-Worker mit Batch-Verarbeitung und Fehlertoleranz

This commit is contained in:
Hermes Agent
2026-06-22 06:34:40 +02:00
parent 3e360911f5
commit e382201878
4 changed files with 226 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
"""Tests für Queue-Worker mit fakeredis."""
from __future__ import annotations
from collections.abc import Generator
from datetime import datetime
from unittest.mock import patch
import fakeredis
import pytest
from src.models.submission import Submission
from src.queue.handler import QueueHandler
from src.queue.worker import verarbeite_submission, worker_loop
class BreakLoopError(Exception):
"""Exception zum Durchbrechen der worker_loop-Endlosschleife in Tests."""
@pytest.fixture
def handler() -> Generator[QueueHandler]:
"""QueueHandler mit fakeredis-Backend."""
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()
yield qh
fake_redis.flushall()
def _make_submission(message_id: str = "msg-001") -> Submission:
"""Erzeugt eine Test-Submission."""
return Submission(
message_id=message_id,
plattform="whatsapp",
absender="+491****7890",
text="Testnachricht für Queue-Worker.",
zeitstempel=datetime(2025, 1, 1, 12, 0, 0),
)
def test_verarbeite_submission_setzt_status_auf_erledigt() -> None:
"""verarbeite_submission setzt den Status auf 'erledigt'."""
sub = _make_submission("msg-001")
assert sub.status == "neu"
result = verarbeite_submission(sub)
assert result is True
assert sub.status == "erledigt"
def test_worker_loop_verarbeitet_alle_submissions(handler: QueueHandler) -> None:
"""worker_loop verarbeitet alle Submissions in der Queue."""
# Mehrere Submissions in die Queue einreihen
subs = [_make_submission(f"msg-w{i}") for i in range(3)]
for sub in subs:
handler.enqueue(sub)
# Verifiziere: Queue enthält 3 Einträge
assert handler.queue_size() == 3
# time.sleep so mocken, dass die Schleife nach einer Leer-Poll-Iteration abbricht
def _raise_on_sleep(seconds: int) -> None: # noqa: ARG001
raise BreakLoopError
with patch("src.queue.worker.time.sleep", side_effect=_raise_on_sleep):
try:
worker_loop(handler, poll_interval=1, batch_size=5)
except BreakLoopError:
pass
# Nach Verarbeitung: dequeue liefert keine neuen Messages mehr
# (alle wurden per XACK bestätigt und sind nicht mehr "new")
remaining = handler.dequeue(count=10)
assert len(remaining) == 0, (
f"Erwartet 0 verbleibende Submissions, aber {len(remaining)} gefunden"
)
def test_worker_loop_stoppt_bei_leerer_queue(handler: QueueHandler) -> None:
"""worker_loop pollt leere Queue ohne Fehler (bricht nach erster sleep-Iteration ab)."""
call_count = 0
def _raise_after_first_sleep(seconds: int) -> None: # noqa: ARG001
nonlocal call_count
call_count += 1
raise BreakLoopError
with patch("src.queue.worker.time.sleep", side_effect=_raise_after_first_sleep):
try:
worker_loop(handler, poll_interval=1, batch_size=5)
except BreakLoopError:
pass
# Sicherstellen, dass sleep genau einmal aufgerufen wurde (leere Queue → sleep)
assert call_count == 1