Schließt #21 (HB-Scraper) und #33 (UI-Aktivierung). Eigenständige ``PARiSHBAdapter``-Klasse für paris.bremische-buergerschaft.de. Backend (HAR-Trace TEMP/paris.bremische-buergerschaft.de.har): - Single-POST gegen ``/starweb/paris/servlet.starweb`` mit form-urlencoded Body - ``path=paris/LISSHFL.web``, ``format=LISSH_BrowseVorgang_Report`` - ``01_LISSHFL_Themen=<query>`` (Volltext-Thesaurus) - ``02_LISSHFL_PARL=S OR L`` (Stadt + Landtag in einem Rutsch) - ``03_LISSHFL_WP=21`` (aktuelle Wahlperiode; Multi-WP-Range timeout-t den Server bei 60s) - Wildcards (``*``) timeout-en ebenfalls — bei leerer Query verwenden wir das hochfrequente Stoppwort ``"der"`` als Catch-all Hit-Format aus dem Single-Page-HTML: - ``<tbody name="RecordRepeater"><tr name="Repeat_TYP">`` - Title in ``<h2><a>`` - ``Drs <b>21/730 S</b>`` mit S/L-Suffix für Stadtbürgerschaft vs Landtag — Drucksachen-IDs werden als ``21/730S`` (ohne Space) gespeichert - ``Änderungsantrag vom 23.02.2026`` (Typ + Datum) - Fraktionen-Liste nach ``<br/>`` - PDF-Link mit ``target="new"`` auf bremische-buergerschaft.de Pipeline: - ``search()`` mit client-side ``"antrag"``-Filter (analog #61), fängt ``"Antrag"``, ``"Änderungsantrag"`` etc. - ``get_document()`` linearer Lookup - ``download_text()`` PDF-via-fitz BL-Eintrag in ``bundeslaender.py``: - ``HB.aktiv = True`` - ``doku_system="PARiS"`` (statt der alten Klassifikation "StarWeb" — PARiS ist eine deutlich abweichende Servlet-Variante, kein eUI) - ``drucksache_format="21/1234S"`` - Test ``test_hb_is_starweb_not_paris`` umbenannt in ``test_hb_is_paris_starweb_variant``, prüft jetzt auf "PARiS" Live-Probe: ``` 21/730S 2026-02-23 | [SPD,GRÜNE,LINKE] | Änderungsantrag | Haushaltsgesetze ... 21/1449 2025-11-05 | [SPD,GRÜNE,LINKE] | Antrag | Finanzierung der Bremischen Häfen 21/555S 2025-06-17 | [CDU] | Antrag | Clima-Campus zügig beantworten ``` 176 Unit-Tests grün, Live-Verifikation Sub-A im Container nach Deploy. Refs: #21, #33, #59 (Phase I) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
98 lines
3.7 KiB
Python
98 lines
3.7 KiB
Python
"""Tests for bundeslaender.py — sanity over the parliament registry.
|
|
|
|
Stand: 16 Bundesländer + Sondereintrag ``BUND`` für den Deutschen
|
|
Bundestag (siehe #56). Der ``Bundesland``-Dataclass-Name ist historisch
|
|
und wird inzwischen als generischer Parlament-Slot verwendet — eine
|
|
Umbenennung ist als separates Refactoring-Item geplant.
|
|
|
|
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_plus_bund(self):
|
|
# 16 echte Bundesländer + BUND-Sondereintrag (#56)
|
|
assert len(BUNDESLAENDER) == 17
|
|
assert "BUND" in BUNDESLAENDER
|
|
|
|
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:
|
|
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
|
|
|
|
def test_alle_bundeslaender_returns_all(self):
|
|
# 16 BL + BUND
|
|
assert len(alle_bundeslaender()) == 17
|
|
|
|
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_paris_starweb_variant(self):
|
|
"""PARiS war als StarWeb-Skin klassifiziert (#48), nach #21/#33
|
|
differenzieren wir auf "PARiS" weil die Servlet-API-Konvention
|
|
deutlich von der modernen StarWeb-Familie abweicht (Form-POST
|
|
statt browse.tt.json/SearchAndDisplay).
|
|
"""
|
|
assert BUNDESLAENDER["HB"].doku_system == "PARiS"
|
|
|
|
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"
|