"""sn_verfahrensgebiet/tests/test_verfahrensgebiet_shape_komplett.py Unit-Tests für :mod:`sn_verfahrensgebiet.functions.verfahrensgebiet_shape_komplett`. Getestete Szenarien: lade_vg_aus_zip: - Dateidialog abgebrochen (leerer Pfad) → None - ZIP-Entpacken schlägt fehl (Exception) → None + QMessageBox.warning - Kein .shp in ZIP → None + Warning "gültiges Shape-Dateiset angeben" - QgsVectorLayer nicht valid → None + Warning - Leere Geometrien im Shapefile → None + Warning - Erfolgspfad → Memory-Layer mit Union-Geometrie, Name "Verfahrensgebiet" verfahrensgebiet_shape_komplett: - lade_vg_aus_zip None (Abbruch) → (None, None, None, NONE) - flurstueck_alkis NONE → (None, None, None, NONE) - Keine Flurstücke selektiert → (None, flst, None, NONE) + QMessageBox.warning - erstelle_verfahrensgebiet_aus_auswahl ungültig → Workflow noch immer abgeschlossen (temp_enriched-Fehler: stimmt_ueberein=True wegen 0-Fläche) - Flächenübereinstimmung → show_info_dialog, return (shape_vg, flst, None, FIRST) - Flächenabweichung + abbrechen → return (shape_vg, flst, None, FIRST) - Flächenabweichung + detailpruefung → lade_punktort_ta + knickpunkt_pruefung aufgerufen - Cleanup: TempVerfahrensgebiet stets aus Projekt entfernt """ import sys import os import types import unittest from unittest.mock import patch, MagicMock, call # --------------------------------------------------------------------------- # 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.verfahrensgebiet_alkis import LoadStatus from sn_verfahrensgebiet.functions.verfahrensgebiet_shape_komplett import ( verfahrensgebiet_shape_komplett, lade_vg_aus_zip, _finde_shp, _erstelle_memory_layer_aus_shp, ) # --------------------------------------------------------------------------- # Hilfs-Dummies # --------------------------------------------------------------------------- def _make_layer(valid=True, name="Layer", lid="L1", feature_count=1, area=1000.0): layer = MagicMock() layer.isValid.return_value = valid layer.id.return_value = lid layer.name.return_value = name layer.featureCount.return_value = feature_count layer.selectedFeatureCount.return_value = feature_count mock_geom = MagicMock() mock_geom.isEmpty.return_value = False mock_geom.area.return_value = area mock_feat = MagicMock() mock_feat.geometry.return_value = mock_geom layer.getFeatures.return_value = [mock_feat] mock_crs = MagicMock() mock_crs.authid.return_value = "EPSG:25833" layer.crs.return_value = mock_crs return layer class _DummyProject: def __init__(self): self.added = [] self.removed = [] self._layers_by_name: dict = {} def addMapLayer(self, layer): self.added.append(layer) def removeMapLayer(self, lid): self.removed.append(lid) def mapLayersByName(self, name): return self._layers_by_name.get(name, []) def mapLayer(self, lid): return None def instance(self): return self def _make_progress_mock(canceled=False): prog = MagicMock() prog.is_canceled.return_value = canceled return prog # --------------------------------------------------------------------------- # Patch-Konstanten # --------------------------------------------------------------------------- _MOD = "sn_verfahrensgebiet.functions.verfahrensgebiet_shape_komplett" _PATCH_LADE_ZIP = f"{_MOD}.lade_vg_aus_zip" _PATCH_GEM = f"{_MOD}.gemarkung_schluessel_und_namen_von_vg_geometrie" _PATCH_FLST = f"{_MOD}.flurstueck_alkis" _PATCH_SELECT = f"{_MOD}._select_by_location" _PATCH_ERSTELLE = f"{_MOD}.erstelle_verfahrensgebiet_aus_auswahl" _PATCH_PROJECT = f"{_MOD}.QgsProject" _PATCH_MSGBOX = f"{_MOD}.QMessageBox" _PATCH_WARNING = f"{_MOD}.warning" _PATCH_PROGRESS = f"{_MOD}.create_progress_dialog" _PATCH_VERGLEICHE = f"{_MOD}._vergleiche_flaechen" _PATCH_SHOW_INFO = f"{_MOD}.show_info_dialog" _PATCH_ASK_DIALOG = f"{_MOD}.ask_detailpruefung_oder_abbrechen" _PATCH_GP_ALKIS = f"{_MOD}.lade_punktort_ta" _PATCH_KNICK = f"{_MOD}.knickpunkt_pruefung" _PATCH_EXTRAHIERE = f"{_MOD}._extrahiere_knickpunkte" _PATCH_LESE_TOL = f"{_MOD}._lese_flaechen_toleranz" # --------------------------------------------------------------------------- # Tests: _finde_shp # --------------------------------------------------------------------------- class TestFindShp(unittest.TestCase): def test_findet_shp_in_root(self): with patch("os.walk") as mock_walk: mock_walk.return_value = [ ("/extract", [], ["shape.shp", "shape.dbf"]), ] result = _finde_shp("/extract") self.assertEqual(result, os.path.join("/extract", "shape.shp")) def test_findet_shp_in_unterordner(self): with patch("os.walk") as mock_walk: mock_walk.return_value = [ ("/extract", ["sub"], []), ("/extract/sub", [], ["daten.shp"]), ] result = _finde_shp("/extract") self.assertEqual(result, os.path.join("/extract/sub", "daten.shp")) def test_kein_shp_gibt_none_zurueck(self): with patch("os.walk") as mock_walk: mock_walk.return_value = [ ("/extract", [], ["shape.dbf", "shape.prj"]), ] result = _finde_shp("/extract") self.assertIsNone(result) def test_case_insensitive_erkennung(self): with patch("os.walk") as mock_walk: mock_walk.return_value = [ ("/extract", [], ["DATEN.SHP"]), ] result = _finde_shp("/extract") self.assertIsNotNone(result) # --------------------------------------------------------------------------- # Tests: _erstelle_memory_layer_aus_shp # --------------------------------------------------------------------------- class TestErstelleMemoryLayerAusShp(unittest.TestCase): def test_leere_geometrien_geben_none(self): shp_layer = MagicMock() geom = MagicMock() geom.isEmpty.return_value = True feat = MagicMock() feat.geometry.return_value = geom shp_layer.getFeatures.return_value = [feat] shp_layer.crs.return_value.authid.return_value = "EPSG:25833" result = _erstelle_memory_layer_aus_shp(shp_layer) self.assertIsNone(result) def test_keine_features_gibt_none(self): shp_layer = MagicMock() shp_layer.getFeatures.return_value = [] shp_layer.crs.return_value.authid.return_value = "EPSG:25833" result = _erstelle_memory_layer_aus_shp(shp_layer) self.assertIsNone(result) @patch(f"{_MOD}.QgsGeometry") @patch(f"{_MOD}.QgsVectorLayer") @patch(f"{_MOD}.QgsFeature") def test_attribute_von_erstem_feature_werden_gesetzt( self, mock_feat_cls, mock_vl_cls, mock_geom_cls ): """Attributwerte des ersten Features werden auf das Union-Feature übertragen.""" # Geometrie-Mock mock_union = MagicMock() mock_union.isEmpty.return_value = False mock_geom_cls.unaryUnion.return_value = mock_union # Memory-Layer-Mock mock_mem = MagicMock() mock_mem.isValid.return_value = True mock_mem.featureCount.return_value = 1 mock_vl_cls.return_value = mock_mem mock_pr = MagicMock() mock_mem.dataProvider.return_value = mock_pr # Feature-Mock (Output) mock_feat_out = MagicMock() mock_feat_cls.return_value = mock_feat_out # Shapefile-Layer-Mock shp_layer = MagicMock() shp_layer.crs.return_value.authid.return_value = "EPSG:25833" shp_layer.fields.return_value = [MagicMock(), MagicMock()] # list → iterable mock_geom = MagicMock() mock_geom.isEmpty.return_value = False src_feat = MagicMock() src_feat.geometry.return_value = mock_geom src_feat.attributes.return_value = ["wert_a", 42] shp_layer.getFeatures.return_value = [src_feat] result = _erstelle_memory_layer_aus_shp(shp_layer) # Felder sollen übernommen werden mock_pr.addAttributes.assert_called_once() # Attributwerte sollen vom ersten Feature gesetzt werden mock_feat_out.setAttributes.assert_called_once_with(["wert_a", 42]) self.assertIs(result, mock_mem) # --------------------------------------------------------------------------- # Tests: lade_vg_aus_zip # --------------------------------------------------------------------------- class TestLadeVgAusZip(unittest.TestCase): def _patch(self, **kwargs): """Hilfsmethode: kombinierter Patch-Kontext für lade_vg_aus_zip.""" return kwargs @patch(f"{_MOD}.QFileDialog") def test_dialog_abgebrochen_gibt_none_zurueck(self, mock_fd): mock_fd.getOpenFileName.return_value = ("", "") result = lade_vg_aus_zip(None) self.assertIsNone(result) @patch(f"{_MOD}.QMessageBox") @patch(f"{_MOD}.zipfile") @patch(f"{_MOD}.QFileDialog") def test_zip_fehler_gibt_none_und_warning(self, mock_fd, mock_zf, mock_mb): mock_fd.getOpenFileName.return_value = ("/tmp/vg.zip", "") mock_zf.ZipFile.side_effect = Exception("bad zip") with patch("os.path.dirname", return_value="/tmp"), \ patch("os.path.splitext", return_value=("/tmp/vg", ".zip")), \ patch("os.path.basename", return_value="vg.zip"), \ patch("os.path.join", return_value="/tmp/vg"): result = lade_vg_aus_zip(None) self.assertIsNone(result) mock_mb.warning.assert_called_once() @patch(f"{_MOD}.QMessageBox") @patch(f"{_MOD}._finde_shp", return_value=None) @patch(f"{_MOD}.zipfile") @patch(f"{_MOD}.QFileDialog") def test_kein_shp_in_zip_gibt_none_und_warning(self, mock_fd, mock_zf, mock_find, mock_mb): mock_fd.getOpenFileName.return_value = ("/tmp/vg.zip", "") mock_zf.ZipFile.return_value.__enter__ = MagicMock(return_value=MagicMock()) mock_zf.ZipFile.return_value.__exit__ = MagicMock(return_value=False) with patch("os.path.dirname", return_value="/tmp"), \ patch("os.path.splitext", return_value=("/tmp/vg", ".zip")), \ patch("os.path.basename", return_value="vg.zip"), \ patch("os.path.join", return_value="/tmp/vg"): result = lade_vg_aus_zip(None) self.assertIsNone(result) mock_mb.warning.assert_called_once() args = mock_mb.warning.call_args[0] self.assertIn("gültiges Shape-Dateiset angeben", args) @patch(f"{_MOD}.QMessageBox") @patch(f"{_MOD}.QgsVectorLayer") @patch(f"{_MOD}._finde_shp", return_value="/tmp/vg/shape.shp") @patch(f"{_MOD}.zipfile") @patch(f"{_MOD}.QFileDialog") def test_invalider_qgslayer_gibt_none_und_warning( self, mock_fd, mock_zf, mock_find, mock_vl, mock_mb ): mock_fd.getOpenFileName.return_value = ("/tmp/vg.zip", "") mock_zf.ZipFile.return_value.__enter__ = MagicMock(return_value=MagicMock()) mock_zf.ZipFile.return_value.__exit__ = MagicMock(return_value=False) invalid_layer = MagicMock() invalid_layer.isValid.return_value = False mock_vl.return_value = invalid_layer with patch("os.path.dirname", return_value="/tmp"), \ patch("os.path.splitext", return_value=("/tmp/vg", ".zip")), \ patch("os.path.basename", return_value="vg.zip"), \ patch("os.path.join", return_value="/tmp/vg"): result = lade_vg_aus_zip(None) self.assertIsNone(result) mock_mb.warning.assert_called_once() args = mock_mb.warning.call_args[0] self.assertIn("gültiges Shape-Dateiset angeben", args) @patch(f"{_MOD}.QMessageBox") @patch(f"{_MOD}._erstelle_memory_layer_aus_shp", return_value=None) @patch(f"{_MOD}.QgsVectorLayer") @patch(f"{_MOD}._finde_shp", return_value="/tmp/vg/shape.shp") @patch(f"{_MOD}.zipfile") @patch(f"{_MOD}.QFileDialog") def test_leere_geometrien_geben_none_und_warning( self, mock_fd, mock_zf, mock_find, mock_vl, mock_mem, mock_mb ): mock_fd.getOpenFileName.return_value = ("/tmp/vg.zip", "") mock_zf.ZipFile.return_value.__enter__ = MagicMock(return_value=MagicMock()) mock_zf.ZipFile.return_value.__exit__ = MagicMock(return_value=False) valid_layer = MagicMock() valid_layer.isValid.return_value = True mock_vl.return_value = valid_layer with patch("os.path.dirname", return_value="/tmp"), \ patch("os.path.splitext", return_value=("/tmp/vg", ".zip")), \ patch("os.path.basename", return_value="vg.zip"), \ patch("os.path.join", return_value="/tmp/vg"): result = lade_vg_aus_zip(None) self.assertIsNone(result) mock_mb.warning.assert_called_once() @patch(f"{_MOD}._erstelle_memory_layer_aus_shp") @patch(f"{_MOD}.QgsVectorLayer") @patch(f"{_MOD}._finde_shp", return_value="/tmp/vg/shape.shp") @patch(f"{_MOD}.zipfile") @patch(f"{_MOD}.QFileDialog") def test_erfolg_gibt_memory_layer_zurueck( self, mock_fd, mock_zf, mock_find, mock_vl, mock_mem ): mock_fd.getOpenFileName.return_value = ("/tmp/vg.zip", "") mock_zf.ZipFile.return_value.__enter__ = MagicMock(return_value=MagicMock()) mock_zf.ZipFile.return_value.__exit__ = MagicMock(return_value=False) valid_layer = MagicMock() valid_layer.isValid.return_value = True mock_vl.return_value = valid_layer mem_layer = _make_layer(name="Verfahrensgebiet", lid="MEM1") mock_mem.return_value = mem_layer with patch("os.path.dirname", return_value="/tmp"), \ patch("os.path.splitext", return_value=("/tmp/vg", ".zip")), \ patch("os.path.basename", return_value="vg.zip"), \ patch("os.path.join", return_value="/tmp/vg"): result = lade_vg_aus_zip(None) self.assertIs(result, mem_layer) # --------------------------------------------------------------------------- # Tests: verfahrensgebiet_shape_komplett (Hauptworkflow) # --------------------------------------------------------------------------- class TestVerfahrensgebietShapeKomplett(unittest.TestCase): # ------------------------------------------------------------------ # # Abbruchpfade # ------------------------------------------------------------------ # @patch(_PATCH_PROGRESS) @patch(_PATCH_PROJECT) @patch(_PATCH_LADE_ZIP) def test_lade_zip_none_aborts(self, mock_lade, mock_proj, mock_prog): mock_proj.instance.return_value = _DummyProject() mock_lade.return_value = None mock_prog.return_value = _make_progress_mock() enriched, flst, bror, status = verfahrensgebiet_shape_komplett(None) self.assertIsNone(enriched) self.assertIsNone(flst) self.assertIsNone(bror) self.assertEqual(status, LoadStatus.NONE) @patch(_PATCH_PROGRESS) @patch(_PATCH_PROJECT) @patch(_PATCH_GEM) @patch(_PATCH_LADE_ZIP) def test_shape_vg_sofort_ins_projekt_nach_laden(self, mock_lade, mock_gem, mock_proj, mock_prog): """Shape-VG wird unmittelbar nach dem Laden ins Projekt eingefügt.""" proj = _DummyProject() mock_proj.instance.return_value = proj shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1") mock_lade.return_value = shape_vg mock_gem.return_value = [] mock_prog.return_value = _make_progress_mock() with patch(_PATCH_FLST, return_value=(None, LoadStatus.NONE)): verfahrensgebiet_shape_komplett(None) # Shape-VG muss ins Projekt eingefügt worden sein self.assertIn(shape_vg, proj.added) @patch(_PATCH_PROGRESS) @patch(_PATCH_PROJECT) @patch(_PATCH_GEM) @patch(_PATCH_LADE_ZIP) def test_shape_vg_wird_bei_abbruch_aus_projekt_entfernt( self, mock_lade, mock_gem, mock_proj, mock_prog ): """Shape-VG wird bei Fehler/Abbruch aus dem Projekt entfernt.""" proj = _DummyProject() mock_proj.instance.return_value = proj shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1") mock_lade.return_value = shape_vg mock_gem.return_value = [] mock_prog.return_value = _make_progress_mock() with patch(_PATCH_FLST, return_value=(None, LoadStatus.NONE)): _, _, _, status = verfahrensgebiet_shape_komplett(None) self.assertEqual(status, LoadStatus.NONE) # Layer-ID muss in removed sein self.assertIn("S1", proj.removed) @patch(_PATCH_PROGRESS) @patch(_PATCH_PROJECT) @patch(_PATCH_GEM) @patch(_PATCH_LADE_ZIP) def test_shape_vg_bleibt_im_projekt_bei_erfolg(self, mock_lade, mock_gem, mock_proj, mock_prog): """Bei erfolgreichem Abschluss wird shape_vg NICHT aus dem Projekt entfernt.""" proj = _DummyProject() mock_proj.instance.return_value = proj shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1") mock_lade.return_value = shape_vg mock_gem.return_value = [] flst_layer = _make_layer(name="Flurstücke", lid="F1") mock_prog.return_value = _make_progress_mock() with patch(_PATCH_FLST, return_value=(flst_layer, LoadStatus.FIRST)), \ patch(_PATCH_SELECT, return_value=5), \ patch(_PATCH_ERSTELLE, return_value=(_make_layer(lid="T1"), 5, 0)), \ patch(_PATCH_VERGLEICHE, return_value=(1000.0, 1000.0, True)), \ patch(_PATCH_SHOW_INFO): verfahrensgebiet_shape_komplett(None) # Layer-ID darf NICHT in removed sein self.assertNotIn("S1", proj.removed) @patch(_PATCH_PROGRESS) @patch(_PATCH_PROJECT) @patch(_PATCH_GEM) @patch(_PATCH_LADE_ZIP) def test_flst_none_aborts(self, mock_lade, mock_gem, mock_proj, mock_prog): mock_proj.instance.return_value = _DummyProject() mock_lade.return_value = _make_layer(name="Verfahrensgebiet") mock_gem.return_value = [("1234", "Muster")] mock_prog.return_value = _make_progress_mock() with patch(_PATCH_FLST, return_value=(None, LoadStatus.NONE)): enriched, flst, bror, status = verfahrensgebiet_shape_komplett(None) self.assertIsNone(enriched) self.assertIsNone(flst) self.assertEqual(status, LoadStatus.NONE) @patch(_PATCH_PROGRESS) @patch(_PATCH_MSGBOX) @patch(_PATCH_SELECT) @patch(_PATCH_PROJECT) @patch(_PATCH_GEM) @patch(_PATCH_FLST) @patch(_PATCH_LADE_ZIP) def test_keine_flurstuecke_selektiert_gibt_warning( self, mock_lade, mock_flst, mock_gem, mock_proj, mock_select, mock_mb, mock_prog ): mock_proj.instance.return_value = _DummyProject() mock_lade.return_value = _make_layer(name="Verfahrensgebiet") mock_gem.return_value = [] flst_layer = _make_layer(name="Flurstücke") mock_flst.return_value = (flst_layer, LoadStatus.FIRST) mock_select.return_value = 0 mock_prog.return_value = _make_progress_mock() enriched, flst, bror, status = verfahrensgebiet_shape_komplett(None) self.assertIsNone(enriched) self.assertIs(flst, flst_layer) self.assertIsNone(bror) self.assertEqual(status, LoadStatus.NONE) mock_mb.warning.assert_called_once() # ------------------------------------------------------------------ # # Flächenübereinstimmung # ------------------------------------------------------------------ # @patch(_PATCH_SHOW_INFO) @patch(_PATCH_PROGRESS) @patch(_PATCH_VERGLEICHE) @patch(_PATCH_ERSTELLE) @patch(_PATCH_SELECT) @patch(_PATCH_PROJECT) @patch(_PATCH_GEM) @patch(_PATCH_FLST) @patch(_PATCH_LADE_ZIP) def test_flaechen_uebereinstimmung_gibt_info_und_kein_dialog( self, mock_lade, mock_flst, mock_gem, mock_proj, mock_select, mock_erstelle, mock_vergleiche, mock_prog, mock_info ): proj = _DummyProject() mock_proj.instance.return_value = proj shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1") mock_lade.return_value = shape_vg mock_gem.return_value = [("1234", "Muster")] flst_layer = _make_layer(name="Flurstücke", lid="F1") mock_flst.return_value = (flst_layer, LoadStatus.FIRST) mock_select.return_value = 5 temp_enriched = _make_layer(name="Verfahrensgebiet", lid="T1") mock_erstelle.return_value = (temp_enriched, 5, 0) mock_vergleiche.return_value = (1000.0, 1000.0, True) mock_prog.return_value = _make_progress_mock() enriched, flst, bror, status = verfahrensgebiet_shape_komplett(None) self.assertIs(enriched, shape_vg) self.assertIs(flst, flst_layer) self.assertIsNone(bror) self.assertEqual(status, LoadStatus.FIRST) mock_info.assert_called_once() # ------------------------------------------------------------------ # # Flächenabweichung + Abbrechen # ------------------------------------------------------------------ # @patch(_PATCH_ASK_DIALOG) @patch(_PATCH_SHOW_INFO) @patch(_PATCH_PROGRESS) @patch(_PATCH_VERGLEICHE) @patch(_PATCH_ERSTELLE) @patch(_PATCH_SELECT) @patch(_PATCH_PROJECT) @patch(_PATCH_GEM) @patch(_PATCH_FLST) @patch(_PATCH_LADE_ZIP) def test_abweichung_abbrechen_gibt_shape_vg_zurueck( self, mock_lade, mock_flst, mock_gem, mock_proj, mock_select, mock_erstelle, mock_vergleiche, mock_prog, mock_info, mock_ask ): proj = _DummyProject() mock_proj.instance.return_value = proj shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1") mock_lade.return_value = shape_vg mock_gem.return_value = [] flst_layer = _make_layer(name="Flurstücke", lid="F1") mock_flst.return_value = (flst_layer, LoadStatus.FIRST) mock_select.return_value = 5 temp_enriched = _make_layer(name="Verfahrensgebiet", lid="T1") mock_erstelle.return_value = (temp_enriched, 5, 0) mock_vergleiche.return_value = (1000.0, 800.0, False) mock_prog.return_value = _make_progress_mock() mock_ask.return_value = "abbrechen" enriched, flst, bror, status = verfahrensgebiet_shape_komplett(None) self.assertIs(enriched, shape_vg) self.assertIs(flst, flst_layer) self.assertIsNone(bror) self.assertEqual(status, LoadStatus.FIRST) mock_info.assert_not_called() # ------------------------------------------------------------------ # # Detailprüfung # ------------------------------------------------------------------ # @patch(_PATCH_SHOW_INFO) @patch(_PATCH_KNICK) @patch(_PATCH_GP_ALKIS) @patch(_PATCH_EXTRAHIERE) @patch(_PATCH_ASK_DIALOG) @patch(_PATCH_PROGRESS) @patch(_PATCH_VERGLEICHE) @patch(_PATCH_ERSTELLE) @patch(_PATCH_SELECT) @patch(_PATCH_PROJECT) @patch(_PATCH_GEM) @patch(_PATCH_FLST) @patch(_PATCH_LADE_ZIP) def test_abweichung_detailpruefung_ruft_knickpunkt_pruefung_auf( self, mock_lade, mock_flst, mock_gem, mock_proj, mock_select, mock_erstelle, mock_vergleiche, mock_prog, mock_ask, mock_extrahiere, mock_gp, mock_knick, mock_info ): proj = _DummyProject() mock_proj.instance.return_value = proj shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1") mock_lade.return_value = shape_vg mock_gem.return_value = [("1234", "Muster")] flst_layer = _make_layer(name="Flurstücke", lid="F1") mock_flst.return_value = (flst_layer, LoadStatus.FIRST) mock_select.return_value = 5 temp_enriched = _make_layer(name="Verfahrensgebiet", lid="T1") mock_erstelle.return_value = (temp_enriched, 5, 0) mock_vergleiche.return_value = (1000.0, 800.0, False) mock_prog.return_value = _make_progress_mock() mock_ask.return_value = "detailpruefung" knickpunkte_mock = [MagicMock() for _ in range(3)] mock_extrahiere.return_value = knickpunkte_mock pta_layer = _make_layer(name="PunktortTA", lid="P1") mock_gp.return_value = pta_layer ergebnis = _make_layer(name="Knickpunkte", lid="K1") mock_knick.return_value = (ergebnis, 2, 1, 0) enriched, flst, bror, status = verfahrensgebiet_shape_komplett(None) mock_gp.assert_called_once() mock_knick.assert_called_once() self.assertIs(enriched, shape_vg) self.assertEqual(status, LoadStatus.FIRST) @patch(_PATCH_SHOW_INFO) @patch(_PATCH_KNICK) @patch(_PATCH_GP_ALKIS, return_value=None) @patch(_PATCH_EXTRAHIERE) @patch(_PATCH_ASK_DIALOG) @patch(_PATCH_PROGRESS) @patch(_PATCH_VERGLEICHE) @patch(_PATCH_ERSTELLE) @patch(_PATCH_SELECT) @patch(_PATCH_PROJECT) @patch(_PATCH_GEM) @patch(_PATCH_FLST) @patch(_PATCH_LADE_ZIP) def test_punktort_ta_none_gibt_shape_vg_zurueck_und_zeigt_fehler( self, mock_lade, mock_flst, mock_gem, mock_proj, mock_select, mock_erstelle, mock_vergleiche, mock_prog, mock_ask, mock_extrahiere, mock_gp, mock_knick, mock_info ): proj = _DummyProject() mock_proj.instance.return_value = proj shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1") mock_lade.return_value = shape_vg mock_gem.return_value = [] flst_layer = _make_layer(name="Flurstücke", lid="F1") mock_flst.return_value = (flst_layer, LoadStatus.FIRST) mock_select.return_value = 5 temp_enriched = _make_layer(name="Verfahrensgebiet", lid="T1") mock_erstelle.return_value = (temp_enriched, 5, 0) mock_vergleiche.return_value = (1000.0, 800.0, False) mock_prog.return_value = _make_progress_mock() mock_ask.return_value = "detailpruefung" mock_extrahiere.return_value = [MagicMock()] with patch(f"{_MOD}.QMessageBox") as mock_mb: enriched, flst, bror, status = verfahrensgebiet_shape_komplett(None) mock_knick.assert_not_called() self.assertIs(enriched, shape_vg) mock_mb.critical.assert_called_once() # ------------------------------------------------------------------ # # Cleanup: TempVerfahrensgebiet # ------------------------------------------------------------------ # @patch(_PATCH_PROGRESS) @patch(_PATCH_GEM) @patch(_PATCH_PROJECT) @patch(_PATCH_LADE_ZIP) def test_temp_layer_wird_stets_entfernt(self, mock_lade, mock_proj, mock_gem, mock_prog): proj = _DummyProject() temp_layer = _make_layer(name="TempVerfahrensgebiet", lid="TEMP") proj._layers_by_name["TempVerfahrensgebiet"] = [temp_layer] mock_proj.instance.return_value = proj mock_lade.return_value = None # → sofortiger Abbruch nach Schritt 1 mock_gem.return_value = [] mock_prog.return_value = _make_progress_mock() verfahrensgebiet_shape_komplett(None) self.assertIn("TEMP", proj.removed) # ------------------------------------------------------------------ # # Rückgabe-Konsistenz # ------------------------------------------------------------------ # @patch(_PATCH_SHOW_INFO) @patch(_PATCH_PROGRESS) @patch(_PATCH_VERGLEICHE) @patch(_PATCH_ERSTELLE) @patch(_PATCH_SELECT) @patch(_PATCH_PROJECT) @patch(_PATCH_GEM) @patch(_PATCH_FLST) @patch(_PATCH_LADE_ZIP) def test_bror_layer_ist_stets_none( self, mock_lade, mock_flst, mock_gem, mock_proj, mock_select, mock_erstelle, mock_vergleiche, mock_prog, mock_info ): """Dritter Rückgabewert ist im Shape-Workflow immer None (kein BROR).""" mock_proj.instance.return_value = _DummyProject() shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1") mock_lade.return_value = shape_vg mock_gem.return_value = [] flst_layer = _make_layer(name="Flurstücke", lid="F1") mock_flst.return_value = (flst_layer, LoadStatus.FIRST) mock_select.return_value = 5 temp_enriched = _make_layer(lid="T1") mock_erstelle.return_value = (temp_enriched, 5, 0) mock_vergleiche.return_value = (1000.0, 1000.0, True) mock_prog.return_value = _make_progress_mock() _, _, bror, _ = verfahrensgebiet_shape_komplett(None) self.assertIsNone(bror) # ------------------------------------------------------------------ # # BROR-Ladelogik # ------------------------------------------------------------------ # @patch(_PATCH_SHOW_INFO) @patch(_PATCH_PROGRESS) @patch(_PATCH_VERGLEICHE) @patch(_PATCH_SELECT) @patch(_PATCH_PROJECT) @patch(_PATCH_GEM) @patch(_PATCH_FLST) @patch(f"{_MOD}.verfahrensgebiet_alkis") @patch(f"{_MOD}.get_variable", return_value="VF-2026-001") @patch(_PATCH_LADE_ZIP) def test_bror_geladen_und_zurueckgegeben_wenn_vkz_gesetzt( self, mock_lade, mock_get_var, mock_vg_alkis, mock_flst, mock_gem, mock_proj, mock_select, mock_vergleiche, mock_prog, mock_info ): """VKZ gesetzt → verfahrensgebiet_alkis aufgerufen, bror_layer zurückgegeben.""" proj = _DummyProject() mock_proj.instance.return_value = proj shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1") mock_lade.return_value = shape_vg mock_gem.return_value = [] flst_layer = _make_layer(name="Flurstücke", lid="F1") mock_flst.return_value = (flst_layer, LoadStatus.FIRST) mock_select.return_value = 5 bror = _make_layer(name="Verfahrensgebiet", lid="BROR") mock_vg_alkis.return_value = (bror, LoadStatus.FIRST, []) mock_vergleiche.return_value = (1000.0, 1000.0, True) mock_prog.return_value = _make_progress_mock() enriched, flst, bror_ret, status = verfahrensgebiet_shape_komplett(None) mock_vg_alkis.assert_called_once() self.assertIs(bror_ret, bror) self.assertIs(enriched, shape_vg) self.assertEqual(status, LoadStatus.FIRST) @patch(_PATCH_SHOW_INFO) @patch(_PATCH_PROGRESS) @patch(_PATCH_VERGLEICHE) @patch(_PATCH_SELECT) @patch(_PATCH_PROJECT) @patch(_PATCH_GEM) @patch(_PATCH_FLST) @patch(f"{_MOD}.verfahrensgebiet_alkis") @patch(f"{_MOD}.get_variable", return_value="VF-2026-001") @patch(_PATCH_LADE_ZIP) def test_bror_wird_vor_shape_vg_ins_projekt_eingefuegt( self, mock_lade, mock_get_var, mock_vg_alkis, mock_flst, mock_gem, mock_proj, mock_select, mock_vergleiche, mock_prog, mock_info ): """BROR wird VOR shape_vg ins Projekt eingefügt (kein Namenskonflikt).""" proj = _DummyProject() mock_proj.instance.return_value = proj shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1") mock_lade.return_value = shape_vg mock_gem.return_value = [] flst_layer = _make_layer(name="Flurstücke", lid="F1") mock_flst.return_value = (flst_layer, LoadStatus.FIRST) mock_select.return_value = 5 bror = _make_layer(name="Verfahrensgebiet", lid="BROR") mock_vg_alkis.return_value = (bror, LoadStatus.FIRST, []) mock_vergleiche.return_value = (1000.0, 1000.0, True) mock_prog.return_value = _make_progress_mock() verfahrensgebiet_shape_komplett(None) # BROR muss in added sein self.assertIn(bror, proj.added) # BROR muss vor shape_vg hinzugefügt werden bror_idx = proj.added.index(bror) shape_idx = proj.added.index(shape_vg) self.assertLess(bror_idx, shape_idx, "BROR muss vor shape_vg eingefügt werden") @patch(_PATCH_PROGRESS) @patch(_PATCH_VERGLEICHE) @patch(_PATCH_SELECT) @patch(_PATCH_PROJECT) @patch(_PATCH_GEM) @patch(_PATCH_FLST) @patch(f"{_MOD}.verfahrensgebiet_alkis") @patch(f"{_MOD}.get_variable", return_value="VF-2026-001") @patch(_PATCH_LADE_ZIP) def test_bror_wird_bei_abbruch_aus_projekt_entfernt( self, mock_lade, mock_get_var, mock_vg_alkis, mock_flst, mock_gem, mock_proj, mock_select, mock_vergleiche, mock_prog ): """BROR und shape_vg werden bei Fehler aus dem Projekt entfernt.""" proj = _DummyProject() mock_proj.instance.return_value = proj shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1") mock_lade.return_value = shape_vg mock_gem.return_value = [] bror = _make_layer(name="Verfahrensgebiet", lid="BROR") mock_vg_alkis.return_value = (bror, LoadStatus.FIRST, []) mock_flst.return_value = (None, LoadStatus.NONE) # → Abbruch mock_prog.return_value = _make_progress_mock() _, _, _, status = verfahrensgebiet_shape_komplett(None) self.assertEqual(status, LoadStatus.NONE) self.assertIn("S1", proj.removed) self.assertIn("BROR", proj.removed) @patch(_PATCH_PROGRESS) @patch(_PATCH_PROJECT) @patch(_PATCH_GEM) @patch(f"{_MOD}.verfahrensgebiet_alkis") @patch(f"{_MOD}.get_variable", return_value="") @patch(_PATCH_LADE_ZIP) def test_verfahrensgebiet_alkis_nicht_aufgerufen_wenn_vkz_leer( self, mock_lade, mock_get_var, mock_vg_alkis, mock_gem, mock_proj, mock_prog ): """Leere VKZ → verfahrensgebiet_alkis wird NICHT aufgerufen.""" mock_proj.instance.return_value = _DummyProject() mock_lade.return_value = _make_layer(name="Verfahrensgebiet") mock_gem.return_value = [] mock_prog.return_value = _make_progress_mock() with patch(_PATCH_FLST, return_value=(None, LoadStatus.NONE)): verfahrensgebiet_shape_komplett(None) mock_vg_alkis.assert_not_called() @patch(_PATCH_SHOW_INFO) @patch(_PATCH_PROGRESS) @patch(_PATCH_VERGLEICHE) @patch(_PATCH_SELECT) @patch(_PATCH_PROJECT) @patch(_PATCH_GEM) @patch(_PATCH_FLST) @patch(f"{_MOD}.verfahrensgebiet_alkis") @patch(f"{_MOD}.get_variable", return_value="VF-2026-001") @patch(_PATCH_LADE_ZIP) def test_bror_als_erstes_argument_beim_flaechenvergleich( self, mock_lade, mock_get_var, mock_vg_alkis, mock_flst, mock_gem, mock_proj, mock_select, mock_vergleiche, mock_prog, mock_info ): """Mit BROR: _vergleiche_flaechen wird (bror, shape_vg, ...) aufgerufen.""" proj = _DummyProject() mock_proj.instance.return_value = proj shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1") mock_lade.return_value = shape_vg mock_gem.return_value = [] flst_layer = _make_layer(name="Flurstücke", lid="F1") mock_flst.return_value = (flst_layer, LoadStatus.FIRST) mock_select.return_value = 5 bror = _make_layer(name="Verfahrensgebiet", lid="BROR") mock_vg_alkis.return_value = (bror, LoadStatus.FIRST, []) mock_vergleiche.return_value = (1000.0, 1000.0, True) mock_prog.return_value = _make_progress_mock() verfahrensgebiet_shape_komplett(None) # Erster Arg = bror, zweiter Arg = shape_vg call_args = mock_vergleiche.call_args[0] self.assertIs(call_args[0], bror) self.assertIs(call_args[1], shape_vg) @patch(_PATCH_SHOW_INFO) @patch(_PATCH_PROGRESS) @patch(_PATCH_VERGLEICHE) @patch(_PATCH_ERSTELLE) @patch(_PATCH_SELECT) @patch(_PATCH_PROJECT) @patch(_PATCH_GEM) @patch(_PATCH_FLST) @patch(f"{_MOD}.verfahrensgebiet_alkis", return_value=(None, LoadStatus.NONE, [])) @patch(f"{_MOD}.get_variable", return_value="VF-2026-001") @patch(_PATCH_LADE_ZIP) def test_fallback_auf_temp_enriched_wenn_bror_ladeversagen( self, mock_lade, mock_get_var, mock_vg_alkis, mock_flst, mock_gem, mock_proj, mock_select, mock_erstelle, mock_vergleiche, mock_prog, mock_info ): """BROR-Ladeversagen → Fallback: _vergleiche_flaechen mit shape_vg vs temp_enriched.""" proj = _DummyProject() mock_proj.instance.return_value = proj shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1") mock_lade.return_value = shape_vg mock_gem.return_value = [] flst_layer = _make_layer(name="Flurstücke", lid="F1") mock_flst.return_value = (flst_layer, LoadStatus.FIRST) mock_select.return_value = 5 temp_enriched = _make_layer(lid="T1") mock_erstelle.return_value = (temp_enriched, 5, 0) mock_vergleiche.return_value = (1000.0, 1000.0, True) mock_prog.return_value = _make_progress_mock() _, _, bror_ret, _ = verfahrensgebiet_shape_komplett(None) # kein BROR zurückgegeben self.assertIsNone(bror_ret) # Fallback: erste Arg = shape_vg call_args = mock_vergleiche.call_args[0] self.assertIs(call_args[0], shape_vg) if __name__ == "__main__": unittest.main()