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 wahlprogramme.py — registry consistency + file existence."""
|
chore: konsolidiere Working-Tree mit dev-Stand nach Nextcloud-Sync-Resolution
Mehrtaegiger Sync-Stillstand hatte ueber 50 conflicted-copy-Dateien im
Working-Tree erzeugt. Die jeweils neuere Version wurde basierend auf
md5-Hash-Vergleich zum laufenden gwoe-antragspruefer-dev-Container
eingespielt.
Konsolidiert (38 modifiziert):
- analyzer.py, auswertungen.py, auth.py, config.py, database.py,
drucksache_typen.py, embeddings.py, main.py, models.py, parlamente.py,
ports/llm_bewerter.py, presse_generator.py, redline_utils.py, report.py,
validators.py, wahlprogramm_fetch.py, wahlprogramm-links.yaml,
wahlprogramm-shas.lock.json
- v2-Templates: base, components/{icon, matrix_mini, queue_widget,
result_row}, screens/{admin_queue, admin_stand, aktuelle-themen,
antrag_detail, auswertungen, cluster, landtag_suche, merkliste,
methodik, tags}, static/v2/v2.css
- Tests: test_embeddings (Strict-Mode-Drop in reconstruct_zitate),
test_endpoints_smoke, test_presse_generator, test_report,
test_wahlprogramme (mit TestRegierungsbildung-Block, +120 LOC)
- docker-compose.dev.yml, docs/adr/index.md, docs/reference/api.md, mkdocs.yml
Neuzugaenge:
- app/marker.py, app/pm_render.py — Konsistenz-Marker, PM-Render-Adapter
- app/templates/v2/screens/scorecard{,_portrait,_werkstatt}.html — Cloud-Design-Scorecard
- app/static/v3/, app/templates/v3/ — v3-Layout-Hierarchie
- docs/adr/0010-stimmverhalten-gwoe-aggregat.md
- docs/adr/0011-aktuelle-themen-pm-generator.md
- docs/adr/0012-debug-auth-token-bypass.md
- scripts/{auto-rate-orphans, pm-quality-audit, pm-sample-bundle, rotate-debug-token}.sh
- tests/e2e/test_smoke_browser.py, tests/test_{auto_rate_runs, icons,
marker, pm_render, presse_generator_style, thread_splitter,
v2_pdf_consistency}.py
Plus inhaltlich uebernommen aus dem Conflict-Stand:
- embeddings.py: fw-by-2023.partei korrigiert von "FW" zu "FREIE WAEHLER"
(war Mismatch zu wahlprogramme.py)
- embeddings.py: detailliertere Naming der BTW-2025-Wahlprogramme
Test-Suite-Stand: 1209 passed, 73 skipped (4 pre-existing failures in
test_presse_generator_style.py + 1 collection error in
integration/test_citations_substring.py — beide nicht durch dieses
Konsolidierungs-Commit verursacht).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 00:04:21 +02:00
|
|
|
import re
|
|
|
|
|
|
test(#134): Coverage-Backfill drei Module
- app/ingest_votes.py 39.2% → 100%
- TestDownloadPdf: schreibt Bytes, propagiert HTTP-Fehler
- TestCli: --supported, kein-arg-error, fehlender PDF-Pfad,
pdf-Pfad-Run, --url-Download-Pfad, exit-Code 2 bei null Resultaten,
Errors-Liste im Output
- DB-Error-Collection in ingest_pdf
- app/wahlprogramme.py 90.7% → 100%
- TestLoadWahlprogrammText: paged-Datei, Normal-Datei-Fallback,
fehlende Datei
- TestSearchWahlprogramm: leere Returns
- TestFindRelevantQuotes: ValueError bei unbekanntem BL
- TestFormatQuoteForPrompt: leeres Dict
- app/abgeordnetenwatch.py 95.2% → 97.6%
- test_rp_pattern_nr_wp_swap: '/538-18.pdf' → '18/538'
- test_sn_pattern_dok_nr_leg_per_swap: 'dok_nr=2150&leg_per=8' → '8/2150'
Total: 47.59% → 48.69%, 666 → 686 Tests, 0 Failures.
2026-04-28 10:50:26 +02:00
|
|
|
import pytest
|
|
|
|
|
|
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
|
|
|
from app.wahlprogramme import (
|
|
|
|
|
WAHLPROGRAMME,
|
|
|
|
|
REFERENZEN_PATH,
|
|
|
|
|
get_wahlprogramm,
|
|
|
|
|
parteien_mit_wahlprogramm,
|
chore: konsolidiere Working-Tree mit dev-Stand nach Nextcloud-Sync-Resolution
Mehrtaegiger Sync-Stillstand hatte ueber 50 conflicted-copy-Dateien im
Working-Tree erzeugt. Die jeweils neuere Version wurde basierend auf
md5-Hash-Vergleich zum laufenden gwoe-antragspruefer-dev-Container
eingespielt.
Konsolidiert (38 modifiziert):
- analyzer.py, auswertungen.py, auth.py, config.py, database.py,
drucksache_typen.py, embeddings.py, main.py, models.py, parlamente.py,
ports/llm_bewerter.py, presse_generator.py, redline_utils.py, report.py,
validators.py, wahlprogramm_fetch.py, wahlprogramm-links.yaml,
wahlprogramm-shas.lock.json
- v2-Templates: base, components/{icon, matrix_mini, queue_widget,
result_row}, screens/{admin_queue, admin_stand, aktuelle-themen,
antrag_detail, auswertungen, cluster, landtag_suche, merkliste,
methodik, tags}, static/v2/v2.css
- Tests: test_embeddings (Strict-Mode-Drop in reconstruct_zitate),
test_endpoints_smoke, test_presse_generator, test_report,
test_wahlprogramme (mit TestRegierungsbildung-Block, +120 LOC)
- docker-compose.dev.yml, docs/adr/index.md, docs/reference/api.md, mkdocs.yml
Neuzugaenge:
- app/marker.py, app/pm_render.py — Konsistenz-Marker, PM-Render-Adapter
- app/templates/v2/screens/scorecard{,_portrait,_werkstatt}.html — Cloud-Design-Scorecard
- app/static/v3/, app/templates/v3/ — v3-Layout-Hierarchie
- docs/adr/0010-stimmverhalten-gwoe-aggregat.md
- docs/adr/0011-aktuelle-themen-pm-generator.md
- docs/adr/0012-debug-auth-token-bypass.md
- scripts/{auto-rate-orphans, pm-quality-audit, pm-sample-bundle, rotate-debug-token}.sh
- tests/e2e/test_smoke_browser.py, tests/test_{auto_rate_runs, icons,
marker, pm_render, presse_generator_style, thread_splitter,
v2_pdf_consistency}.py
Plus inhaltlich uebernommen aus dem Conflict-Stand:
- embeddings.py: fw-by-2023.partei korrigiert von "FW" zu "FREIE WAEHLER"
(war Mismatch zu wahlprogramme.py)
- embeddings.py: detailliertere Naming der BTW-2025-Wahlprogramme
Test-Suite-Stand: 1209 passed, 73 skipped (4 pre-existing failures in
test_presse_generator_style.py + 1 collection error in
integration/test_citations_substring.py — beide nicht durch dieses
Konsolidierungs-Commit verursacht).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 00:04:21 +02:00
|
|
|
regierung_aktuell,
|
|
|
|
|
regierungsbildung_for,
|
|
|
|
|
regierungsende_for,
|
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
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# Registry consistency
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
class TestRegistryStructure:
|
|
|
|
|
def test_active_bundeslaender_present(self):
|
|
|
|
|
for code in ["NRW", "LSA", "MV", "BE"]:
|
|
|
|
|
assert code in WAHLPROGRAMME, f"missing wahlprogramme entry for {code}"
|
|
|
|
|
|
|
|
|
|
def test_each_entry_has_required_keys(self):
|
|
|
|
|
required = {"file", "titel", "partei", "jahr", "seiten"}
|
|
|
|
|
for bl, parteien in WAHLPROGRAMME.items():
|
|
|
|
|
for partei, info in parteien.items():
|
|
|
|
|
missing = required - set(info.keys())
|
|
|
|
|
assert not missing, f"{bl}/{partei} missing keys: {missing}"
|
|
|
|
|
|
|
|
|
|
def test_jahr_is_integer(self):
|
|
|
|
|
for bl, parteien in WAHLPROGRAMME.items():
|
|
|
|
|
for partei, info in parteien.items():
|
|
|
|
|
assert isinstance(info["jahr"], int), f"{bl}/{partei} jahr not int"
|
|
|
|
|
|
|
|
|
|
def test_seiten_is_positive_integer(self):
|
|
|
|
|
for bl, parteien in WAHLPROGRAMME.items():
|
|
|
|
|
for partei, info in parteien.items():
|
|
|
|
|
assert isinstance(info["seiten"], int)
|
|
|
|
|
assert info["seiten"] > 0
|
|
|
|
|
|
|
|
|
|
def test_file_extension_is_pdf(self):
|
|
|
|
|
for bl, parteien in WAHLPROGRAMME.items():
|
|
|
|
|
for partei, info in parteien.items():
|
|
|
|
|
assert info["file"].endswith(".pdf")
|
|
|
|
|
|
|
|
|
|
|
chore: konsolidiere Working-Tree mit dev-Stand nach Nextcloud-Sync-Resolution
Mehrtaegiger Sync-Stillstand hatte ueber 50 conflicted-copy-Dateien im
Working-Tree erzeugt. Die jeweils neuere Version wurde basierend auf
md5-Hash-Vergleich zum laufenden gwoe-antragspruefer-dev-Container
eingespielt.
Konsolidiert (38 modifiziert):
- analyzer.py, auswertungen.py, auth.py, config.py, database.py,
drucksache_typen.py, embeddings.py, main.py, models.py, parlamente.py,
ports/llm_bewerter.py, presse_generator.py, redline_utils.py, report.py,
validators.py, wahlprogramm_fetch.py, wahlprogramm-links.yaml,
wahlprogramm-shas.lock.json
- v2-Templates: base, components/{icon, matrix_mini, queue_widget,
result_row}, screens/{admin_queue, admin_stand, aktuelle-themen,
antrag_detail, auswertungen, cluster, landtag_suche, merkliste,
methodik, tags}, static/v2/v2.css
- Tests: test_embeddings (Strict-Mode-Drop in reconstruct_zitate),
test_endpoints_smoke, test_presse_generator, test_report,
test_wahlprogramme (mit TestRegierungsbildung-Block, +120 LOC)
- docker-compose.dev.yml, docs/adr/index.md, docs/reference/api.md, mkdocs.yml
Neuzugaenge:
- app/marker.py, app/pm_render.py — Konsistenz-Marker, PM-Render-Adapter
- app/templates/v2/screens/scorecard{,_portrait,_werkstatt}.html — Cloud-Design-Scorecard
- app/static/v3/, app/templates/v3/ — v3-Layout-Hierarchie
- docs/adr/0010-stimmverhalten-gwoe-aggregat.md
- docs/adr/0011-aktuelle-themen-pm-generator.md
- docs/adr/0012-debug-auth-token-bypass.md
- scripts/{auto-rate-orphans, pm-quality-audit, pm-sample-bundle, rotate-debug-token}.sh
- tests/e2e/test_smoke_browser.py, tests/test_{auto_rate_runs, icons,
marker, pm_render, presse_generator_style, thread_splitter,
v2_pdf_consistency}.py
Plus inhaltlich uebernommen aus dem Conflict-Stand:
- embeddings.py: fw-by-2023.partei korrigiert von "FW" zu "FREIE WAEHLER"
(war Mismatch zu wahlprogramme.py)
- embeddings.py: detailliertere Naming der BTW-2025-Wahlprogramme
Test-Suite-Stand: 1209 passed, 73 skipped (4 pre-existing failures in
test_presse_generator_style.py + 1 collection error in
integration/test_citations_substring.py — beide nicht durch dieses
Konsolidierungs-Commit verursacht).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 00:04:21 +02:00
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# Regierungsbildungs-Felder — Konsistenz pro Bundesland
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
class TestRegierungsbildung:
|
|
|
|
|
"""Pro Bundesland gehören alle Wahlprogramm-Einträge zur gleichen
|
|
|
|
|
Legislatur — d.h. dasselbe regierungsbildung-Datum. Ausnahme: BUND, wo
|
|
|
|
|
Grundsatzprogramme stehen, die regierungsbildung=None tragen."""
|
|
|
|
|
|
|
|
|
|
_ISO_DATE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
|
|
|
|
|
|
|
|
|
|
def test_every_entry_has_regierungs_fields(self):
|
|
|
|
|
for bl, parteien in WAHLPROGRAMME.items():
|
|
|
|
|
for partei, info in parteien.items():
|
|
|
|
|
assert "regierungsbildung" in info, f"{bl}/{partei}: regierungsbildung fehlt"
|
|
|
|
|
assert "regierungsende" in info, f"{bl}/{partei}: regierungsende fehlt"
|
|
|
|
|
|
|
|
|
|
def test_regierungsbildung_iso_or_none(self):
|
|
|
|
|
for bl, parteien in WAHLPROGRAMME.items():
|
|
|
|
|
for partei, info in parteien.items():
|
|
|
|
|
rb = info.get("regierungsbildung")
|
|
|
|
|
if rb is not None:
|
|
|
|
|
assert isinstance(rb, str) and self._ISO_DATE.match(rb), \
|
|
|
|
|
f"{bl}/{partei}: regierungsbildung kein ISO-Datum: {rb!r}"
|
|
|
|
|
|
|
|
|
|
def test_regierungsende_iso_or_none(self):
|
|
|
|
|
for bl, parteien in WAHLPROGRAMME.items():
|
|
|
|
|
for partei, info in parteien.items():
|
|
|
|
|
re_ = info.get("regierungsende")
|
|
|
|
|
if re_ is not None:
|
|
|
|
|
assert isinstance(re_, str) and self._ISO_DATE.match(re_), \
|
|
|
|
|
f"{bl}/{partei}: regierungsende kein ISO-Datum: {re_!r}"
|
|
|
|
|
|
|
|
|
|
def test_alle_parteien_eines_bl_haben_gleiches_datum(self):
|
|
|
|
|
"""Alle Wahlprogramm-Einträge eines Bundeslands gehören zur selben
|
|
|
|
|
Regierung und müssen daher dasselbe Bildungs-/Endedatum tragen."""
|
|
|
|
|
for bl, parteien in WAHLPROGRAMME.items():
|
|
|
|
|
bildung = {info.get("regierungsbildung") for info in parteien.values()}
|
|
|
|
|
ende = {info.get("regierungsende") for info in parteien.values()}
|
|
|
|
|
assert len(bildung) == 1, \
|
|
|
|
|
f"{bl}: regierungsbildung divergent: {bildung}"
|
|
|
|
|
assert len(ende) == 1, \
|
|
|
|
|
f"{bl}: regierungsende divergent: {ende}"
|
|
|
|
|
|
|
|
|
|
def test_grundsatzprogramme_haben_keine_regierung(self):
|
|
|
|
|
"""Grundsatzprogramme (ist_grundsatz=True) tragen keine Regierungs-
|
|
|
|
|
bildung — sie sind zeitlos."""
|
|
|
|
|
for bl, parteien in WAHLPROGRAMME.items():
|
|
|
|
|
for partei, info in parteien.items():
|
|
|
|
|
if info.get("ist_grundsatz"):
|
|
|
|
|
assert info.get("regierungsbildung") is None, \
|
|
|
|
|
f"{bl}/{partei} ist Grundsatzprogramm, sollte regierungsbildung=None haben"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestRegierungsHelper:
|
|
|
|
|
def test_regierungsbildung_for_known_bl(self):
|
|
|
|
|
assert regierungsbildung_for("NRW") == "2022-06-29"
|
|
|
|
|
assert regierungsbildung_for("HH") == "2025-05-07"
|
|
|
|
|
|
|
|
|
|
def test_regierungsbildung_for_bund_btw2025(self):
|
|
|
|
|
# BUND tragt nun die BTW-2025-Wahlprogramme; Kabinett Merz I
|
|
|
|
|
# vereidigt 06.05.2025. Grundsatzprogramme bleiben nur in
|
|
|
|
|
# embeddings.PROGRAMME als zweite Referenz.
|
|
|
|
|
assert regierungsbildung_for("BUND") == "2025-05-06"
|
|
|
|
|
|
|
|
|
|
def test_regierungsbildung_for_unknown_bl(self):
|
|
|
|
|
assert regierungsbildung_for("XX") is None
|
|
|
|
|
|
|
|
|
|
def test_regierung_aktuell_true_for_active_bl(self):
|
|
|
|
|
assert regierung_aktuell("NRW") is True
|
|
|
|
|
assert regierung_aktuell("BB") is True
|
|
|
|
|
assert regierung_aktuell("BUND") is True
|
|
|
|
|
|
|
|
|
|
def test_regierungsende_for_active_is_none(self):
|
|
|
|
|
assert regierungsende_for("NRW") is None
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# File existence — every registered file must exist on disk
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
class TestFileExistence:
|
|
|
|
|
"""Catches typos in the file field that would silently break embedding
|
|
|
|
|
indexing or PDF download links."""
|
|
|
|
|
|
|
|
|
|
def test_every_registered_pdf_exists(self):
|
|
|
|
|
missing = []
|
|
|
|
|
for bl, parteien in WAHLPROGRAMME.items():
|
|
|
|
|
for partei, info in parteien.items():
|
|
|
|
|
path = REFERENZEN_PATH / info["file"]
|
|
|
|
|
if not path.exists():
|
|
|
|
|
missing.append(f"{bl}/{partei}: {info['file']}")
|
|
|
|
|
assert not missing, "missing PDFs:\n " + "\n ".join(missing)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# Lookup helpers
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
class TestGetWahlprogramm:
|
|
|
|
|
def test_returns_dict_for_known_combination(self):
|
|
|
|
|
info = get_wahlprogramm("MV", "CDU")
|
|
|
|
|
assert info is not None
|
|
|
|
|
assert info["partei"] == "CDU Mecklenburg-Vorpommern"
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestParteienMitWahlprogramm:
|
|
|
|
|
def test_nrw_has_five_parteien(self):
|
|
|
|
|
parteien = parteien_mit_wahlprogramm("NRW")
|
|
|
|
|
assert len(parteien) == 5
|
|
|
|
|
assert set(parteien) == {"CDU", "SPD", "GRÜNE", "FDP", "AfD"}
|
|
|
|
|
|
|
|
|
|
def test_mv_has_six_parteien(self):
|
|
|
|
|
parteien = parteien_mit_wahlprogramm("MV")
|
|
|
|
|
assert set(parteien) == {"CDU", "SPD", "GRÜNE", "FDP", "AfD", "LINKE"}
|
|
|
|
|
|
|
|
|
|
def test_be_has_five_parteien(self):
|
|
|
|
|
parteien = parteien_mit_wahlprogramm("BE")
|
|
|
|
|
assert set(parteien) == {"CDU", "SPD", "GRÜNE", "LINKE", "AfD"}
|
|
|
|
|
|
chore: konsolidiere Working-Tree mit dev-Stand nach Nextcloud-Sync-Resolution
Mehrtaegiger Sync-Stillstand hatte ueber 50 conflicted-copy-Dateien im
Working-Tree erzeugt. Die jeweils neuere Version wurde basierend auf
md5-Hash-Vergleich zum laufenden gwoe-antragspruefer-dev-Container
eingespielt.
Konsolidiert (38 modifiziert):
- analyzer.py, auswertungen.py, auth.py, config.py, database.py,
drucksache_typen.py, embeddings.py, main.py, models.py, parlamente.py,
ports/llm_bewerter.py, presse_generator.py, redline_utils.py, report.py,
validators.py, wahlprogramm_fetch.py, wahlprogramm-links.yaml,
wahlprogramm-shas.lock.json
- v2-Templates: base, components/{icon, matrix_mini, queue_widget,
result_row}, screens/{admin_queue, admin_stand, aktuelle-themen,
antrag_detail, auswertungen, cluster, landtag_suche, merkliste,
methodik, tags}, static/v2/v2.css
- Tests: test_embeddings (Strict-Mode-Drop in reconstruct_zitate),
test_endpoints_smoke, test_presse_generator, test_report,
test_wahlprogramme (mit TestRegierungsbildung-Block, +120 LOC)
- docker-compose.dev.yml, docs/adr/index.md, docs/reference/api.md, mkdocs.yml
Neuzugaenge:
- app/marker.py, app/pm_render.py — Konsistenz-Marker, PM-Render-Adapter
- app/templates/v2/screens/scorecard{,_portrait,_werkstatt}.html — Cloud-Design-Scorecard
- app/static/v3/, app/templates/v3/ — v3-Layout-Hierarchie
- docs/adr/0010-stimmverhalten-gwoe-aggregat.md
- docs/adr/0011-aktuelle-themen-pm-generator.md
- docs/adr/0012-debug-auth-token-bypass.md
- scripts/{auto-rate-orphans, pm-quality-audit, pm-sample-bundle, rotate-debug-token}.sh
- tests/e2e/test_smoke_browser.py, tests/test_{auto_rate_runs, icons,
marker, pm_render, presse_generator_style, thread_splitter,
v2_pdf_consistency}.py
Plus inhaltlich uebernommen aus dem Conflict-Stand:
- embeddings.py: fw-by-2023.partei korrigiert von "FW" zu "FREIE WAEHLER"
(war Mismatch zu wahlprogramme.py)
- embeddings.py: detailliertere Naming der BTW-2025-Wahlprogramme
Test-Suite-Stand: 1209 passed, 73 skipped (4 pre-existing failures in
test_presse_generator_style.py + 1 collection error in
integration/test_citations_substring.py — beide nicht durch dieses
Konsolidierungs-Commit verursacht).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 00:04:21 +02:00
|
|
|
def test_bund_has_eight_parteien(self):
|
|
|
|
|
# BTW 2025: CDU, CSU, SPD, GRÜNE, FDP, AfD, LINKE, BSW.
|
|
|
|
|
parteien = parteien_mit_wahlprogramm("BUND")
|
|
|
|
|
assert set(parteien) == {"CDU", "CSU", "SPD", "GRÜNE", "FDP", "AfD", "LINKE", "BSW"}
|
|
|
|
|
|
|
|
|
|
def test_by_has_five_parteien(self):
|
|
|
|
|
parteien = parteien_mit_wahlprogramm("BY")
|
|
|
|
|
assert set(parteien) == {"CSU", "FREIE WÄHLER", "GRÜNE", "SPD", "AfD"}
|
|
|
|
|
|
2026-05-08 14:18:41 +02:00
|
|
|
def test_hb_has_five_parteien(self):
|
chore: konsolidiere Working-Tree mit dev-Stand nach Nextcloud-Sync-Resolution
Mehrtaegiger Sync-Stillstand hatte ueber 50 conflicted-copy-Dateien im
Working-Tree erzeugt. Die jeweils neuere Version wurde basierend auf
md5-Hash-Vergleich zum laufenden gwoe-antragspruefer-dev-Container
eingespielt.
Konsolidiert (38 modifiziert):
- analyzer.py, auswertungen.py, auth.py, config.py, database.py,
drucksache_typen.py, embeddings.py, main.py, models.py, parlamente.py,
ports/llm_bewerter.py, presse_generator.py, redline_utils.py, report.py,
validators.py, wahlprogramm_fetch.py, wahlprogramm-links.yaml,
wahlprogramm-shas.lock.json
- v2-Templates: base, components/{icon, matrix_mini, queue_widget,
result_row}, screens/{admin_queue, admin_stand, aktuelle-themen,
antrag_detail, auswertungen, cluster, landtag_suche, merkliste,
methodik, tags}, static/v2/v2.css
- Tests: test_embeddings (Strict-Mode-Drop in reconstruct_zitate),
test_endpoints_smoke, test_presse_generator, test_report,
test_wahlprogramme (mit TestRegierungsbildung-Block, +120 LOC)
- docker-compose.dev.yml, docs/adr/index.md, docs/reference/api.md, mkdocs.yml
Neuzugaenge:
- app/marker.py, app/pm_render.py — Konsistenz-Marker, PM-Render-Adapter
- app/templates/v2/screens/scorecard{,_portrait,_werkstatt}.html — Cloud-Design-Scorecard
- app/static/v3/, app/templates/v3/ — v3-Layout-Hierarchie
- docs/adr/0010-stimmverhalten-gwoe-aggregat.md
- docs/adr/0011-aktuelle-themen-pm-generator.md
- docs/adr/0012-debug-auth-token-bypass.md
- scripts/{auto-rate-orphans, pm-quality-audit, pm-sample-bundle, rotate-debug-token}.sh
- tests/e2e/test_smoke_browser.py, tests/test_{auto_rate_runs, icons,
marker, pm_render, presse_generator_style, thread_splitter,
v2_pdf_consistency}.py
Plus inhaltlich uebernommen aus dem Conflict-Stand:
- embeddings.py: fw-by-2023.partei korrigiert von "FW" zu "FREIE WAEHLER"
(war Mismatch zu wahlprogramme.py)
- embeddings.py: detailliertere Naming der BTW-2025-Wahlprogramme
Test-Suite-Stand: 1209 passed, 73 skipped (4 pre-existing failures in
test_presse_generator_style.py + 1 collection error in
integration/test_citations_substring.py — beide nicht durch dieses
Konsolidierungs-Commit verursacht).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 00:04:21 +02:00
|
|
|
# AfD war wegen Listenstreit nicht zur Bürgerschaftswahl 2023 zugelassen.
|
2026-05-08 14:18:41 +02:00
|
|
|
# Stattdessen ist BIW (Bürger in Wut) als 6. Fraktion in der 21. WP.
|
chore: konsolidiere Working-Tree mit dev-Stand nach Nextcloud-Sync-Resolution
Mehrtaegiger Sync-Stillstand hatte ueber 50 conflicted-copy-Dateien im
Working-Tree erzeugt. Die jeweils neuere Version wurde basierend auf
md5-Hash-Vergleich zum laufenden gwoe-antragspruefer-dev-Container
eingespielt.
Konsolidiert (38 modifiziert):
- analyzer.py, auswertungen.py, auth.py, config.py, database.py,
drucksache_typen.py, embeddings.py, main.py, models.py, parlamente.py,
ports/llm_bewerter.py, presse_generator.py, redline_utils.py, report.py,
validators.py, wahlprogramm_fetch.py, wahlprogramm-links.yaml,
wahlprogramm-shas.lock.json
- v2-Templates: base, components/{icon, matrix_mini, queue_widget,
result_row}, screens/{admin_queue, admin_stand, aktuelle-themen,
antrag_detail, auswertungen, cluster, landtag_suche, merkliste,
methodik, tags}, static/v2/v2.css
- Tests: test_embeddings (Strict-Mode-Drop in reconstruct_zitate),
test_endpoints_smoke, test_presse_generator, test_report,
test_wahlprogramme (mit TestRegierungsbildung-Block, +120 LOC)
- docker-compose.dev.yml, docs/adr/index.md, docs/reference/api.md, mkdocs.yml
Neuzugaenge:
- app/marker.py, app/pm_render.py — Konsistenz-Marker, PM-Render-Adapter
- app/templates/v2/screens/scorecard{,_portrait,_werkstatt}.html — Cloud-Design-Scorecard
- app/static/v3/, app/templates/v3/ — v3-Layout-Hierarchie
- docs/adr/0010-stimmverhalten-gwoe-aggregat.md
- docs/adr/0011-aktuelle-themen-pm-generator.md
- docs/adr/0012-debug-auth-token-bypass.md
- scripts/{auto-rate-orphans, pm-quality-audit, pm-sample-bundle, rotate-debug-token}.sh
- tests/e2e/test_smoke_browser.py, tests/test_{auto_rate_runs, icons,
marker, pm_render, presse_generator_style, thread_splitter,
v2_pdf_consistency}.py
Plus inhaltlich uebernommen aus dem Conflict-Stand:
- embeddings.py: fw-by-2023.partei korrigiert von "FW" zu "FREIE WAEHLER"
(war Mismatch zu wahlprogramme.py)
- embeddings.py: detailliertere Naming der BTW-2025-Wahlprogramme
Test-Suite-Stand: 1209 passed, 73 skipped (4 pre-existing failures in
test_presse_generator_style.py + 1 collection error in
integration/test_citations_substring.py — beide nicht durch dieses
Konsolidierungs-Commit verursacht).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 00:04:21 +02:00
|
|
|
parteien = parteien_mit_wahlprogramm("HB")
|
2026-05-08 14:18:41 +02:00
|
|
|
assert set(parteien) == {"SPD", "CDU", "GRÜNE", "LINKE", "BIW"}
|
chore: konsolidiere Working-Tree mit dev-Stand nach Nextcloud-Sync-Resolution
Mehrtaegiger Sync-Stillstand hatte ueber 50 conflicted-copy-Dateien im
Working-Tree erzeugt. Die jeweils neuere Version wurde basierend auf
md5-Hash-Vergleich zum laufenden gwoe-antragspruefer-dev-Container
eingespielt.
Konsolidiert (38 modifiziert):
- analyzer.py, auswertungen.py, auth.py, config.py, database.py,
drucksache_typen.py, embeddings.py, main.py, models.py, parlamente.py,
ports/llm_bewerter.py, presse_generator.py, redline_utils.py, report.py,
validators.py, wahlprogramm_fetch.py, wahlprogramm-links.yaml,
wahlprogramm-shas.lock.json
- v2-Templates: base, components/{icon, matrix_mini, queue_widget,
result_row}, screens/{admin_queue, admin_stand, aktuelle-themen,
antrag_detail, auswertungen, cluster, landtag_suche, merkliste,
methodik, tags}, static/v2/v2.css
- Tests: test_embeddings (Strict-Mode-Drop in reconstruct_zitate),
test_endpoints_smoke, test_presse_generator, test_report,
test_wahlprogramme (mit TestRegierungsbildung-Block, +120 LOC)
- docker-compose.dev.yml, docs/adr/index.md, docs/reference/api.md, mkdocs.yml
Neuzugaenge:
- app/marker.py, app/pm_render.py — Konsistenz-Marker, PM-Render-Adapter
- app/templates/v2/screens/scorecard{,_portrait,_werkstatt}.html — Cloud-Design-Scorecard
- app/static/v3/, app/templates/v3/ — v3-Layout-Hierarchie
- docs/adr/0010-stimmverhalten-gwoe-aggregat.md
- docs/adr/0011-aktuelle-themen-pm-generator.md
- docs/adr/0012-debug-auth-token-bypass.md
- scripts/{auto-rate-orphans, pm-quality-audit, pm-sample-bundle, rotate-debug-token}.sh
- tests/e2e/test_smoke_browser.py, tests/test_{auto_rate_runs, icons,
marker, pm_render, presse_generator_style, thread_splitter,
v2_pdf_consistency}.py
Plus inhaltlich uebernommen aus dem Conflict-Stand:
- embeddings.py: fw-by-2023.partei korrigiert von "FW" zu "FREIE WAEHLER"
(war Mismatch zu wahlprogramme.py)
- embeddings.py: detailliertere Naming der BTW-2025-Wahlprogramme
Test-Suite-Stand: 1209 passed, 73 skipped (4 pre-existing failures in
test_presse_generator_style.py + 1 collection error in
integration/test_citations_substring.py — beide nicht durch dieses
Konsolidierungs-Commit verursacht).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 00:04:21 +02:00
|
|
|
|
|
|
|
|
def test_he_has_five_parteien(self):
|
|
|
|
|
parteien = parteien_mit_wahlprogramm("HE")
|
|
|
|
|
assert set(parteien) == {"CDU", "SPD", "GRÜNE", "FDP", "AfD"}
|
|
|
|
|
|
|
|
|
|
def test_ni_has_four_parteien(self):
|
|
|
|
|
parteien = parteien_mit_wahlprogramm("NI")
|
|
|
|
|
assert set(parteien) == {"SPD", "CDU", "GRÜNE", "AfD"}
|
|
|
|
|
|
|
|
|
|
def test_sl_has_three_parteien(self):
|
|
|
|
|
parteien = parteien_mit_wahlprogramm("SL")
|
|
|
|
|
assert set(parteien) == {"SPD", "CDU", "AfD"}
|
|
|
|
|
|
|
|
|
|
def test_sn_has_six_parteien(self):
|
|
|
|
|
parteien = parteien_mit_wahlprogramm("SN")
|
|
|
|
|
assert set(parteien) == {"CDU", "SPD", "AfD", "BSW", "LINKE", "GRÜNE"}
|
|
|
|
|
|
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_unknown_bundesland_empty_list(self):
|
|
|
|
|
assert parteien_mit_wahlprogramm("XX") == []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# embeddings.PROGRAMME consistency cross-check
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
class TestEmbeddingsRegistryConsistency:
|
|
|
|
|
"""Every entry in WAHLPROGRAMME must also exist in embeddings.PROGRAMME
|
|
|
|
|
so the indexer can find it. Mismatch is the kind of bug a manual smoke
|
|
|
|
|
misses but would show up during indexing."""
|
|
|
|
|
|
|
|
|
|
def test_every_wahlprogramm_has_embeddings_entry(self):
|
|
|
|
|
from app.embeddings import PROGRAMME
|
|
|
|
|
|
2026-05-07 15:30:41 +02:00
|
|
|
# Match WAHLPROGRAMME-Eintrag → PROGRAMME-Eintrag entweder ueber den
|
|
|
|
|
# file-stem (Standard: "cdu-mv-2021" matcht "cdu-mv-2021") ODER ueber
|
|
|
|
|
# den `pdf`-Wert in PROGRAMME (BUND-Grundsatzprogramme nutzen kuerzere
|
|
|
|
|
# PROGRAMME-Keys wie "cdu-grundsatz" obwohl die Datei
|
|
|
|
|
# "cdu-grundsatzprogramm.pdf" heisst).
|
|
|
|
|
pdf_to_pid = {p.get("pdf"): pid for pid, p in PROGRAMME.items() if p.get("pdf")}
|
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
|
|
|
missing = []
|
|
|
|
|
for bl, parteien in WAHLPROGRAMME.items():
|
|
|
|
|
for partei, info in parteien.items():
|
2026-05-07 15:30:41 +02:00
|
|
|
file_stem = info["file"].rsplit(".", 1)[0]
|
|
|
|
|
if file_stem in PROGRAMME:
|
|
|
|
|
continue
|
|
|
|
|
if info["file"] in pdf_to_pid:
|
|
|
|
|
continue
|
|
|
|
|
missing.append(f"{bl}/{partei} → {info['file']}")
|
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
|
|
|
assert not missing, (
|
|
|
|
|
"WAHLPROGRAMME entries missing in embeddings.PROGRAMME:\n "
|
|
|
|
|
+ "\n ".join(missing)
|
|
|
|
|
)
|
test(#134): Coverage-Backfill drei Module
- app/ingest_votes.py 39.2% → 100%
- TestDownloadPdf: schreibt Bytes, propagiert HTTP-Fehler
- TestCli: --supported, kein-arg-error, fehlender PDF-Pfad,
pdf-Pfad-Run, --url-Download-Pfad, exit-Code 2 bei null Resultaten,
Errors-Liste im Output
- DB-Error-Collection in ingest_pdf
- app/wahlprogramme.py 90.7% → 100%
- TestLoadWahlprogrammText: paged-Datei, Normal-Datei-Fallback,
fehlende Datei
- TestSearchWahlprogramm: leere Returns
- TestFindRelevantQuotes: ValueError bei unbekanntem BL
- TestFormatQuoteForPrompt: leeres Dict
- app/abgeordnetenwatch.py 95.2% → 97.6%
- test_rp_pattern_nr_wp_swap: '/538-18.pdf' → '18/538'
- test_sn_pattern_dok_nr_leg_per_swap: 'dok_nr=2150&leg_per=8' → '8/2150'
Total: 47.59% → 48.69%, 666 → 686 Tests, 0 Failures.
2026-04-28 10:50:26 +02:00
|
|
|
|
|
|
|
|
|
2026-05-08 14:18:41 +02:00
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# Strikte Cross-Konsistenz mit programme.PROGRAMME (Drift-Schutz, ADR 0013)
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
class TestWahlprogrammeProgrammeConsistency:
|
|
|
|
|
"""WAHLPROGRAMME (legacy, mit titel/seiten/regierung) und
|
|
|
|
|
programme.PROGRAMME (zentrale Registry mit Geltungsdaten) speichern
|
|
|
|
|
überlappende Felder. Diese Tests fangen stille Drift, bis #222 die
|
|
|
|
|
Quelle vereinheitlicht (Compat-Shim).
|
|
|
|
|
|
|
|
|
|
Invarianten:
|
|
|
|
|
- Für jedes (bl, partei) in WAHLPROGRAMME liefert
|
|
|
|
|
``aktuelles_wahlprogramm(bl, partei)`` einen Eintrag (nicht None).
|
|
|
|
|
- Der ``pdf``-Wert in PROGRAMME stimmt mit ``file`` in WAHLPROGRAMME
|
|
|
|
|
überein.
|
|
|
|
|
- Die ``partei``-Kurzform stimmt überein (PROGRAMME["partei"] ist die
|
|
|
|
|
Kurzform; WAHLPROGRAMME-Key ist auch Kurzform).
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def test_every_wahlprogramm_has_aktuelles_programm_match(self):
|
|
|
|
|
from app.programme import aktuelles_wahlprogramm
|
|
|
|
|
|
|
|
|
|
mismatches = []
|
|
|
|
|
for bl, parteien in WAHLPROGRAMME.items():
|
|
|
|
|
for partei, info in parteien.items():
|
|
|
|
|
prog = aktuelles_wahlprogramm(bl, partei)
|
|
|
|
|
if prog is None:
|
|
|
|
|
mismatches.append(
|
|
|
|
|
f"{bl}/{partei}: aktuelles_wahlprogramm liefert None, "
|
|
|
|
|
f"obwohl WAHLPROGRAMME-Eintrag {info['file']} existiert"
|
|
|
|
|
)
|
|
|
|
|
assert not mismatches, "\n ".join(mismatches)
|
|
|
|
|
|
|
|
|
|
def test_pdf_filenames_match_between_registries(self):
|
|
|
|
|
from app.programme import aktuelles_wahlprogramm
|
|
|
|
|
|
|
|
|
|
drift = []
|
|
|
|
|
for bl, parteien in WAHLPROGRAMME.items():
|
|
|
|
|
for partei, info in parteien.items():
|
|
|
|
|
prog = aktuelles_wahlprogramm(bl, partei)
|
|
|
|
|
if prog is None:
|
|
|
|
|
continue # vom Vortest abgedeckt
|
|
|
|
|
wp_pdf = info["file"]
|
|
|
|
|
pr_pdf = prog.get("pdf")
|
|
|
|
|
if wp_pdf != pr_pdf:
|
|
|
|
|
drift.append(f"{bl}/{partei}: WAHLPROGRAMME.file={wp_pdf!r} ≠ PROGRAMME.pdf={pr_pdf!r}")
|
|
|
|
|
assert not drift, "Drift zwischen WAHLPROGRAMME und PROGRAMME:\n " + "\n ".join(drift)
|
|
|
|
|
|
|
|
|
|
def test_partei_kurzform_consistency(self):
|
|
|
|
|
"""PROGRAMME["partei"] ist die Kurzform (z.B. 'CDU'), nicht
|
|
|
|
|
die Langform ('CDU NRW'). Test-Sicherheitsnetz, falls jemand
|
|
|
|
|
versehentlich die Langform reinträgt."""
|
|
|
|
|
from app.programme import aktuelles_wahlprogramm
|
|
|
|
|
|
|
|
|
|
wrong = []
|
|
|
|
|
for bl, parteien in WAHLPROGRAMME.items():
|
|
|
|
|
for partei in parteien.keys():
|
|
|
|
|
prog = aktuelles_wahlprogramm(bl, partei)
|
|
|
|
|
if prog is None:
|
|
|
|
|
continue
|
|
|
|
|
if prog.get("partei") != partei:
|
|
|
|
|
wrong.append(
|
|
|
|
|
f"{bl}/{partei}: PROGRAMME.partei={prog.get('partei')!r} "
|
|
|
|
|
f"≠ WAHLPROGRAMME-Key {partei!r}"
|
|
|
|
|
)
|
|
|
|
|
assert not wrong, "\n ".join(wrong)
|
|
|
|
|
|
|
|
|
|
def test_no_orphan_aktuelle_programme_in_registry(self):
|
|
|
|
|
"""Die andere Richtung: jedes aktuelle Wahlprogramm in PROGRAMME
|
|
|
|
|
(gueltig_bis IS NULL, typ='wahlprogramm') muss in WAHLPROGRAMME
|
|
|
|
|
vorhanden sein. Sonst ist die Bewertungs-Pipeline blind dafür."""
|
|
|
|
|
from app.programme import all_programme
|
|
|
|
|
|
|
|
|
|
orphans = []
|
|
|
|
|
for prog in all_programme():
|
|
|
|
|
if prog.get("typ") != "wahlprogramm":
|
|
|
|
|
continue
|
|
|
|
|
if prog.get("gueltig_bis") is not None:
|
|
|
|
|
continue # historisches Programm
|
|
|
|
|
bl = prog.get("bundesland")
|
|
|
|
|
partei = prog.get("partei")
|
|
|
|
|
if bl not in WAHLPROGRAMME:
|
|
|
|
|
orphans.append(f"{prog['id']}: BL {bl} nicht in WAHLPROGRAMME")
|
|
|
|
|
continue
|
|
|
|
|
if partei not in WAHLPROGRAMME[bl]:
|
|
|
|
|
orphans.append(f"{prog['id']}: {bl}/{partei} fehlt in WAHLPROGRAMME")
|
|
|
|
|
assert not orphans, "Aktuelle Wahlprogramme in PROGRAMME ohne WAHLPROGRAMME-Eintrag:\n " + "\n ".join(orphans)
|
|
|
|
|
|
|
|
|
|
|
test(#134): Coverage-Backfill drei Module
- app/ingest_votes.py 39.2% → 100%
- TestDownloadPdf: schreibt Bytes, propagiert HTTP-Fehler
- TestCli: --supported, kein-arg-error, fehlender PDF-Pfad,
pdf-Pfad-Run, --url-Download-Pfad, exit-Code 2 bei null Resultaten,
Errors-Liste im Output
- DB-Error-Collection in ingest_pdf
- app/wahlprogramme.py 90.7% → 100%
- TestLoadWahlprogrammText: paged-Datei, Normal-Datei-Fallback,
fehlende Datei
- TestSearchWahlprogramm: leere Returns
- TestFindRelevantQuotes: ValueError bei unbekanntem BL
- TestFormatQuoteForPrompt: leeres Dict
- app/abgeordnetenwatch.py 95.2% → 97.6%
- test_rp_pattern_nr_wp_swap: '/538-18.pdf' → '18/538'
- test_sn_pattern_dok_nr_leg_per_swap: 'dok_nr=2150&leg_per=8' → '8/2150'
Total: 47.59% → 48.69%, 666 → 686 Tests, 0 Failures.
2026-04-28 10:50:26 +02:00
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# load_wahlprogramm_text — Fallback-Pfade (#134 Coverage-Backfill)
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
class TestLoadWahlprogrammText:
|
|
|
|
|
def test_returns_empty_for_unknown_combination(self):
|
|
|
|
|
from app.wahlprogramme import load_wahlprogramm_text
|
|
|
|
|
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: {"file": "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 zurueckgefallen,
|
|
|
|
|
komplett unter Seite 1."""
|
|
|
|
|
from app import wahlprogramme as wp_mod
|
|
|
|
|
monkeypatch.setattr(wp_mod, "get_wahlprogramm",
|
|
|
|
|
lambda bl, p: {"file": "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 normale Textdatei → leeres Dict."""
|
|
|
|
|
from app import wahlprogramme as wp_mod
|
|
|
|
|
monkeypatch.setattr(wp_mod, "get_wahlprogramm",
|
|
|
|
|
lambda bl, p: {"file": "test.pdf"})
|
|
|
|
|
# tmp_path ist leer
|
|
|
|
|
monkeypatch.setattr(wp_mod, "KONTEXT_PATH", tmp_path)
|
|
|
|
|
|
|
|
|
|
assert wp_mod.load_wahlprogramm_text("X", "Y") == {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestSearchWahlprogramm:
|
|
|
|
|
def test_returns_empty_for_unknown_combination(self):
|
|
|
|
|
from app.wahlprogramme import search_wahlprogramm
|
|
|
|
|
assert search_wahlprogramm("XX", "XYZ", ["test"]) == []
|
|
|
|
|
|
|
|
|
|
def test_returns_empty_when_text_missing(self, monkeypatch):
|
|
|
|
|
"""Bekannte Partei + Bundesland aber keine Textdatei → leer."""
|
|
|
|
|
from app import wahlprogramme as wp_mod
|
|
|
|
|
monkeypatch.setattr(wp_mod, "get_wahlprogramm",
|
|
|
|
|
lambda bl, p: {"file": "missing.pdf"})
|
|
|
|
|
monkeypatch.setattr(wp_mod, "load_wahlprogramm_text",
|
|
|
|
|
lambda bl, p: {})
|
|
|
|
|
assert wp_mod.search_wahlprogramm("X", "Y", ["test"]) == []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestFindRelevantQuotes:
|
|
|
|
|
def test_unknown_bundesland_raises(self):
|
|
|
|
|
from app.wahlprogramme import find_relevant_quotes
|
|
|
|
|
with pytest.raises(ValueError, match="Unbekanntes Bundesland"):
|
|
|
|
|
find_relevant_quotes("Antrag-Text", ["CDU"], bundesland="ZZ")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestFormatQuoteForPrompt:
|
|
|
|
|
def test_empty_quotes_returns_empty_string(self):
|
|
|
|
|
from app.wahlprogramme import format_quote_for_prompt
|
|
|
|
|
assert format_quote_for_prompt({}) == ""
|