gwoe-antragspruefer/tests/test_programme.py
Dotty Dotter 1cf249353e test: 26 Tests fuer app.programme — Helper-API + Migration
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) <noreply@anthropic.com>
2026-05-08 01:07:59 +02:00

224 lines
10 KiB
Python

"""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