"""Tests for wahlprogramme.py. Nach dem #222-Refactor ist dieses Modul nur noch ein dünner Wrapper: keyword-basierte Suche + PDF-Text-Loader + ein Compat-Adapter ``get_wahlprogramm`` der zu ``programme.aktuelles_wahlprogramm`` delegiert. Die Stamm-Daten (``WAHLPROGRAMME``-Literal) sind nach ``programme.PROGRAMME`` gewandert. Strukturelle Daten-Tests leben deshalb in ``test_programme.py``. """ import pytest from app.wahlprogramme import ( REFERENZEN_PATH, get_wahlprogramm, load_wahlprogramm_text, search_wahlprogramm, find_relevant_quotes, format_quote_for_prompt, ) from app.programme import parteien_mit_wahlprogramm # ───────────────────────────────────────────────────────────────────────────── # Stichproben aktiver Bundesländer + zugeordnete Parteien # ───────────────────────────────────────────────────────────────────────────── class TestParteienMitWahlprogramm: """Smoke-Test: in den jeweiligen BL liegen die erwarteten Fraktionen. Strikte Schema-Tests gegen ``programme.PROGRAMME`` in test_programme.py.""" def test_nrw_has_five_parteien(self): parteien = parteien_mit_wahlprogramm("NRW") assert set(parteien) == {"CDU", "SPD", "GRÜNE", "FDP", "AfD"} def test_bund_has_eight_parteien(self): parteien = parteien_mit_wahlprogramm("BUND") assert set(parteien) == {"CDU", "CSU", "SPD", "GRÜNE", "FDP", "AfD", "LINKE", "BSW"} def test_hb_has_five_parteien(self): # AfD war wegen Listenstreit nicht zur Bürgerschaftswahl 2023 zugelassen. # Stattdessen ist BiW (Bürger in Wut) als 6. Fraktion in der 21. WP. parteien = parteien_mit_wahlprogramm("HB") assert set(parteien) == {"SPD", "CDU", "GRÜNE", "LINKE", "BiW"} def test_unknown_bundesland_empty_list(self): assert parteien_mit_wahlprogramm("XX") == [] # ───────────────────────────────────────────────────────────────────────────── # get_wahlprogramm — Compat-Adapter # ───────────────────────────────────────────────────────────────────────────── class TestGetWahlprogramm: def test_returns_programm_for_known_combination(self): prog = get_wahlprogramm("NRW", "CDU") assert prog is not None assert prog["pdf"] == "cdu-nrw-2022.pdf" assert prog["partei"] == "CDU" assert prog["bundesland"] == "NRW" 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 # BSW nicht im NRW-Landtag # ───────────────────────────────────────────────────────────────────────────── # File existence — every registered pdf must exist on disk # ───────────────────────────────────────────────────────────────────────────── class TestFileExistence: """Catches typos im pdf-Feld der Programm-Registry, die das Indexing oder PDF-Download silently brechen würden.""" def test_every_registered_pdf_exists(self): from app.programme import all_programme missing = [] for prog in all_programme(): pdf = prog.get("pdf") if not pdf: continue path = REFERENZEN_PATH / pdf if not path.exists(): missing.append(f"{prog['id']}: {pdf}") assert not missing, "missing PDFs:\n " + "\n ".join(missing) # ───────────────────────────────────────────────────────────────────────────── # load_wahlprogramm_text — Fallback-Pfade (#134 Coverage-Backfill) # ───────────────────────────────────────────────────────────────────────────── class TestLoadWahlprogrammText: def test_returns_empty_for_unknown_combination(self): 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: {"pdf": "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 zurückgefallen, komplett unter Seite 1.""" from app import wahlprogramme as wp_mod monkeypatch.setattr(wp_mod, "get_wahlprogramm", lambda bl, p: {"pdf": "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 flat-Textdatei vorhanden → leeres Dict.""" from app import wahlprogramme as wp_mod monkeypatch.setattr(wp_mod, "get_wahlprogramm", lambda bl, p: {"pdf": "test.pdf"}) monkeypatch.setattr(wp_mod, "KONTEXT_PATH", tmp_path) result = wp_mod.load_wahlprogramm_text("X", "Y") assert result == {} # ───────────────────────────────────────────────────────────────────────────── # search_wahlprogramm — Edge cases # ───────────────────────────────────────────────────────────────────────────── class TestSearchWahlprogramm: def test_returns_empty_for_unknown_combination(self): result = search_wahlprogramm("XX", "XYZ", ["foo"]) assert result == [] def test_returns_empty_when_text_missing(self, monkeypatch): """get_wahlprogramm liefert ein Programm, aber kein paged-Text: search_wahlprogramm muss [] liefern, nicht crashen.""" from app import wahlprogramme as wp_mod monkeypatch.setattr(wp_mod, "get_wahlprogramm", lambda bl, p: {"pdf": "missing.pdf", "name": "X Wahlprogramm 2024", "gueltig_ab": "2024-01-01"}) monkeypatch.setattr(wp_mod, "load_wahlprogramm_text", lambda bl, p: {}) assert search_wahlprogramm("X", "Y", ["foo"]) == [] # ───────────────────────────────────────────────────────────────────────────── # find_relevant_quotes — Bundesland-Validierung # ───────────────────────────────────────────────────────────────────────────── class TestFindRelevantQuotes: def test_unknown_bundesland_raises(self): with pytest.raises(ValueError, match="Unbekanntes Bundesland"): find_relevant_quotes("text", ["CDU"], "XX") class TestFormatQuoteForPrompt: def test_empty_quotes_returns_empty_string(self): assert format_quote_for_prompt({}) == ""