"""Tests fuer app/protokoll_parsers/hh.py — Hamburger Beschlussprotokoll-Parser (#155). Stichprobe-getestet gegen WP23 Sitzung 22 (Hamburg). """ from __future__ import annotations import pytest from app.protokoll_parsers.hh import ( _normalize_fraktionen_hh, _parse_vote_block_hh, _resolve_drucksache_hh, RESULT_ANCHOR_RE, ALLE_FRAKTIONEN_HH, FRAKTIONEN_MAP_HH, ) class TestNormalizeFraktionenHh: def test_simple_spd(self): assert _normalize_fraktionen_hh("der SPD") == ["SPD"] def test_gruene_n_form(self): assert _normalize_fraktionen_hh("der GRÜNEN") == ["GRÜNE"] def test_die_linke(self): assert _normalize_fraktionen_hh("der Linken") == ["LINKE"] def test_combined_phrase(self): result = _normalize_fraktionen_hh( "der SPD und GRÜNEN gegen die Stimmen der CDU und AfD" ) assert set(result) == {"SPD", "GRÜNE", "CDU", "AfD"} def test_empty_returns_empty(self): assert _normalize_fraktionen_hh("") == [] def test_no_double_count(self): # 'GRÜNEN' und 'GRÜNE' beide → nur 1× GRÜNE result = _normalize_fraktionen_hh("der GRÜNE und der GRÜNEN") assert result.count("GRÜNE") == 1 class TestParseVoteBlockHh: def test_full_block(self): block = ( " mit den Stimmen der SPD und GRÜNEN " "gegen die Stimmen der CDU und AfD " "bei Enthaltung der Linken" ) votes = _parse_vote_block_hh(block) assert set(votes["ja"]) == {"SPD", "GRÜNE"} assert set(votes["nein"]) == {"CDU", "AfD"} assert votes["enthaltung"] == ["LINKE"] def test_only_ja_and_nein(self): block = ( " mit den Stimmen der SPD, GRÜNEN, CDU und Linken " "gegen die Stimmen der AfD" ) votes = _parse_vote_block_hh(block) assert set(votes["ja"]) == {"SPD", "GRÜNE", "CDU", "LINKE"} assert votes["nein"] == ["AfD"] assert votes["enthaltung"] == [] def test_empty_block_returns_empty_lists(self): votes = _parse_vote_block_hh("") assert votes == {"ja": [], "nein": [], "enthaltung": []} def test_only_ja(self): block = " mit den Stimmen der SPD und GRÜNEN" votes = _parse_vote_block_hh(block) assert set(votes["ja"]) == {"SPD", "GRÜNE"} class TestResolveDrucksacheHh: def test_finds_ds_with_full_prefix(self): text = "Drucksache 23/1234 ... mehrheitlich angenommen" anchor = text.index("mehrheitlich") assert _resolve_drucksache_hh(text, anchor) == "23/1234" def test_finds_bare_ds_format(self): text = "Bericht 23/3666 ... einstimmig angenommen" anchor = text.index("einstimmig") assert _resolve_drucksache_hh(text, anchor) == "23/3666" def test_picks_closest(self): text = "Drucksache 23/100 ... Drucksache 23/200 ... einstimmig angenommen" anchor = text.index("einstimmig") assert _resolve_drucksache_hh(text, anchor) == "23/200" def test_returns_none_when_no_ds(self): text = "irgendwas ... einstimmig angenommen" anchor = text.index("einstimmig") assert _resolve_drucksache_hh(text, anchor) is None class TestResultAnchorRegex: def test_einstimmig_angenommen(self): text = "alle Empfehlungen einstimmig angenommen" m = RESULT_ANCHOR_RE.search(text) assert m assert m.group("modus") == "einstimmig" assert m.group("ergebnis") == "angenommen" def test_mehrheitlich_with_block(self): text = ( "Antrag mehrheitlich mit den Stimmen der SPD und GRÜNEN " "gegen die Stimmen der CDU und AfD angenommen" ) m = RESULT_ANCHOR_RE.search(text) assert m assert m.group("modus") == "mehrheitlich" assert m.group("ergebnis") == "angenommen" def test_mehrheitlich_abgelehnt(self): text = ( "Antrag mehrheitlich mit den Stimmen der SPD, GRÜNEN gegen die " "Stimmen der AfD abgelehnt" ) m = RESULT_ANCHOR_RE.search(text) assert m assert m.group("ergebnis") == "abgelehnt" class TestConstants: def test_all_fraktionen_complete(self): assert set(ALLE_FRAKTIONEN_HH) == {"SPD", "GRÜNE", "CDU", "AfD", "LINKE"} def test_mapping_covers_all_fraktionen(self): all_codes = set() for _phrase, codes in FRAKTIONEN_MAP_HH: all_codes.update(codes) for f in ALLE_FRAKTIONEN_HH: assert f in all_codes