"""Tests for wahlprogramme.py — registry consistency + file existence.""" import pytest from app.wahlprogramme import ( WAHLPROGRAMME, REFERENZEN_PATH, get_wahlprogramm, parteien_mit_wahlprogramm, ) # ───────────────────────────────────────────────────────────────────────────── # Registry consistency # ───────────────────────────────────────────────────────────────────────────── class TestRegistryStructure: def test_active_bundeslaender_present(self): for code in ["NRW", "LSA", "MV", "BE"]: assert code in WAHLPROGRAMME, f"missing wahlprogramme entry for {code}" def test_each_entry_has_required_keys(self): required = {"file", "titel", "partei", "jahr", "seiten"} for bl, parteien in WAHLPROGRAMME.items(): for partei, info in parteien.items(): missing = required - set(info.keys()) assert not missing, f"{bl}/{partei} missing keys: {missing}" def test_jahr_is_integer(self): for bl, parteien in WAHLPROGRAMME.items(): for partei, info in parteien.items(): assert isinstance(info["jahr"], int), f"{bl}/{partei} jahr not int" def test_seiten_is_positive_integer(self): for bl, parteien in WAHLPROGRAMME.items(): for partei, info in parteien.items(): assert isinstance(info["seiten"], int) assert info["seiten"] > 0 def test_file_extension_is_pdf(self): for bl, parteien in WAHLPROGRAMME.items(): for partei, info in parteien.items(): assert info["file"].endswith(".pdf") # ───────────────────────────────────────────────────────────────────────────── # File existence — every registered file must exist on disk # ───────────────────────────────────────────────────────────────────────────── class TestFileExistence: """Catches typos in the file field that would silently break embedding indexing or PDF download links.""" def test_every_registered_pdf_exists(self): missing = [] for bl, parteien in WAHLPROGRAMME.items(): for partei, info in parteien.items(): path = REFERENZEN_PATH / info["file"] if not path.exists(): missing.append(f"{bl}/{partei}: {info['file']}") assert not missing, "missing PDFs:\n " + "\n ".join(missing) # ───────────────────────────────────────────────────────────────────────────── # Lookup helpers # ───────────────────────────────────────────────────────────────────────────── class TestGetWahlprogramm: def test_returns_dict_for_known_combination(self): info = get_wahlprogramm("MV", "CDU") assert info is not None assert info["partei"] == "CDU Mecklenburg-Vorpommern" def test_returns_none_for_unknown_bundesland(self): assert get_wahlprogramm("XX", "CDU") is None def test_returns_none_for_unknown_partei(self): assert get_wahlprogramm("NRW", "BSW") is None class TestParteienMitWahlprogramm: def test_nrw_has_five_parteien(self): parteien = parteien_mit_wahlprogramm("NRW") assert len(parteien) == 5 assert set(parteien) == {"CDU", "SPD", "GRÜNE", "FDP", "AfD"} def test_mv_has_six_parteien(self): parteien = parteien_mit_wahlprogramm("MV") assert set(parteien) == {"CDU", "SPD", "GRÜNE", "FDP", "AfD", "LINKE"} def test_be_has_five_parteien(self): parteien = parteien_mit_wahlprogramm("BE") assert set(parteien) == {"CDU", "SPD", "GRÜNE", "LINKE", "AfD"} def test_unknown_bundesland_empty_list(self): assert parteien_mit_wahlprogramm("XX") == [] # ───────────────────────────────────────────────────────────────────────────── # embeddings.PROGRAMME consistency cross-check # ───────────────────────────────────────────────────────────────────────────── class TestEmbeddingsRegistryConsistency: """Every entry in WAHLPROGRAMME must also exist in embeddings.PROGRAMME so the indexer can find it. Mismatch is the kind of bug a manual smoke misses but would show up during indexing.""" def test_every_wahlprogramm_has_embeddings_entry(self): from app.embeddings import PROGRAMME # Build expected programm_id from filename: "cdu-mv-2021.pdf" → "cdu-mv-2021" missing = [] for bl, parteien in WAHLPROGRAMME.items(): for partei, info in parteien.items(): pid = info["file"].rsplit(".", 1)[0] if pid not in PROGRAMME: missing.append(f"{bl}/{partei} → {pid}") assert not missing, ( "WAHLPROGRAMME entries missing in embeddings.PROGRAMME:\n " + "\n ".join(missing) ) # ───────────────────────────────────────────────────────────────────────────── # load_wahlprogramm_text — Fallback-Pfade (#134 Coverage-Backfill) # ───────────────────────────────────────────────────────────────────────────── class TestLoadWahlprogrammText: def test_returns_empty_for_unknown_combination(self): from app.wahlprogramme import load_wahlprogramm_text assert load_wahlprogramm_text("XX", "XYZ") == {} def test_paged_textfile_used_when_present(self, tmp_path, monkeypatch): """Wenn die paged-Textdatei existiert, wird sie genutzt. Format: '--- PAGE N ---'-Marker pro Seitenanfang.""" from app import wahlprogramme as wp_mod # Mock get_wahlprogramm -> bekannte Datei monkeypatch.setattr(wp_mod, "get_wahlprogramm", lambda bl, p: {"file": "test.pdf"}) paged = tmp_path / "test-paged.txt" paged.write_text("--- PAGE 1 ---\nseite eins\n--- PAGE 2 ---\nseite zwei") monkeypatch.setattr(wp_mod, "KONTEXT_PATH", tmp_path) result = wp_mod.load_wahlprogramm_text("X", "Y") assert 2 in result assert "seite zwei" in result[2] def test_falls_back_to_normal_textfile(self, tmp_path, monkeypatch): """Ohne paged-Datei wird auf normale .txt-Datei zurueckgefallen, komplett unter Seite 1.""" from app import wahlprogramme as wp_mod monkeypatch.setattr(wp_mod, "get_wahlprogramm", lambda bl, p: {"file": "test.pdf"}) normal = tmp_path / "test.txt" normal.write_text("flacher text ohne seitenmarker") monkeypatch.setattr(wp_mod, "KONTEXT_PATH", tmp_path) result = wp_mod.load_wahlprogramm_text("X", "Y") assert result == {1: "flacher text ohne seitenmarker"} def test_returns_empty_when_no_textfile(self, tmp_path, monkeypatch): """Weder paged- noch normale Textdatei → leeres Dict.""" from app import wahlprogramme as wp_mod monkeypatch.setattr(wp_mod, "get_wahlprogramm", lambda bl, p: {"file": "test.pdf"}) # tmp_path ist leer monkeypatch.setattr(wp_mod, "KONTEXT_PATH", tmp_path) assert wp_mod.load_wahlprogramm_text("X", "Y") == {} class TestSearchWahlprogramm: def test_returns_empty_for_unknown_combination(self): from app.wahlprogramme import search_wahlprogramm assert search_wahlprogramm("XX", "XYZ", ["test"]) == [] def test_returns_empty_when_text_missing(self, monkeypatch): """Bekannte Partei + Bundesland aber keine Textdatei → leer.""" from app import wahlprogramme as wp_mod monkeypatch.setattr(wp_mod, "get_wahlprogramm", lambda bl, p: {"file": "missing.pdf"}) monkeypatch.setattr(wp_mod, "load_wahlprogramm_text", lambda bl, p: {}) assert wp_mod.search_wahlprogramm("X", "Y", ["test"]) == [] class TestFindRelevantQuotes: def test_unknown_bundesland_raises(self): from app.wahlprogramme import find_relevant_quotes with pytest.raises(ValueError, match="Unbekanntes Bundesland"): find_relevant_quotes("Antrag-Text", ["CDU"], bundesland="ZZ") class TestFormatQuoteForPrompt: def test_empty_quotes_returns_empty_string(self): from app.wahlprogramme import format_quote_for_prompt assert format_quote_for_prompt({}) == ""