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.
This commit is contained in:
parent
b13b46a444
commit
698562b1f5
@ -93,3 +93,81 @@ class TestDelete:
|
||||
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
|
||||
|
||||
@ -156,3 +156,55 @@ class TestInitialSeed:
|
||||
seed = [_make_assessment("18/1"), _make_assessment("18/2")]
|
||||
repo = InMemoryAntragRepository(initial=seed)
|
||||
assert len(_run(repo.list())) == 2
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# SqliteAntragRepository — Delegation an database.* (#134 Coverage-Backfill)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestSqliteAntragRepositoryDelegation:
|
||||
def test_save_delegates(self):
|
||||
from unittest.mock import AsyncMock, patch
|
||||
from app.repositories.antrag_repository import SqliteAntragRepository
|
||||
with patch("app.repositories.antrag_repository.database.upsert_assessment",
|
||||
new=AsyncMock(return_value=True)) as m:
|
||||
assert _run(SqliteAntragRepository().save({"drucksache": "x"})) is True
|
||||
m.assert_called_once_with({"drucksache": "x"})
|
||||
|
||||
def test_get_delegates(self):
|
||||
from unittest.mock import AsyncMock, patch
|
||||
from app.repositories.antrag_repository import SqliteAntragRepository
|
||||
with patch("app.repositories.antrag_repository.database.get_assessment",
|
||||
new=AsyncMock(return_value={"x": 1})) as m:
|
||||
assert _run(SqliteAntragRepository().get("18/1")) == {"x": 1}
|
||||
m.assert_called_once_with("18/1")
|
||||
|
||||
def test_list_delegates(self):
|
||||
from unittest.mock import AsyncMock, patch
|
||||
from app.repositories.antrag_repository import SqliteAntragRepository
|
||||
with patch("app.repositories.antrag_repository.database.get_all_assessments",
|
||||
new=AsyncMock(return_value=[])) as m:
|
||||
_run(SqliteAntragRepository().list("NRW"))
|
||||
m.assert_called_once_with("NRW")
|
||||
|
||||
def test_search_delegates(self):
|
||||
from unittest.mock import AsyncMock, patch
|
||||
from app.repositories.antrag_repository import SqliteAntragRepository
|
||||
with patch("app.repositories.antrag_repository.database.search_assessments",
|
||||
new=AsyncMock(return_value=[])) as m:
|
||||
_run(SqliteAntragRepository().search("klima", "NRW", 25))
|
||||
m.assert_called_once_with("klima", "NRW", 25)
|
||||
|
||||
def test_delete_delegates(self):
|
||||
from unittest.mock import AsyncMock, patch
|
||||
from app.repositories.antrag_repository import SqliteAntragRepository
|
||||
with patch("app.repositories.antrag_repository.database.delete_assessment",
|
||||
new=AsyncMock(return_value=True)) as m:
|
||||
_run(SqliteAntragRepository().delete("18/1"))
|
||||
m.assert_called_once_with("18/1")
|
||||
|
||||
|
||||
def test_get_antrag_repository_returns_singleton():
|
||||
from app.repositories.antrag_repository import get_antrag_repository
|
||||
assert get_antrag_repository() is get_antrag_repository()
|
||||
|
||||
@ -225,3 +225,90 @@ class TestExportLongFormat:
|
||||
# Generic FREIE WÄHLER darf in der Zeile NICHT auftauchen
|
||||
bb_lines = [l for l in csv_text.splitlines() if "BB" in l and "8/2," in l]
|
||||
assert any("BVB-FW" in l for l in bb_lines)
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Edge-Cases (#134 Coverage-Backfill)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestLoadAssessmentsRobustness:
|
||||
"""_load_assessments toleriert kaputte JSON-Eintraege im fraktionen-Feld."""
|
||||
|
||||
def test_invalid_json_in_fraktionen_falls_back_to_empty(self, tmp_path):
|
||||
from app.auswertungen import _load_assessments
|
||||
db = tmp_path / "broken.db"
|
||||
conn = sqlite3.connect(str(db))
|
||||
conn.execute("""
|
||||
CREATE TABLE assessments (
|
||||
drucksache TEXT PRIMARY KEY, title TEXT,
|
||||
fraktionen TEXT, datum TEXT, bundesland TEXT,
|
||||
gwoe_score REAL, link TEXT, gwoe_begruendung TEXT,
|
||||
gwoe_matrix TEXT, gwoe_schwerpunkt TEXT,
|
||||
wahlprogramm_scores TEXT, verbesserungen TEXT,
|
||||
staerken TEXT, schwaechen TEXT, empfehlung TEXT,
|
||||
empfehlung_symbol TEXT, verbesserungspotenzial TEXT,
|
||||
themen TEXT, antrag_zusammenfassung TEXT,
|
||||
antrag_kernpunkte TEXT, source TEXT, model TEXT,
|
||||
created_at TEXT, updated_at TEXT
|
||||
)
|
||||
""")
|
||||
# fraktionen-Feld enthaelt kein gueltiges JSON
|
||||
conn.execute(
|
||||
"INSERT INTO assessments (drucksache, bundesland, datum, fraktionen, gwoe_score) "
|
||||
"VALUES (?, ?, ?, ?, ?)",
|
||||
("18/777", "NRW", "2024-01-01", "{not json", 5.0),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
rows = _load_assessments(db)
|
||||
assert len(rows) == 1
|
||||
assert rows[0]["fraktionen"] == [] # Fallback
|
||||
|
||||
|
||||
class TestAggregateMatrixSkipsBlanks:
|
||||
def test_skips_assessments_without_bundesland(self, tmp_path):
|
||||
"""Anträge ohne bundesland werden ignoriert (continue-Branch line 115)."""
|
||||
from app.auswertungen import aggregate_matrix
|
||||
db = tmp_path / "blanks.db"
|
||||
conn = sqlite3.connect(str(db))
|
||||
conn.execute("""
|
||||
CREATE TABLE assessments (
|
||||
drucksache TEXT PRIMARY KEY, title TEXT,
|
||||
fraktionen TEXT, datum TEXT, bundesland TEXT,
|
||||
gwoe_score REAL, link TEXT, gwoe_begruendung TEXT,
|
||||
gwoe_matrix TEXT, gwoe_schwerpunkt TEXT,
|
||||
wahlprogramm_scores TEXT, verbesserungen TEXT,
|
||||
staerken TEXT, schwaechen TEXT, empfehlung TEXT,
|
||||
empfehlung_symbol TEXT, verbesserungspotenzial TEXT,
|
||||
themen TEXT, antrag_zusammenfassung TEXT,
|
||||
antrag_kernpunkte TEXT, source TEXT, model TEXT,
|
||||
created_at TEXT, updated_at TEXT
|
||||
)
|
||||
""")
|
||||
conn.execute(
|
||||
"INSERT INTO assessments (drucksache, bundesland, datum, fraktionen, gwoe_score) "
|
||||
"VALUES (?, ?, ?, ?, ?)",
|
||||
("X/1", None, "2024-01-01", '["CDU"]', 7.0), # bundesland NULL
|
||||
)
|
||||
conn.execute(
|
||||
"INSERT INTO assessments (drucksache, bundesland, datum, fraktionen, gwoe_score) "
|
||||
"VALUES (?, ?, ?, ?, ?)",
|
||||
("18/1", "NRW", "2024-01-01", '["CDU"]', 7.0),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
m = aggregate_matrix(db_path=db)
|
||||
assert m["total"] == 1 # nur der NRW-Eintrag
|
||||
assert m["bundeslaender"] == ["NRW"]
|
||||
|
||||
|
||||
class TestGetWahlperioden:
|
||||
def test_returns_sorted_list(self, sample_db):
|
||||
from app.auswertungen import get_wahlperioden
|
||||
wps = get_wahlperioden(db_path=sample_db)
|
||||
assert wps == sorted(wps)
|
||||
# Sample-DB enthaelt NRW-WP18, MV-WP8, MV-WP7 sowie BB-WP8
|
||||
assert any("NRW" in w for w in wps)
|
||||
|
||||
@ -47,3 +47,22 @@ class TestVersionHistory:
|
||||
rows_b = _run(repo.versions("18/2"))
|
||||
assert len(rows_a) == 1 and rows_a[0]["gwoe_score"] == 5.0
|
||||
assert len(rows_b) == 1 and rows_b[0]["gwoe_score"] == 8.0
|
||||
|
||||
|
||||
# ─── SqliteBewertungRepository — Delegation (#134 Coverage-Backfill) ──────────
|
||||
|
||||
|
||||
class TestSqliteBewertungRepositoryDelegation:
|
||||
def test_versions_delegates(self):
|
||||
from unittest.mock import AsyncMock, patch
|
||||
from app.repositories.bewertung_repository import SqliteBewertungRepository
|
||||
fake = [{"version": 1, "gwoe_score": 5.0}]
|
||||
with patch("app.repositories.bewertung_repository.database.get_assessment_history",
|
||||
new=AsyncMock(return_value=fake)) as m:
|
||||
assert _run(SqliteBewertungRepository().versions("18/1")) == fake
|
||||
m.assert_called_once_with("18/1")
|
||||
|
||||
|
||||
def test_get_bewertung_repository_returns_singleton():
|
||||
from app.repositories.bewertung_repository import get_bewertung_repository
|
||||
assert get_bewertung_repository() is get_bewertung_repository()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user