Reaktiviert die in Phase J vertagte Adapter-Implementation: statt
ASP.NET-Postbacks zu simulieren (blockt durch __VIEWSTATE-Komplexität
plus robots.txt: Disallow: /), liest die neue ``SNEdasXmlAdapter``-
Klasse einen wöchentlich manuell aus EDAS exportierten XML-Dump.
Workflow:
1. User exportiert in der EDAS-Suchmaske mit Filter "Dokumententyp =
Antr" einen XML-Dump (bis zu 2500 Treffer/Export, sortiert
newest-first nach Datum)
2. Datei wird unter ``data/sn-edas-export.xml`` abgelegt (ins
persistent volume des prod-containers)
3. ``search()``/``get_document()`` lesen die XML-Datei lokal — keine
Server-Calls gegen edas.landtag.sachsen.de
4. ``download_text()`` resolved die echte PDF-URL on-demand über einen
einzelnen GET gegen ``viewer_navigation.aspx`` (single GET, kein
Postback) und holt dann das PDF von ``ws.landtag.sachsen.de/images``
XML-Schema (ISO-8859-1):
- ``<ID>`` interne EDAS-Doc-ID
- ``<Wahlperiode>``, ``<Dokumentenart>``, ``<Dokumentennummer>``
- ``<Fundstelle>`` z.B. ``"Antr CDU, BSW, SPD 01.10.2024 Drs 8/2"`` —
enthält Typ, Urheber und Datum, parsen via Regex
- ``<Titel>`` Volltext-Titel
PDF-URL-Schema (extrahiert aus dem viewer_navigation.aspx onLoad-
Handler): ``ws.landtag.sachsen.de/images/{wp}_Drs_{nr}_{...}.pdf``
mit variablen Suffix-Komponenten — wir machen die Resolution lazy.
Mapper-Erweiterung:
- ``parteien.PARTEIEN``-Tabelle um ``BÜNDNISGRÜNE``/``Bündnisgrüne``
ergänzt — der Sachsen-spezifische zusammengeschriebene Eigenname der
GRÜNEN-Fraktion (sonst wären 8/2100 etc. mit leerer Fraktionen-Liste
rausgekommen)
BL-Eintrag:
- ``SN.aktiv = True``
- ``doku_system="EDAS-XML-Export"`` (klare Klassifikation, dass es
KEIN normaler Webcrawler ist)
- Test ``test_sn_is_eigensystem_not_parldok`` umbenannt in
``test_sn_uses_xml_export_not_parldok``
Live-Probe lokal:
```
search('Klima', limit=5):
8/2100 2025-03-17 | [GRÜNE] | Fahrradoffensive Sachsen ...
7/192 2019-10-11 | [LINKE] | Erste Schritte zur Klimager...
7/2067 2020-03-19 | [CDU, SPD, GRÜNE] | Sächsische Waldbesitzer ...
```
176 Unit-Tests grün. Container braucht beim Deploy einen XML-Upload
ins data/-Volume — separater scp-Schritt.
Refs: #26, #38, #59 (Phase J revived)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
101 lines
3.9 KiB
Python
101 lines
3.9 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_uses_xml_export_not_parldok(self):
|
|
"""EDAS ist ASP.NET-Webforms (NICHT ParlDok-kompatibel) und
|
|
zusätzlich per robots.txt vom Crawling ausgeschlossen. Wir lesen
|
|
stattdessen einen wöchentlich manuell exportierten XML-Dump aus
|
|
data/sn-edas-export.xml — Klassifikation entsprechend "EDAS-XML-Export"."""
|
|
assert BUNDESLAENDER["SN"].doku_system == "EDAS-XML-Export"
|
|
|
|
|
|
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"
|