From 1cf249353ee52bce0a04e3c9ba2595955f083818 Mon Sep 17 00:00:00 2001 From: Dotty Dotter Date: Fri, 8 May 2026 01:07:59 +0200 Subject: [PATCH] =?UTF-8?q?test:=2026=20Tests=20fuer=20app.programme=20?= =?UTF-8?q?=E2=80=94=20Helper-API=20+=20Migration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Direkte Test-Abdeckung der zentralen Programm-Registry. Vorher nur indirekt via test_legislaturen.py (das nicht aus programme.py importiert) und ueber den Antrag-Detail-Smoke-Test. Geprueft: - Migration aus WAHLPROGRAMME + embeddings.PROGRAMME (>80 Eintraege, alle Pflichtfelder, eindeutige IDs, alle drei Typen vertreten) - aktuelles_wahlprogramm: NRW/CDU → 2022, BUND/CDU → BTW 2025; XX → None - wahlprogramm_zum_zeitpunkt: 2024 in NRW gibt cdu-nrw-2022; vor 2025-05-06 in BUND gibt None (BTW-2025-Programme gelten erst ab Regierungsbildung) - grundsatzprogramm_zum_zeitpunkt: BL-Fallback (CDU NRW → cdu-grundsatz-nrw, CDU HE → cdu-grundsatz Bund-Fallback); SSW/SH; CSU 2023 - parteien_mit_wahlprogramm: NRW=5, BUND=8 - alle_versionen, get_programm, Edge-Cases Co-Authored-By: Claude Opus 4.7 (1M context) --- tests/test_programme.py | 223 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 tests/test_programme.py diff --git a/tests/test_programme.py b/tests/test_programme.py new file mode 100644 index 0000000..52c1ff3 --- /dev/null +++ b/tests/test_programme.py @@ -0,0 +1,223 @@ +"""Tests for app.programme — zentrale Programm-Registry mit Geltungsdaten. + +Diese Tests prüfen die Helper-API und die Migrationspfade aus +``WAHLPROGRAMME`` und ``embeddings.PROGRAMME``. Architektur-Doku in +docs/adr/0013-programme-legislaturen-zeitpunktige-bewertung.md. +""" +import sys +import types + +import pytest + +# Stub openai für embeddings.py-Import (programme._migrate_from_legacy() +# triggert beim ersten API-Call den embeddings-Import). +if "openai" not in sys.modules: + o = types.ModuleType("openai") + o.OpenAI = lambda **kw: None + sys.modules["openai"] = o + +try: + import fitz as _fitz + if not hasattr(_fitz, "open"): + import pymupdf + sys.modules["fitz"] = pymupdf +except ImportError: + try: + import pymupdf + sys.modules["fitz"] = pymupdf + except ImportError: + pass + +from app.programme import ( + PROGRAMME, + aktuelles_wahlprogramm, + alle_versionen, + all_programme, + get_programm, + grundsatzprogramm_zum_zeitpunkt, + parteien_mit_wahlprogramm, + wahlprogramm_zum_zeitpunkt, +) + + +# ───────────────────────────────────────────────────────────────────────────── +# Migration: Daten landen aus den Legacy-Quellen in PROGRAMME +# ───────────────────────────────────────────────────────────────────────────── + +class TestMigration: + def test_migration_populates_programme(self): + progs = all_programme() + assert len(progs) > 80, \ + f"Expected >80 programme nach migration; got {len(progs)}" + + def test_alle_typen_vertreten(self): + progs = all_programme() + typs = {p["typ"] for p in progs} + assert "wahlprogramm" in typs + assert "grundsatzprogramm-bund" in typs + assert "grundsatzprogramm-land" in typs + + def test_jeder_eintrag_hat_pflichtfelder(self): + for prog in all_programme(): + for f in ("id", "titel", "name", "typ", "partei", + "gueltig_ab", "pdf"): + assert f in prog, f"{prog.get('id')}: feld {f} fehlt" + + def test_ids_sind_eindeutig(self): + ids = [p["id"] for p in all_programme()] + assert len(ids) == len(set(ids)), "Duplicate Programm-IDs" + + +# ───────────────────────────────────────────────────────────────────────────── +# aktuelles_wahlprogramm +# ───────────────────────────────────────────────────────────────────────────── + +class TestAktuellesWahlprogramm: + def test_nrw_cdu_returns_2022(self): + p = aktuelles_wahlprogramm("NRW", "CDU") + assert p is not None + assert p["id"] == "cdu-nrw-2022" + assert p["bundesland"] == "NRW" + assert p["partei"] == "CDU" + assert p["typ"] == "wahlprogramm" + + def test_bund_cdu_returns_btw_2025(self): + p = aktuelles_wahlprogramm("BUND", "CDU") + assert p is not None + assert p["id"] == "cdu-bund-2025" + assert p["gueltig_ab"] == "2025-05-06" + + def test_unknown_bl_returns_none(self): + assert aktuelles_wahlprogramm("XX", "CDU") is None + + def test_unknown_partei_returns_none(self): + assert aktuelles_wahlprogramm("NRW", "BSW") is None # nicht im Landtag NRW + + def test_aktuelles_hat_gueltig_bis_none(self): + for bl in ["NRW", "BUND", "BB", "TH"]: + for partei in parteien_mit_wahlprogramm(bl): + p = aktuelles_wahlprogramm(bl, partei) + if p is not None: + assert p["gueltig_bis"] is None, \ + f"{bl}/{partei} aktuell aber gueltig_bis gesetzt" + + +# ───────────────────────────────────────────────────────────────────────────── +# wahlprogramm_zum_zeitpunkt — historisch korrekte Einordnung +# ───────────────────────────────────────────────────────────────────────────── + +class TestWahlprogrammZumZeitpunkt: + def test_nrw_cdu_aktuelles_datum(self): + # 2024-01-01 liegt im Geltungszeitraum von cdu-nrw-2022 + p = wahlprogramm_zum_zeitpunkt("NRW", "CDU", "2024-01-01") + assert p is not None + assert p["id"] == "cdu-nrw-2022" + + def test_bund_btw_2025_nach_regierungsbildung(self): + p = wahlprogramm_zum_zeitpunkt("BUND", "SPD", "2025-09-01") + assert p is not None + assert p["id"] == "spd-bund-2025" + + def test_bund_btw_2025_vor_regierungsbildung(self): + # Vor 2025-05-06 (Vereidigung Merz I) gibt es kein indiziertes + # Bund-Wahlprogramm, da BTW-2025-Programme erst ab Regierungsbildung + # gelten und vor BTW 2025 die alten Bund-Grundsatzprogramme als + # Quelle dienten (heute nur in embeddings.PROGRAMME). + p = wahlprogramm_zum_zeitpunkt("BUND", "SPD", "2024-01-01") + assert p is None + + def test_datum_vor_aktueller_wp(self): + # Antrag aus 2018 in NRW: aktuelle WP18-Programm gilt erst ab 2022. + p = wahlprogramm_zum_zeitpunkt("NRW", "CDU", "2018-09-01") + # Heute keine WP17-Programme indiziert → erwarten None. + assert p is None + + +# ───────────────────────────────────────────────────────────────────────────── +# grundsatzprogramm_zum_zeitpunkt — Bund + Land mit Vorgänger-Logik +# ───────────────────────────────────────────────────────────────────────────── + +class TestGrundsatzprogrammZumZeitpunkt: + def test_cdu_bund_2025(self): + p = grundsatzprogramm_zum_zeitpunkt("CDU", "2025-01-01") + assert p is not None + assert p["id"] == "cdu-grundsatz" # 2024er Programm + assert p["typ"] == "grundsatzprogramm-bund" + + def test_cdu_bund_vor_2024_keiner(self): + # Hannoveraner Programm 2007 ist nicht im Schema indiziert. + p = grundsatzprogramm_zum_zeitpunkt("CDU", "2010-01-01") + assert p is None + + def test_cdu_nrw_landesgrundsatz_bevorzugt(self): + # Mit bundesland=NRW wird das Landesgrundsatzprogramm zurückgegeben, + # nicht das Bundes. + p = grundsatzprogramm_zum_zeitpunkt("CDU", "2024-01-01", bundesland="NRW") + assert p is not None + assert p["id"] == "cdu-grundsatz-nrw" + assert p["typ"] == "grundsatzprogramm-land" + + def test_cdu_he_kein_landesgrundsatz_fallback_auf_bund(self): + # Hessen hat kein CDU-Landesgrundsatzprogramm → Fallback auf Bund. + p = grundsatzprogramm_zum_zeitpunkt("CDU", "2025-01-01", bundesland="HE") + assert p is not None + assert p["id"] == "cdu-grundsatz" # Bund + + def test_ssw_sh_landesgrundsatz(self): + # SSW existiert nur in SH — Rahmenprogramm 2016. + p = grundsatzprogramm_zum_zeitpunkt("SSW", "2024-01-01", bundesland="SH") + assert p is not None + assert p["id"] == "ssw-grundsatz" + + def test_csu_2023_aktuell(self): + p = grundsatzprogramm_zum_zeitpunkt("CSU", "2024-01-01", bundesland="BY") + assert p is not None + assert p["id"] == "csu-grundsatz" + assert p["gueltig_ab"] == "2023-05-06" + + +# ───────────────────────────────────────────────────────────────────────────── +# parteien_mit_wahlprogramm +# ───────────────────────────────────────────────────────────────────────────── + +class TestParteienMitWahlprogramm: + def test_nrw_5_parteien(self): + parteien = set(parteien_mit_wahlprogramm("NRW")) + assert parteien == {"CDU", "SPD", "GRÜNE", "FDP", "AfD"} + + def test_bund_8_parteien(self): + parteien = set(parteien_mit_wahlprogramm("BUND")) + assert parteien == {"CDU", "CSU", "SPD", "GRÜNE", "FDP", + "AfD", "LINKE", "BSW"} + + def test_unknown_bl_empty(self): + assert parteien_mit_wahlprogramm("XX") == [] + + +# ───────────────────────────────────────────────────────────────────────────── +# alle_versionen — Lieferung sortiert nach gueltig_ab +# ───────────────────────────────────────────────────────────────────────────── + +class TestAlleVersionen: + def test_nrw_cdu_eine_version(self): + # Heute nur cdu-nrw-2022 indiziert. + versions = alle_versionen("NRW", "CDU") + assert len(versions) == 1 + assert versions[0]["id"] == "cdu-nrw-2022" + + def test_unknown_bl_leer(self): + assert alle_versionen("XX", "CDU") == [] + + +# ───────────────────────────────────────────────────────────────────────────── +# get_programm — direct ID-Lookup +# ───────────────────────────────────────────────────────────────────────────── + +class TestGetProgramm: + def test_known_id(self): + p = get_programm("cdu-nrw-2022") + assert p is not None + assert p["partei"] == "CDU" + + def test_unknown_id_returns_none(self): + assert get_programm("does-not-exist") is None