gwoe-antragspruefer/tests/test_bundeslaender.py

86 lines
3.1 KiB
Python
Raw Normal View History

Add pytest suite + fix two regex bugs uncovered by it (#46) Erste Tests für die Codebase. 77 Tests, 0.08s Laufzeit, decken die drei Bug-Klassen aus der April-2026-Adapter-Session ab plus haben schon zwei weitere Bugs in Production-Code aufgedeckt. ## Setup - requirements-dev.txt mit pytest + pytest-asyncio - pytest.ini mit asyncio_mode=auto - tests/conftest.py stubbt fitz/bs4/openai/pydantic_settings, damit die Suite ohne den vollen prod-requirements-Satz läuft (pure unit tests, kein PDF-Parsing, kein HTTP) ## Tests - tests/test_parlamente.py (33 Tests) * PortalaAdapter._parse_hit_list_cards: doctype/doctype_full NameError-Regression aus 1cb030a, plus Title/Drucksache/Fraktion- /Datum/PDF-Extraktion gegen ein BE-Card-Fixture * PortalaAdapter._parse_hit_list_dump: gegen ein LSA-Perl-Dump- Fixture inkl. Hex-Escape-Decoding (\x{fc} → ü) * PortalaAdapter._parse_hit_list_html: Auto-Detection zwischen Card- und Dump-Format * PortalaAdapter._normalize_fraktion: kanonische Fraktion-Codes inkl. F.D.P.-mit-Punkten, BÜNDNIS 90, DIE LINKE, BSW * ParLDokAdapter._hit_to_drucksache: JSON-Hit → Drucksache Mapping inkl. /navpanes-Stripping, MdL-mit-Partei-in-Klammern, Landesregierung-Detection * ParLDokAdapter._fulltext_id: bundle.js-mirroring (deferred, aber dokumentiert) * ADAPTERS-Registry-Sanity - tests/test_embeddings.py (11 Tests) * _chunk_source_label: Programm-Name + Seite (Halluzinations- Bug-Regression aus 1b5fd96) * format_quotes_for_prompt: jeder Chunk muss Programm-Name enthalten, strict-citation-Hinweis muss im Output sein, keine NRW-Halluzinationen für MV/BE-Chunk-Sets - tests/test_wahlprogramme.py (14 Tests) * Registry-Struktur (jahr int, seiten int, .pdf-Endung) * File-Existenz: jede registrierte PDF muss in static/referenzen/ liegen — würde Tippfehler in den 22 indexierten Programmen sofort fangen * embeddings.PROGRAMME-Konsistenz-Cross-Check - tests/test_bundeslaender.py (15 Tests) * Sanity über 16-State-Registry * #48-Klassifikations-Regression: TH=ParlDok, HB=StarWeb, SN=Eigensystem * Wahltermine plausibel (zwischen 2026 und 2035) - tests/test_analyzer.py (4 Tests) * Markdown-Codeblock-Stripping aus dem JSON-Retry-Loop ## Bug-Funde während der Test-Schreibphase Zwei Production-Bugs in den _normalize_fraktion-Helfern wurden durch die neuen Tests sofort aufgedeckt und im selben Commit gefixt: 1. PortalaAdapter._normalize_fraktion matched "F.D.P." (mit Punkten, wie historische SH/HB-Drucksachen) nicht — Regex \bFDP\b ist zu strikt. Fix: \bF\.?\s*D\.?\s*P\.?\b analog zu ParLDokAdapter. 2. ParLDokAdapter._normalize_fraktion (auch PortalaAdapter) matched "Ministerium der Finanzen" nicht als Landesregierung, weil \bMINISTER\b die Wortgrenze auch nach MINISTER verlangt — bei MINISTERIUM steht aber IUM danach, keine Wortgrenze. Fix: \bMINISTER ohne abschließendes \b. Beide Bugs hätten Fraktion-Felder bei Drucksachen der Bremischen Bürgerschaft (FDP-Listen) und bei Landesregierungs-Drucksachen in MV/LSA fälschlich leer gelassen — exakt der "fraktionen=[]"- Befund aus dem MV-Smoke-Test in #4. Phase 0 aus Roadmap-Issue #49. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 23:26:06 +02:00
"""Tests for bundeslaender.py — sanity over 16-state registry.
Includes the #48 classification regression: TH must be ParlDok, HB must
be StarWeb, SN must be Eigensystem (not ParlDok).
"""
from app.bundeslaender import BUNDESLAENDER, get, aktive_bundeslaender, alle_bundeslaender
class TestRegistryStructure:
def test_sixteen_bundeslaender(self):
assert len(BUNDESLAENDER) == 16
def test_codes_are_uppercase(self):
for code in BUNDESLAENDER:
assert code.isupper(), f"{code} is not uppercase"
def test_each_entry_has_naechste_wahl_or_none(self):
for code, bl in BUNDESLAENDER.items():
assert bl.naechste_wahl is None or len(bl.naechste_wahl) == 10
def test_wahlperiode_is_positive_integer(self):
for bl in BUNDESLAENDER.values():
assert isinstance(bl.wahlperiode, int) and bl.wahlperiode > 0
class TestActiveBundeslaender:
Activate Baden-Württemberg via PARLISAdapter (#29, Phase 1) PARLIS auf parlis.landtag-bw.de läuft technisch auf demselben eUI-Backend wie LSA-PADOKA und BE-PARDOK, hat aber drei wichtige Unterschiede, die eine eigene Klasse statt einer PortalaAdapter- Subklasse rechtfertigen: 1. Body-Schema: minimales lines mit l1/l2/l3/l4 (statt LSA/BE 2/3/4/10/11/20.x/90.x), serverrecordname=vorgang, format=suchergebnis-vorgang-full, sort=SORT01/D SORT02/D SORT03, keine parsed/json-Felder. Quelle: dokukratie/scrapers/portala.query.bw.json plus HAR-Verifikation gegen die Live-Instanz. 2. Async polling: die initiale SearchAndDisplay-Antwort liefert nur search_id mit status=running, KEINE report_id. Erst eine zweite SearchAndDisplay-Anfrage mit id=<search_id> (ohne search-Component) bekommt nach 1-3 Sekunden die report_id zurück. Reverse-engineered aus esearch-ui.main.js requestReportOK() Z. ~1268. 3. Hit-Format: report.tt.html liefert Records als JSON-in-HTML-Comments <!--{"WMV33":[...],"EWBV22":[...],...}-->. Komplett anderes Format als LSA Perl-Dump oder BE HTML-Cards. Felder: - EWBV22: "Drucksache 17/10323" - EWBD05: direkter PDF-URL - WMV33: Schlagworte (joined by ;) - WMV30: Urheber-Kurzform - EWBV23: "Antrag <Urheber> <DD.MM.YYYY>" Smoke-Test (lokal): BW q='': 8 hits in 17s, jüngste WP17-Anträge mit Datum + Fraktion BW q='Schule': 8 hits, alle wirklich Schul-bezogen (Hochschule, Grundschule, Schwimmunterricht, Lehrerbedarf etc.) BW q='Klima': 8 hits, Klimaschutz/CO2/Energieberatung get_document(17/10323): roundtrip funktioniert bundeslaender.py: aktiv=True für BW; Anmerkung erweitert mit PARLISAdapter-Verweis und drei-Unterschiede-Hinweis für künftige Wartung. Test test_four_active_bundeslaender umbenannt zu test_active_bundeslaender_include_phase_1_set, prüft jetzt nur Subset-Bedingung statt exakter Count, damit Phase-1/2-Erweiterungen keine Test-Updates brauchen. Phase 1 (1/3) aus Roadmap-Issue #49. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 23:38:04 +02:00
def test_active_bundeslaender_include_phase_1_set(self):
"""At least the original four (NRW, LSA, MV, BE) plus any
Phase-1 additions (BW after #29) must be active. The test
avoids hardcoding the exact count so adding a new active
Bundesland in a follow-up doesn't break this case."""
active_codes = {bl.code for bl in aktive_bundeslaender()}
original = {"NRW", "LSA", "MV", "BE"}
assert original <= active_codes
Add pytest suite + fix two regex bugs uncovered by it (#46) Erste Tests für die Codebase. 77 Tests, 0.08s Laufzeit, decken die drei Bug-Klassen aus der April-2026-Adapter-Session ab plus haben schon zwei weitere Bugs in Production-Code aufgedeckt. ## Setup - requirements-dev.txt mit pytest + pytest-asyncio - pytest.ini mit asyncio_mode=auto - tests/conftest.py stubbt fitz/bs4/openai/pydantic_settings, damit die Suite ohne den vollen prod-requirements-Satz läuft (pure unit tests, kein PDF-Parsing, kein HTTP) ## Tests - tests/test_parlamente.py (33 Tests) * PortalaAdapter._parse_hit_list_cards: doctype/doctype_full NameError-Regression aus 1cb030a, plus Title/Drucksache/Fraktion- /Datum/PDF-Extraktion gegen ein BE-Card-Fixture * PortalaAdapter._parse_hit_list_dump: gegen ein LSA-Perl-Dump- Fixture inkl. Hex-Escape-Decoding (\x{fc} → ü) * PortalaAdapter._parse_hit_list_html: Auto-Detection zwischen Card- und Dump-Format * PortalaAdapter._normalize_fraktion: kanonische Fraktion-Codes inkl. F.D.P.-mit-Punkten, BÜNDNIS 90, DIE LINKE, BSW * ParLDokAdapter._hit_to_drucksache: JSON-Hit → Drucksache Mapping inkl. /navpanes-Stripping, MdL-mit-Partei-in-Klammern, Landesregierung-Detection * ParLDokAdapter._fulltext_id: bundle.js-mirroring (deferred, aber dokumentiert) * ADAPTERS-Registry-Sanity - tests/test_embeddings.py (11 Tests) * _chunk_source_label: Programm-Name + Seite (Halluzinations- Bug-Regression aus 1b5fd96) * format_quotes_for_prompt: jeder Chunk muss Programm-Name enthalten, strict-citation-Hinweis muss im Output sein, keine NRW-Halluzinationen für MV/BE-Chunk-Sets - tests/test_wahlprogramme.py (14 Tests) * Registry-Struktur (jahr int, seiten int, .pdf-Endung) * File-Existenz: jede registrierte PDF muss in static/referenzen/ liegen — würde Tippfehler in den 22 indexierten Programmen sofort fangen * embeddings.PROGRAMME-Konsistenz-Cross-Check - tests/test_bundeslaender.py (15 Tests) * Sanity über 16-State-Registry * #48-Klassifikations-Regression: TH=ParlDok, HB=StarWeb, SN=Eigensystem * Wahltermine plausibel (zwischen 2026 und 2035) - tests/test_analyzer.py (4 Tests) * Markdown-Codeblock-Stripping aus dem JSON-Retry-Loop ## Bug-Funde während der Test-Schreibphase Zwei Production-Bugs in den _normalize_fraktion-Helfern wurden durch die neuen Tests sofort aufgedeckt und im selben Commit gefixt: 1. PortalaAdapter._normalize_fraktion matched "F.D.P." (mit Punkten, wie historische SH/HB-Drucksachen) nicht — Regex \bFDP\b ist zu strikt. Fix: \bF\.?\s*D\.?\s*P\.?\b analog zu ParLDokAdapter. 2. ParLDokAdapter._normalize_fraktion (auch PortalaAdapter) matched "Ministerium der Finanzen" nicht als Landesregierung, weil \bMINISTER\b die Wortgrenze auch nach MINISTER verlangt — bei MINISTERIUM steht aber IUM danach, keine Wortgrenze. Fix: \bMINISTER ohne abschließendes \b. Beide Bugs hätten Fraktion-Felder bei Drucksachen der Bremischen Bürgerschaft (FDP-Listen) und bei Landesregierungs-Drucksachen in MV/LSA fälschlich leer gelassen — exakt der "fraktionen=[]"- Befund aus dem MV-Smoke-Test in #4. Phase 0 aus Roadmap-Issue #49. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 23:26:06 +02:00
def test_alle_bundeslaender_returns_all_sixteen(self):
assert len(alle_bundeslaender()) == 16
def test_alle_bundeslaender_active_first(self):
out = alle_bundeslaender()
active_codes = {bl.code for bl in aktive_bundeslaender()}
# The first len(active) entries must all be active
for bl in out[: len(active_codes)]:
assert bl.code in active_codes
class TestGetHelper:
def test_returns_bundesland_for_known_code(self):
bl = get("NRW")
assert bl is not None
assert bl.name == "Nordrhein-Westfalen"
def test_returns_none_for_unknown_code(self):
assert get("XX") is None
class TestClassificationFix48:
"""Regression: #48 corrected three doku_system entries that the
follow-up adapter issues depend on."""
def test_th_is_parldok_not_starweb(self):
assert BUNDESLAENDER["TH"].doku_system == "ParlDok"
def test_hb_is_starweb_not_paris(self):
"""PARiS is just a StarWeb skin — must be classified as StarWeb."""
assert BUNDESLAENDER["HB"].doku_system == "StarWeb"
def test_sn_is_eigensystem_not_parldok(self):
"""EDAS is ASP.NET-Webforms, NOT ParlDok-compatible with MV."""
assert BUNDESLAENDER["SN"].doku_system == "Eigensystem"
class TestWahltermineSane:
"""All upcoming elections must be in chronological order and in the
near future (sanity check that someone has not pasted a 1990 date)."""
def test_no_election_before_2026(self):
for bl in BUNDESLAENDER.values():
if bl.naechste_wahl:
assert bl.naechste_wahl >= "2026-01-01"
def test_no_election_after_2035(self):
for bl in BUNDESLAENDER.values():
if bl.naechste_wahl:
assert bl.naechste_wahl < "2035-01-01"