diff --git a/tests/test_protokoll_parsers_hh.py b/tests/test_protokoll_parsers_hh.py new file mode 100644 index 0000000..b69ef81 --- /dev/null +++ b/tests/test_protokoll_parsers_hh.py @@ -0,0 +1,135 @@ +"""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