gwoe-antragspruefer/tests/test_abonnement_repository.py
Dotty Dotter 698562b1f5 test(#134): Coverage-Backfill auswertungen + Repositories
- app/auswertungen.py 87.4% → 97.9%
  - TestLoadAssessmentsRobustness: ungueltiges JSON in fraktionen-Spalte
    fallback to []
  - TestAggregateMatrixSkipsBlanks: bundesland-NULL-Eintrag wird ignoriert
  - TestGetWahlperioden: sortierte Liste

- app/repositories/abonnement_repository.py 85.2% → 100%
- app/repositories/antrag_repository.py 87.0% → 98.1%
- app/repositories/bewertung_repository.py 90% → 100%

Pattern fuer Sqlite-Repos: AsyncMock auf database.X-Funktion, dann
pruefen dass die Methode korrekt delegiert (Argumente, Return-Wert).
Trivial wrappers, aber jetzt auditierbar.

Total: 48.7% → 49.2%, 686 → 705 Tests.
2026-04-28 10:54:28 +02:00

174 lines
7.3 KiB
Python

"""Tests für AbonnementRepository (#136, ADR 0008)."""
from __future__ import annotations
import asyncio
from app.repositories import (
AbonnementRepository,
InMemoryAbonnementRepository,
SqliteAbonnementRepository,
)
def _run(coro):
return asyncio.get_event_loop().run_until_complete(coro)
class TestProtocolConformance:
def test_in_memory_implements_protocol(self):
repo = InMemoryAbonnementRepository()
assert isinstance(repo, AbonnementRepository)
def test_sqlite_implements_protocol(self):
repo = SqliteAbonnementRepository()
assert isinstance(repo, AbonnementRepository)
class TestCreateAndList:
def test_create_returns_id(self):
repo = InMemoryAbonnementRepository()
sid = _run(repo.create("u1", "a@b.de"))
assert sid == 1
def test_create_increments_id(self):
repo = InMemoryAbonnementRepository()
s1 = _run(repo.create("u1", "a@b.de"))
s2 = _run(repo.create("u1", "c@d.de"))
assert s2 == s1 + 1
def test_list_by_user_filters(self):
repo = InMemoryAbonnementRepository()
_run(repo.create("u1", "a@b.de"))
_run(repo.create("u2", "c@d.de"))
rows = _run(repo.list_by_user("u1"))
assert len(rows) == 1
assert rows[0]["email"] == "a@b.de"
def test_list_all_returns_every_sub(self):
repo = InMemoryAbonnementRepository()
_run(repo.create("u1", "a@b.de"))
_run(repo.create("u2", "c@d.de"))
assert len(_run(repo.list_all())) == 2
class TestListDue:
def test_list_due_returns_unsent(self):
repo = InMemoryAbonnementRepository()
_run(repo.create("u1", "a@b.de"))
due = _run(repo.list_due())
assert len(due) == 1
def test_mark_sent_removes_from_due(self):
repo = InMemoryAbonnementRepository()
sid = _run(repo.create("u1", "a@b.de"))
_run(repo.mark_sent(sid))
assert _run(repo.list_due()) == []
def test_list_due_filters_by_frequency(self):
repo = InMemoryAbonnementRepository()
_run(repo.create("u1", "a@b.de", frequency="daily"))
_run(repo.create("u2", "c@d.de", frequency="weekly"))
daily = _run(repo.list_due("daily"))
weekly = _run(repo.list_due("weekly"))
assert len(daily) == 1 and daily[0]["email"] == "a@b.de"
assert len(weekly) == 1 and weekly[0]["email"] == "c@d.de"
class TestDelete:
def test_delete_checks_ownership(self):
repo = InMemoryAbonnementRepository()
sid = _run(repo.create("u1", "a@b.de"))
# Fremder User kann nicht löschen
assert _run(repo.delete("u2", sid)) is False
# Eigentümer kann löschen
assert _run(repo.delete("u1", sid)) is True
def test_delete_by_id_skips_ownership_check(self):
repo = InMemoryAbonnementRepository()
sid = _run(repo.create("u1", "a@b.de"))
# delete_by_id ist für Unsubscribe-Links (Token-gesichert), nicht für
# den Self-Service; kein User-Check
assert _run(repo.delete_by_id(sid)) is True
def test_delete_by_id_missing_returns_false(self):
repo = InMemoryAbonnementRepository()
assert _run(repo.delete_by_id(999)) is False
# ─────────────────────────────────────────────────────────────────────────────
# SqliteAbonnementRepository — Delegation an database.* (#134 Coverage-Backfill)
# ─────────────────────────────────────────────────────────────────────────────
class TestSqliteAbonnementRepositoryDelegation:
"""Die Sqlite-Variante ist nur ein duenner Wrapper um Module-Funktionen
in app.database. Test prueft dass jede Methode korrekt delegiert,
ohne echte DB-Calls (Module-Funktionen werden gemockt)."""
def test_create_delegates(self):
from unittest.mock import AsyncMock, patch
from app.repositories.abonnement_repository import SqliteAbonnementRepository
repo = SqliteAbonnementRepository()
with patch("app.repositories.abonnement_repository.database.create_subscription",
new=AsyncMock(return_value=42)) as m:
result = _run(repo.create("u1", "a@b.de", "NRW", "CDU", "weekly"))
assert result == 42
m.assert_called_once_with("u1", "a@b.de", "NRW", "CDU", "weekly")
def test_list_by_user_delegates(self):
from unittest.mock import AsyncMock, patch
from app.repositories.abonnement_repository import SqliteAbonnementRepository
repo = SqliteAbonnementRepository()
fake = [{"id": 1, "email": "x@y"}]
with patch("app.repositories.abonnement_repository.database.list_subscriptions",
new=AsyncMock(return_value=fake)) as m:
assert _run(repo.list_by_user("u1")) == fake
m.assert_called_once_with("u1")
def test_list_all_delegates(self):
from unittest.mock import AsyncMock, patch
from app.repositories.abonnement_repository import SqliteAbonnementRepository
with patch("app.repositories.abonnement_repository.database.list_all_subscriptions",
new=AsyncMock(return_value=[])) as m:
assert _run(SqliteAbonnementRepository().list_all()) == []
m.assert_called_once_with()
def test_list_due_delegates(self):
from unittest.mock import AsyncMock, patch
from app.repositories.abonnement_repository import SqliteAbonnementRepository
with patch("app.repositories.abonnement_repository.database.get_all_subscriptions_due",
new=AsyncMock(return_value=[])) as m:
_run(SqliteAbonnementRepository().list_due("weekly"))
m.assert_called_once_with("weekly")
def test_delete_delegates(self):
from unittest.mock import AsyncMock, patch
from app.repositories.abonnement_repository import SqliteAbonnementRepository
with patch("app.repositories.abonnement_repository.database.delete_subscription",
new=AsyncMock(return_value=True)) as m:
_run(SqliteAbonnementRepository().delete("u1", 5))
m.assert_called_once_with("u1", 5)
def test_delete_by_id_delegates(self):
from unittest.mock import AsyncMock, patch
from app.repositories.abonnement_repository import SqliteAbonnementRepository
with patch("app.repositories.abonnement_repository.database.delete_subscription_by_id",
new=AsyncMock(return_value=False)) as m:
_run(SqliteAbonnementRepository().delete_by_id(99))
m.assert_called_once_with(99)
def test_mark_sent_delegates(self):
from unittest.mock import AsyncMock, patch
from app.repositories.abonnement_repository import SqliteAbonnementRepository
with patch("app.repositories.abonnement_repository.database.mark_subscription_sent",
new=AsyncMock(return_value=None)) as m:
_run(SqliteAbonnementRepository().mark_sent(7))
m.assert_called_once_with(7)
def test_get_abonnement_repository_returns_singleton():
from app.repositories.abonnement_repository import get_abonnement_repository
a = get_abonnement_repository()
b = get_abonnement_repository()
assert a is b