2026-04-28 23:11:38 +02:00
|
|
|
"""Tests fuer die Phase-2-Stub-Parser (#106 Folge / #148-#163).
|
|
|
|
|
|
|
|
|
|
Pro BL ein Modul `app/protokoll_parsers/<bl>.py`, das:
|
|
|
|
|
1. importierbar ist (Recherche-Findings im Docstring)
|
|
|
|
|
2. ``parse_protocol(...)`` raised ``NotImplementedError`` mit Issue-Verweis
|
|
|
|
|
3. **NICHT** in ``PROTOKOLL_PARSERS``-Registry registriert ist (sonst wuerde
|
|
|
|
|
der Auto-Ingest-Cron versuchen, sie zu nutzen und mit NotImplementedError
|
|
|
|
|
abbrechen)
|
|
|
|
|
|
|
|
|
|
Diese Tests sind das Safety-Net: sobald ein Stub durch einen echten Parser
|
|
|
|
|
ersetzt wird, MUSS gleichzeitig der Eintrag in PROTOKOLL_PARSERS gesetzt
|
|
|
|
|
werden — sonst greift ``test_implemented_parsers_are_registered``.
|
|
|
|
|
"""
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import importlib
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
from app.protokoll_parsers import PROTOKOLL_PARSERS, supported_bundeslaender
|
|
|
|
|
|
|
|
|
|
STUB_BL_CODES = [
|
feat(#153): HB-Parser produktiv — Bremer Beschlussprotokolle (Status-Only)
Bremen publiziert wie Hessen nur Beschlussprotokolle (TOPs + Status-Saetze),
KEINE Wortprotokolle mit Vote-Block. Daher minimaler Parser:
- Drucksache + Status (angenommen/abgelehnt/ueberwiesen)
- Vote-Listen bleiben leer (HB hat keine Fraktions-Detail)
Anchor-Regex: "Die Buergerschaft (Landtag|Stadtbuergerschaft) <verb> <rest> <terminator>"
Verb-Mapping:
- "lehnt ... ab" → abgelehnt
- "stimmt ... zu" → angenommen
- "beschliesst ..." → angenommen
- "verabschiedet ..." → angenommen
- "verweist|ueberweist|leitet" → ueberwiesen
- "nimmt ... Kenntnis" → uebersprungen (kein Vote)
Drucksachen-Aufloesung: erst Inline-Form "(21/N)", dann Block-Form
"Drucksache 21/N" rueckwaerts vom Anchor.
URL-Pattern (verifiziert WP21 Sitzung 33 Land):
https://www.bremische-buergerschaft.de/dokumente/wp21/land/protokoll/b21l{n4}.pdf
Cron unterstuetzt jetzt {n4}-Platzhalter (4-stellig). HB Land WP21
ingestiert via direktes URL-Probing (b21l0001.pdf … b21l9999.pdf).
Stadtbuergerschaft (b21s*) als Folge-Issue.
Tests: 21 HB-Tests, Verifikation S33 → 20 Beschluesse extrahiert.
Stand: 8 produktive Parser (NRW, BUND, BE, HH, TH, HE, SH, HB).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 01:41:40 +02:00
|
|
|
# BUND/BE/HH/TH/HE/SH/HB raus, weil seit 2026-04-28/29 produktive Parser
|
|
|
|
|
"BB", "BW", "BY",
|
2026-04-29 01:29:06 +02:00
|
|
|
"LSA", "MV", "NI", "RP", "SL", "SN",
|
2026-04-28 23:11:38 +02:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(params=STUB_BL_CODES)
|
|
|
|
|
def stub_module(request):
|
|
|
|
|
code = request.param
|
|
|
|
|
mod_name = f"app.protokoll_parsers.{code.lower()}"
|
|
|
|
|
return code, importlib.import_module(mod_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestStubImportability:
|
|
|
|
|
def test_each_stub_imports(self, stub_module):
|
|
|
|
|
code, mod = stub_module
|
|
|
|
|
assert mod is not None
|
|
|
|
|
assert hasattr(mod, "parse_protocol")
|
|
|
|
|
|
|
|
|
|
def test_each_stub_has_recherche_in_docstring(self, stub_module):
|
|
|
|
|
"""Docstring enthaelt 'Recherche'-Marker und Status-Hinweis."""
|
|
|
|
|
code, mod = stub_module
|
|
|
|
|
doc = mod.__doc__ or ""
|
|
|
|
|
assert "Recherche" in doc or "Status" in doc, \
|
|
|
|
|
f"Stub {code}: Docstring enthaelt keine Recherche-Findings"
|
|
|
|
|
|
|
|
|
|
def test_each_stub_links_to_issue(self, stub_module):
|
|
|
|
|
"""Docstring verweist auf konkretes Tracking-Issue."""
|
|
|
|
|
code, mod = stub_module
|
|
|
|
|
doc = mod.__doc__ or ""
|
|
|
|
|
assert "issues/" in doc, f"Stub {code}: kein Issue-Link"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestStubBehavior:
|
|
|
|
|
def test_each_stub_raises_not_implemented(self, stub_module):
|
|
|
|
|
code, mod = stub_module
|
|
|
|
|
with pytest.raises(NotImplementedError) as exc:
|
|
|
|
|
mod.parse_protocol("/dev/null")
|
|
|
|
|
msg = str(exc.value)
|
|
|
|
|
assert "noch nicht implementiert" in msg, \
|
|
|
|
|
f"Stub {code}: NotImplementedError-Message ist nicht informativ"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestRegistryDiscipline:
|
|
|
|
|
"""Sicherheitsnetz: Stubs duerfen NICHT in PROTOKOLL_PARSERS sein.
|
|
|
|
|
|
|
|
|
|
Sobald ein Stub durch echten Parser ersetzt wird, MUSS der Implementer:
|
|
|
|
|
1. NotImplementedError aus parse_protocol entfernen
|
|
|
|
|
2. PROTOKOLL_PARSERS[code] = parse_protocol setzen
|
|
|
|
|
|
|
|
|
|
Dieser Test schlaegt fehl, wenn eines der beiden vergessen wird —
|
|
|
|
|
explizit unten ueber test_implemented_parsers_are_registered.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def test_stubs_not_in_registry(self):
|
|
|
|
|
registered = set(supported_bundeslaender())
|
feat(#153): HB-Parser produktiv — Bremer Beschlussprotokolle (Status-Only)
Bremen publiziert wie Hessen nur Beschlussprotokolle (TOPs + Status-Saetze),
KEINE Wortprotokolle mit Vote-Block. Daher minimaler Parser:
- Drucksache + Status (angenommen/abgelehnt/ueberwiesen)
- Vote-Listen bleiben leer (HB hat keine Fraktions-Detail)
Anchor-Regex: "Die Buergerschaft (Landtag|Stadtbuergerschaft) <verb> <rest> <terminator>"
Verb-Mapping:
- "lehnt ... ab" → abgelehnt
- "stimmt ... zu" → angenommen
- "beschliesst ..." → angenommen
- "verabschiedet ..." → angenommen
- "verweist|ueberweist|leitet" → ueberwiesen
- "nimmt ... Kenntnis" → uebersprungen (kein Vote)
Drucksachen-Aufloesung: erst Inline-Form "(21/N)", dann Block-Form
"Drucksache 21/N" rueckwaerts vom Anchor.
URL-Pattern (verifiziert WP21 Sitzung 33 Land):
https://www.bremische-buergerschaft.de/dokumente/wp21/land/protokoll/b21l{n4}.pdf
Cron unterstuetzt jetzt {n4}-Platzhalter (4-stellig). HB Land WP21
ingestiert via direktes URL-Probing (b21l0001.pdf … b21l9999.pdf).
Stadtbuergerschaft (b21s*) als Folge-Issue.
Tests: 21 HB-Tests, Verifikation S33 → 20 Beschluesse extrahiert.
Stand: 8 produktive Parser (NRW, BUND, BE, HH, TH, HE, SH, HB).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 01:41:40 +02:00
|
|
|
# Aktuell: NRW + BUND + BE + HH + TH + HE + SH + HB produktiv
|
|
|
|
|
assert registered == {"NRW", "BUND", "BE", "HH", "TH", "HE", "SH", "HB"}, (
|
2026-04-28 23:11:38 +02:00
|
|
|
"Unerwartete Registry-Eintraege. Wenn neue BL implementiert sind, "
|
|
|
|
|
"diesen Test anpassen UND den Stub durch echten Parser ersetzen."
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def test_implemented_parsers_are_registered(self, stub_module):
|
|
|
|
|
"""Wenn ein Stub-Modul KEIN NotImplementedError mehr wirft (also
|
|
|
|
|
implementiert wurde), muss es in PROTOKOLL_PARSERS registriert sein.
|
|
|
|
|
|
|
|
|
|
Dieser Test ist der Trigger: sobald der Implementer parse_protocol
|
|
|
|
|
echt implementiert (kein NotImplementedError mehr), schlaegt der
|
|
|
|
|
andere Test (test_each_stub_raises_not_implemented) fehl. Das ist
|
|
|
|
|
das richtige Signal — dann muessen Tests + Registry beide angepasst
|
|
|
|
|
werden.
|
|
|
|
|
"""
|
|
|
|
|
code, mod = stub_module
|
|
|
|
|
# Probe: wirft das Modul noch NotImplementedError?
|
|
|
|
|
try:
|
|
|
|
|
mod.parse_protocol("/dev/null")
|
|
|
|
|
implemented = True
|
|
|
|
|
except NotImplementedError:
|
|
|
|
|
implemented = False
|
|
|
|
|
except Exception:
|
|
|
|
|
# Anderer Fehler (z.B. fitz konnte /dev/null nicht oeffnen) →
|
|
|
|
|
# Modul ist implementiert (wuerde mit echtem PDF arbeiten)
|
|
|
|
|
implemented = True
|
|
|
|
|
|
|
|
|
|
if implemented:
|
|
|
|
|
assert code in PROTOKOLL_PARSERS, (
|
|
|
|
|
f"{code}-Parser scheint implementiert (keine NotImplementedError), "
|
|
|
|
|
f"aber nicht in PROTOKOLL_PARSERS registriert. Beide muessen "
|
|
|
|
|
f"zusammen aktualisiert werden."
|
|
|
|
|
)
|