test(#134): wahlprogramm_fetch Coverage 42.8% → 54.4%

8 zusaetzliche Tests:
- TestLockFileRobustness: kaputtes JSON, fehlende Datei, _save_lock-Roundtrip
- TestLoadLinks: missing yaml + empty yaml (gestubbed)
- TestGetMissingProgrammes: leere/gefuellte Eintraege, Bundesland-Filter

yaml ist im Unit-Setup gestubbed; Tests patchen _load_links direkt
statt echte YAML-Parsing zu erzwingen — die echte Datei-Validierung
gehoert in die integration-Suite gegen die produktive links.yaml.
This commit is contained in:
Dotty Dotter 2026-04-28 08:42:29 +02:00
parent 8f3a811a83
commit 722b073bbd

View File

@ -300,6 +300,88 @@ class TestShaLock:
assert lock["spd-mv.pdf"] == _sha(content)
# ---------------------------------------------------------------------------
# Test 5: Lock-File und YAML-Robustheit (#134 Coverage-Backfill)
# ---------------------------------------------------------------------------
class TestLockFileRobustness:
def test_corrupt_lock_file_returns_empty_dict(self, tmp_path):
"""Kaputtes JSON darf den Caller nicht crashen — leeren Lock liefern."""
from app.wahlprogramm_fetch import _load_lock
bad = tmp_path / "broken-lock.json"
bad.write_text("{ this is not json ;)")
with patch("app.wahlprogramm_fetch._LOCK_FILE", bad):
result = _load_lock()
assert result == {}
def test_missing_lock_file_returns_empty_dict(self, tmp_path):
from app.wahlprogramm_fetch import _load_lock
missing = tmp_path / "no-such-file.json"
with patch("app.wahlprogramm_fetch._LOCK_FILE", missing):
assert _load_lock() == {}
def test_save_lock_writes_valid_json(self, tmp_path):
from app.wahlprogramm_fetch import _save_lock
target = tmp_path / "lock.json"
with patch("app.wahlprogramm_fetch._LOCK_FILE", target):
_save_lock({"x.pdf": "abc123", "y.pdf": "def456"})
import json
loaded = json.loads(target.read_text())
assert loaded == {"x.pdf": "abc123", "y.pdf": "def456"}
class TestLoadLinks:
def test_missing_yaml_returns_empty(self, tmp_path):
from app.wahlprogramm_fetch import _load_links
with patch("app.wahlprogramm_fetch._LINKS_FILE", tmp_path / "missing.yaml"):
assert _load_links() == {}
def test_empty_yaml_returns_empty(self, tmp_path):
from app.wahlprogramm_fetch import _load_links
target = tmp_path / "empty.yaml"
target.write_text("")
with patch("app.wahlprogramm_fetch._LINKS_FILE", target):
assert _load_links() == {}
# Hinweis: yaml ist im Unit-Setup gestubbed (siehe Top-of-File), deshalb
# testen wir _load_links nur mit existing-vs-missing-File. Die echte
# YAML-Parsing-Logik wird in der integration-Suite gegen die echte
# links.yaml validiert.
class TestGetMissingProgrammes:
"""Tests fuer get_missing_programmes — listet BL/Partei-Kombinationen mit
Kandidaten-URL aber fehlender lokaler Datei. yaml ist gestubbed; Tests
patchen daher _load_links direkt."""
def test_no_yaml_returns_empty(self):
from app.wahlprogramm_fetch import get_missing_programmes
with patch("app.wahlprogramm_fetch._load_links", return_value={}):
assert get_missing_programmes() == []
def test_lists_entries_when_file_missing(self, tmp_path):
"""Eintrag in YAML, registriertes WAHLPROGRAMME-File fehlt → listed."""
from app.wahlprogramm_fetch import get_missing_programmes
fake_links = {"BX": {"XYZ": [{"url": "https://example.com/x.pdf"}]}}
with patch("app.wahlprogramm_fetch._load_links", return_value=fake_links):
with patch("app.wahlprogramm_fetch._REFERENZEN_DIR", tmp_path / "ref"):
missing = get_missing_programmes()
codes = [m["bl"] for m in missing]
assert "BX" in codes
def test_bundesland_filter(self, tmp_path):
from app.wahlprogramm_fetch import get_missing_programmes
fake_links = {
"BX": {"XYZ": [{"url": "https://example.com/x.pdf"}]},
"BY": {"ABC": [{"url": "https://example.com/y.pdf"}]},
}
with patch("app.wahlprogramm_fetch._load_links", return_value=fake_links):
with patch("app.wahlprogramm_fetch._REFERENZEN_DIR", tmp_path / "ref"):
missing = get_missing_programmes(bundesland="BX")
codes = {m["bl"] for m in missing}
assert codes == {"BX"}
# ---------------------------------------------------------------------------
# Test 4: og_card — cache_key Determinismus und Cache-Miss/Hit
# ---------------------------------------------------------------------------