- 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.
197 lines
9.5 KiB
Python
197 lines
9.5 KiB
Python
"""Tests for wahlprogramme.py — registry consistency + file existence."""
|
|
import pytest
|
|
|
|
from app.wahlprogramme import (
|
|
WAHLPROGRAMME,
|
|
REFERENZEN_PATH,
|
|
get_wahlprogramm,
|
|
parteien_mit_wahlprogramm,
|
|
)
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# 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")
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# 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"}
|
|
|
|
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
|
|
|
|
# Build expected programm_id from filename: "cdu-mv-2021.pdf" → "cdu-mv-2021"
|
|
missing = []
|
|
for bl, parteien in WAHLPROGRAMME.items():
|
|
for partei, info in parteien.items():
|
|
pid = info["file"].rsplit(".", 1)[0]
|
|
if pid not in PROGRAMME:
|
|
missing.append(f"{bl}/{partei} → {pid}")
|
|
assert not missing, (
|
|
"WAHLPROGRAMME entries missing in embeddings.PROGRAMME:\n "
|
|
+ "\n ".join(missing)
|
|
)
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# 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({}) == ""
|