"""sn_verfahrensgebiet/tests/test_beteiligung_pruefung.py Unit-Tests für :mod:`sn_verfahrensgebiet.functions.beteiligung_pruefung`. Getestete Szenarien: baue_flurstueck_csv: - Normalfall mit führenden Nullen bei Zähler/Nenner - Nenner = 0 → Platzhalter "____" - Nenner leer → Platzhalter "____" - Ungültiges Format (nicht 4 Teile) → None - Leerer Wert → None baue_flurstueck_csv_liste: - Bildet Liste nur aus gültigen Zeilen, schreibt Flurstueck_csv in jede Zeile zurück lade_beteiligung_csv: - Dateidialog abgebrochen → None - Spalte FLURSTUECK fehlt → BeteiligungPruefungFehler - Leere Datei → BeteiligungPruefungFehler - Erfolgsfall → (zeilen, spaltennamen) """ import csv import os import sys import tempfile import types import unittest from unittest.mock import patch, MagicMock # --------------------------------------------------------------------------- # Minimales QGIS-Mock-Setup # --------------------------------------------------------------------------- if "qgis" not in sys.modules: qgis_mod = types.ModuleType("qgis") qgis_core = types.ModuleType("qgis.core") qgis_utils = types.ModuleType("qgis.utils") qgis_utils.iface = types.SimpleNamespace() qgis_utils.plugins = {} qgis_mod.utils = qgis_utils qgis_mod.core = qgis_core sys.modules["qgis"] = qgis_mod sys.modules["qgis.core"] = qgis_core sys.modules["qgis.utils"] = qgis_utils from sn_verfahrensgebiet.functions.beteiligung_pruefung import ( baue_flurstueck_csv, baue_flurstueck_csv_liste, lade_beteiligung_csv, BeteiligungPruefungFehler, BeteiligungErgebnis, FELD_FLURSTUECK_CSV, CSV_SPALTE_FLURSTUECK, CSV_SPALTE_VEF_VKZF, _lade_via_alkis_pipeline_und_vergleiche, _try3_bootstrap_und_try2, ) _MOD = "sn_verfahrensgebiet.functions.beteiligung_pruefung" class TestBaueFlurstueckCsv(unittest.TestCase): def test_normalfall_mit_fuehrenden_nullen(self): ergebnis = baue_flurstueck_csv("1234/123/45/00", "14") self.assertEqual(ergebnis, "141234___00123004500") def test_nenner_null_wird_zu_unterstrichen(self): ergebnis = baue_flurstueck_csv("1234/123/0/00", "14") self.assertEqual(ergebnis, "141234___00123____00") def test_nenner_leer_wird_zu_unterstrichen(self): ergebnis = baue_flurstueck_csv("1234//45/00", "14") # Gemarkung 1234, Zähler leer -> "00000"? -> zfill auf leeren String ergibt "00000" self.assertTrue(ergebnis.startswith("141234___00000")) def test_gemarkung_ohne_fuehrende_nullen_wird_aufgefuellt(self): ergebnis = baue_flurstueck_csv("12/1/1/00", "14") self.assertTrue(ergebnis.startswith("140012___")) def test_ungueltiges_format_gibt_none(self): self.assertIsNone(baue_flurstueck_csv("1234/123/45", "14")) self.assertIsNone(baue_flurstueck_csv("1234/123/45/00/99", "14")) def test_leerer_wert_gibt_none(self): self.assertIsNone(baue_flurstueck_csv("", "14")) self.assertIsNone(baue_flurstueck_csv(None, "14")) def test_nenner_9001_wird_zu_buchstabe_a(self): ergebnis = baue_flurstueck_csv("1234/123/9001/00", "14") self.assertEqual(ergebnis, "141234___00123000a00") def test_nenner_9002_wird_zu_buchstabe_b(self): ergebnis = baue_flurstueck_csv("1234/123/9002/00", "14") self.assertEqual(ergebnis, "141234___00123000b00") def test_nenner_9026_wird_zu_buchstabe_z(self): ergebnis = baue_flurstueck_csv("1234/123/9026/00", "14") self.assertEqual(ergebnis, "141234___00123000z00") def test_nenner_ausserhalb_buchstaben_bereich_bleibt_numerisch(self): ergebnis = baue_flurstueck_csv("1234/123/9027/00", "14") self.assertEqual(ergebnis, "141234___00123902700") def test_nenner_9001_mit_fuehrenden_nullen_wird_erkannt(self): ergebnis = baue_flurstueck_csv("1234/123/09001/00", "14") self.assertEqual(ergebnis, "141234___00123000a00") def test_einstellige_folge_wird_auf_2_stellen_aufgefuellt(self): ergebnis = baue_flurstueck_csv("1234/123/45/5", "14") self.assertEqual(ergebnis, "141234___00123004505") class TestBaueFlurstueckCsvListe(unittest.TestCase): def test_nur_gueltige_zeilen_in_liste(self): zeilen = [ {CSV_SPALTE_FLURSTUECK: "1234/123/45/00"}, {CSV_SPALTE_FLURSTUECK: "ungueltig"}, {CSV_SPALTE_FLURSTUECK: "5678/1/0/01"}, ] ergebnis = baue_flurstueck_csv_liste(zeilen, "14") self.assertEqual(len(ergebnis), 2) self.assertEqual(zeilen[0][FELD_FLURSTUECK_CSV], "141234___00123004500") self.assertEqual(zeilen[1][FELD_FLURSTUECK_CSV], "") self.assertEqual(zeilen[2][FELD_FLURSTUECK_CSV], "145678___00001____01") class TestLadeViaAlkisPipelineUndVergleiche(unittest.TestCase): @patch(f"{_MOD}._try1_listenabgleich") @patch(f"{_MOD}.apply_style") @patch(f"{_MOD}.QgsProject") @patch(f"{_MOD}.verfahrensgebiet_alkis_komplett") def test_pipeline_fehler_wirft_beteiligung_pruefung_fehler( self, mock_pipeline, mock_project_cls, mock_apply_style, mock_try1 ): mock_pipeline.return_value = (None, None, None, None) with self.assertRaises(BeteiligungPruefungFehler): _lade_via_alkis_pipeline_und_vergleiche(object(), ["145678___00001____01"]) mock_try1.assert_not_called() @patch(f"{_MOD}._try1_listenabgleich") @patch(f"{_MOD}.apply_style") @patch(f"{_MOD}.QgsProject") @patch(f"{_MOD}.verfahrensgebiet_alkis_komplett") def test_ruft_pipeline_silent_auf_und_delegiert_an_try1( self, mock_pipeline, mock_project_cls, mock_apply_style, mock_try1 ): tab_widget = object() enriched = MagicMock(name="enriched") enriched.isValid.return_value = True enriched.id.return_value = "vg1" flst = MagicMock(name="flst") flst.isValid.return_value = True flst.name.return_value = "Flurstücke" bror = MagicMock(name="bror") bror.isValid.return_value = True bror.id.return_value = "bror1" mock_pipeline.return_value = (enriched, flst, bror, "FIRST") mock_project = MagicMock() mock_project.mapLayersByName.return_value = [] mock_project.mapLayer.return_value = None mock_project_cls.instance.return_value = mock_project vergleich_layer = MagicMock(name="vergleich") statistik = {"beide": 1, "nur_verfahrensgebiet": 2, "nur_aglb": 3} mock_try1.return_value = (vergleich_layer, statistik) flurstueck_csv_liste = ["145678___00001____01"] ergebnis = _lade_via_alkis_pipeline_und_vergleiche(tab_widget, flurstueck_csv_liste) mock_pipeline.assert_called_once_with(tab_widget, silent=True) mock_try1.assert_called_once_with(tab_widget, enriched, flurstueck_csv_liste) self.assertIsInstance(ergebnis, BeteiligungErgebnis) self.assertEqual(ergebnis.modus, "try2") self.assertEqual(ergebnis.statistik, statistik) self.assertIn("Übereinstimmend: 1", ergebnis.statistik_text) layers = [lyr for lyr, _ in ergebnis.layers] self.assertIn(enriched, layers) self.assertIn(vergleich_layer, layers) self.assertIn(flst, layers) self.assertIn(bror, layers) class TestTry3BootstrapUndTry2(unittest.TestCase): @patch(f"{_MOD}._lade_via_alkis_pipeline_und_vergleiche") @patch(f"{_MOD}.set_variable") def test_setzt_verfahrensnummer_und_ergaenzt_statistik_text( self, mock_set_variable, mock_lade ): basis_ergebnis = BeteiligungErgebnis( modus="try2", layers=[], statistik={"beide": 0, "nur_verfahrensgebiet": 0, "nur_aglb": 0}, statistik_text="Übereinstimmend: 0\n...", ) mock_lade.return_value = basis_ergebnis zeilen = [{CSV_SPALTE_VEF_VKZF: "12345", CSV_SPALTE_FLURSTUECK: "1234/1/1/00"}] spaltennamen = [CSV_SPALTE_VEF_VKZF, CSV_SPALTE_FLURSTUECK] ergebnis = _try3_bootstrap_und_try2(object(), zeilen, spaltennamen, ["kz1"]) mock_set_variable.assert_called_once_with("verfahrensnummer", "12345", scope="project") self.assertEqual(ergebnis.modus, "try3") self.assertTrue(ergebnis.statistik_text.startswith("Verfahrensnummer aus CSV: 12345")) class TestLadeBeteiligungCsv(unittest.TestCase): def setUp(self): self._tmp_files = [] def tearDown(self): for pfad in self._tmp_files: try: os.remove(pfad) except OSError: pass def _schreibe_csv(self, header, zeilen): fd, pfad = tempfile.mkstemp(suffix=".csv") self._tmp_files.append(pfad) with os.fdopen(fd, "w", encoding="utf-8", newline="") as f: writer = csv.writer(f) writer.writerow(header) for zeile in zeilen: writer.writerow(zeile) return pfad @patch(f"{_MOD}.QFileDialog") def test_dialog_abgebrochen_gibt_none(self, mock_fd): mock_fd.getOpenFileName.return_value = ("", "") self.assertIsNone(lade_beteiligung_csv(None)) @patch(f"{_MOD}.QFileDialog") def test_fehlende_flurstueck_spalte_wirft_fehler(self, mock_fd): pfad = self._schreibe_csv(["FOO", "BAR"], [["1", "2"]]) mock_fd.getOpenFileName.return_value = (pfad, "") with self.assertRaises(BeteiligungPruefungFehler) as ctx: lade_beteiligung_csv(None) self.assertIn(CSV_SPALTE_FLURSTUECK, str(ctx.exception)) @patch(f"{_MOD}.QFileDialog") def test_leere_datei_wirft_fehler(self, mock_fd): fd, pfad = tempfile.mkstemp(suffix=".csv") os.close(fd) self._tmp_files.append(pfad) mock_fd.getOpenFileName.return_value = (pfad, "") with self.assertRaises(BeteiligungPruefungFehler): lade_beteiligung_csv(None) @patch(f"{_MOD}.QFileDialog") def test_erfolgsfall_gibt_zeilen_und_spalten_zurueck(self, mock_fd): pfad = self._schreibe_csv( [CSV_SPALTE_VEF_VKZF, "FLA_Beteiligung", CSV_SPALTE_FLURSTUECK], [["12345", "1", "1234/123/45/00"]], ) mock_fd.getOpenFileName.return_value = (pfad, "") zeilen, spalten = lade_beteiligung_csv(None) self.assertEqual(len(zeilen), 1) self.assertIn(CSV_SPALTE_FLURSTUECK, spalten) self.assertEqual(zeilen[0][CSV_SPALTE_FLURSTUECK], "1234/123/45/00") if __name__ == "__main__": unittest.main()