Antwort auf B1 + B2 aus der Roadmap: - B1: Antrag VOR Regierungsbildung (z.B. NRW WP18-Antrag im Mai 2022, vor Vereidigung Wuest II am 29.06.2022) bekommt jetzt das passende Wahlprogramm zurueck — der Geltungsbeginn ist der Wahltag, nicht die Vereidigung. - B2: Opposition vs. Regierung wird einheitlich behandelt. Die fruehere Logik "Geltung ab Regierungsbildung" war fuer Regierungsfraktionen intuitiv (Koalitionsvertrag wird zu Politik), fuer Opposition aber willkuerlich. Programme werden zur Wahl beschlossen und sind Wahlversprechen ab dem Tag der Wahl. Implementation in programme._migrate_from_legacy: - gueltig_ab = aktuelle_legislatur(bl)["wahltermin"] (Fallback auf altes "regierungsbildung" fuer rueckwaerts-kompatible Eintraege) - ``wahl``-Feld auf Wahltag gesetzt - ``wp``-Feld aus aktuelle_legislatur ergaenzt Das ``regierungsbildung``-Feld in WAHLPROGRAMME bleibt erhalten und versorgt den Bewertungs-Kontext-Block weiterhin mit dem Anzeige-Wert "Regierung zur Antragszeit" (per legislaturen.regierung_zum_zeitpunkt laeuft das primaer ueber legislaturen.REGIERUNGEN). UI-Effekt: im Antrag-Detail liest sich z.B. ein BUND-Eintrag jetzt "gueltig seit 2025-02-23, 60 S." (BTW-Wahltag) statt "2025-05-06" (Vereidigung Merz I). Tests: 115 gruen (test_programme + test_legislaturen + test_wahlprogramme + test_embeddings). Tests test_bund_btw_2025_in_uebergangsphase und test_bund_btw_2025_vor_wahl neu, decken die geaenderte Geltungs-Logik explizit ab. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
232 lines
11 KiB
Python
232 lines
11 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"
|
|
# Wahltag, nicht Regierungsbildung (B1+B2: Programme gelten ab Wahl)
|
|
assert p["gueltig_ab"] == "2025-02-23"
|
|
|
|
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_wahl(self):
|
|
# Nach Wahltag (2025-02-23) gilt das BTW-2025-Programm.
|
|
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_in_uebergangsphase(self):
|
|
# Zwischen Wahl (2025-02-23) und Vereidigung Merz I (2025-05-06)
|
|
# gilt das BTW-2025-Programm bereits — B1+B2 (Programme gelten ab
|
|
# Wahltag).
|
|
p = wahlprogramm_zum_zeitpunkt("BUND", "SPD", "2025-04-01")
|
|
assert p is not None
|
|
assert p["id"] == "spd-bund-2025"
|
|
|
|
def test_bund_btw_2025_vor_wahl(self):
|
|
# Vor BTW 2025 (vor 2025-02-23) kein Bund-Wahlprogramm indiziert.
|
|
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
|
|
# Wahltag 2022-05-15.
|
|
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
|