Vier Sub-Issues unter Umbrella #50 — opt-in via 'pytest -m integration', Default-Suite (77 Unit-Tests) bleibt unberührt. - Sub-Issue A (#51): test_adapters_live.py — pro aktivem BL Reachability, Drucksache-ID-Format, Type-Filter, Datum-/Fraktion-Plausibilität, PDF-Link-HEAD-Probe (slow). NI als xfail (Login-Wall). - Sub-Issue B (#52): test_frontend_xref.py + ground_truth.py — pro BL ein manuell kuratiertes Frontend-Sample (Drucksache + Title-Substring + Fraktionen + Datum + PDF-URL), gegen das adapter.get_document() gespiegelt wird. Fängt Bug-Klasse 14 (Cross-Bundesland-Match). - Sub-Issue C (#53): test_wahlprogramme_indexed.py — Indexing-Status pro aktivem BL aus embeddings.db, PDF-Inhalts-Plausibilität (14 Marker + Wahlperioden-Horizont), expliziter Anti-Marker für Bug-Klasse 8 (CDU-BE 2021 vs 2026 PDF-Tausch durch abgeordnetenwatch). - Sub-Issue D (#54): test_citations_substring.py — Property-Verification: jedes vom LLM zitierte Snippet muss als (whitespace-normalisierter) Substring auf der angegebenen PDF-Seite vorhanden sein. Strict-Match mit Truncation-Marker-Toleranz, kein Fuzzy. Liest reale Assessments aus gwoe-antraege.db. Fängt Bug-Klassen 7/10/17 (Halluzination). Architektur: separates tests/integration/ Verzeichnis mit eigenem conftest.py, das die Stubs der Unit-Suite (fitz/bs4/openai/pydantic_settings) gezielt entfernt und auf echte Module umstellt — mit Fallback-Skip via pytest.require_module wenn lokale Dev-Maschine die Prod-Deps nicht hat. 206 neue Integration-Tests, 13 Helper-Unit-Tests. 77 Unit-Tests bleiben grün. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
106 lines
3.9 KiB
Python
106 lines
3.9 KiB
Python
"""Sub-Issue B — Adapter ↔ Frontend Cross-Validation.
|
|
|
|
Pro aktivem BL ist im ``ground_truth.py``-Modul ein einzelnes Drucksachen-
|
|
Tupel kuratiert, das aus der echten Frontend-Suche des jeweiligen
|
|
Landtags stammt. Dieser Test ruft ``adapter.get_document(...)`` mit der
|
|
bekannten ID auf und prüft, dass:
|
|
|
|
- die Drucksache überhaupt gefunden wird
|
|
- der Title (substring) passt
|
|
- die erwarteten Fraktionen drin sind
|
|
- das Datum (wenn gesetzt im Sample) übereinstimmt
|
|
- der PDF-Link das erwartete URL-Fragment enthält
|
|
|
|
Bug-Klassen aus den letzten Sessions, die diese Datei abdeckt:
|
|
- 14 (get_document() liefert Match aus falschem Bundesland)
|
|
- Allgemeine Schema-Drift in URL-Strukturen, Hit-Format-Änderungen,
|
|
Encoding-Bugs, Pagination-Cut-Offs, Adapter-Reuse-Konfigurations-Fehler
|
|
|
|
Issue: #52 (Sub-Issue B des Umbrella #50)
|
|
|
|
Wartung: siehe Doku im ``ground_truth.py``-Header.
|
|
"""
|
|
import pytest
|
|
|
|
from app.bundeslaender import aktive_bundeslaender
|
|
from app.parlamente import ADAPTERS
|
|
|
|
from .ground_truth import GROUND_TRUTH
|
|
|
|
|
|
pytestmark = pytest.mark.integration
|
|
|
|
|
|
_ACTIVE_CODES = {bl.code for bl in aktive_bundeslaender()}
|
|
|
|
# Skip Samples für BL die nicht (mehr) aktiv sind
|
|
_GT_PARAMS = [pytest.param(gt, id=gt.bundesland) for gt in GROUND_TRUTH if gt.bundesland in _ACTIVE_CODES]
|
|
|
|
|
|
@pytest.mark.parametrize("gt", _GT_PARAMS)
|
|
async def test_adapter_finds_known_drucksache(gt):
|
|
"""Cross-Validation gegen die Frontend-Suche des jeweiligen Landtags.
|
|
|
|
Wenn dieser Test fehlschlägt: erst den Frontend-URL aus
|
|
``gt.frontend_search_url`` öffnen und prüfen, ob die Drucksache
|
|
überhaupt noch existiert. Wenn ja → Adapter-Bug. Wenn nein → ein
|
|
neues Sample im ``ground_truth.py`` aufnehmen.
|
|
"""
|
|
if gt.bundesland not in ADAPTERS:
|
|
pytest.skip(f"{gt.bundesland} hat keinen registrierten Adapter")
|
|
if not gt.title_substring:
|
|
pytest.skip(
|
|
f"{gt.bundesland}: Sample noch nicht kuratiert "
|
|
"(title_substring leer in ground_truth.py)"
|
|
)
|
|
|
|
adapter = ADAPTERS[gt.bundesland]
|
|
doc = await adapter.get_document(gt.drucksache)
|
|
assert doc is not None, (
|
|
f"{gt.bundesland} adapter ({type(adapter).__name__}) hat die "
|
|
f"bekannte Drucksache {gt.drucksache!r} nicht gefunden. Frontend-"
|
|
f"Probe: {gt.frontend_search_url}"
|
|
)
|
|
|
|
# 1. Drucksachen-Nummer roundtrip
|
|
assert doc.drucksache == gt.drucksache, (
|
|
f"{gt.bundesland}: get_document({gt.drucksache!r}) lieferte "
|
|
f"abweichende drucksache={doc.drucksache!r}"
|
|
)
|
|
|
|
# 2. Title-Substring
|
|
assert gt.title_substring.lower() in doc.title.lower(), (
|
|
f"{gt.bundesland}: title_substring {gt.title_substring!r} nicht "
|
|
f"in adapter-title {doc.title!r}"
|
|
)
|
|
|
|
# 3. Erwartete Fraktionen sind alle da (Subset-Match — Adapter darf
|
|
# mehr Fraktionen erkennen als das Sample erwartet)
|
|
if gt.expected_fraktionen:
|
|
adapter_fraktionen = set(doc.fraktionen)
|
|
missing = gt.expected_fraktionen - adapter_fraktionen
|
|
assert not missing, (
|
|
f"{gt.bundesland}: erwartete Fraktionen {gt.expected_fraktionen} "
|
|
f"nicht alle im Adapter-Output {adapter_fraktionen}; fehlt: {missing}"
|
|
)
|
|
|
|
# 4. Datum (nur wenn das Sample eines hat)
|
|
if gt.datum:
|
|
assert doc.datum == gt.datum, (
|
|
f"{gt.bundesland}: erwartetes datum={gt.datum!r}, adapter lieferte "
|
|
f"{doc.datum!r}"
|
|
)
|
|
|
|
# 5. PDF-Link enthält erwartetes URL-Fragment
|
|
if gt.pdf_url_substring:
|
|
assert gt.pdf_url_substring.lower() in doc.link.lower(), (
|
|
f"{gt.bundesland}: pdf_url_substring {gt.pdf_url_substring!r} "
|
|
f"nicht in adapter-link {doc.link!r}"
|
|
)
|
|
|
|
# 6. Bundesland-Konsistenz — fängt Bug-Klasse 14 (Cross-Bundesland-Match)
|
|
assert doc.bundesland == gt.bundesland, (
|
|
f"adapter[{gt.bundesland}].get_document() lieferte ein Doc mit "
|
|
f"bundesland={doc.bundesland!r}"
|
|
)
|