import sys import types import unittest from unittest.mock import patch if "qgis" not in sys.modules: qgis_mod = types.ModuleType("qgis") qgis_utils = types.ModuleType("qgis.utils") qgis_utils.iface = types.SimpleNamespace( mapCanvas=lambda: types.SimpleNamespace(setExtent=lambda _e: None, refresh=lambda: None) ) qgis_utils.plugins = {} qgis_mod.utils = qgis_utils sys.modules["qgis"] = qgis_mod sys.modules["qgis.utils"] = qgis_utils from sn_verfahrensgebiet.ui.tabs.working_tab import WorkingTab, LoadStatus class _DummyLayer: def __init__(self, valid=True, layer_id="L1", layer_name="DummyLayer"): self._valid = valid self._id = layer_id self._name = layer_name def isValid(self): return self._valid def id(self): return self._id def name(self): return self._name def extent(self): return object() class _DummyProject: def __init__(self): self.added = [] def addMapLayer(self, layer): self.added.append(layer) class _DummyCanvas: def __init__(self): self.extent = None def setExtent(self, extent): self.extent = extent def refresh(self): return None class _DummyButton: def __init__(self): self._enabled = True self._text = "Aus ALKIS laden" def setEnabled(self, enabled): self._enabled = enabled def isEnabled(self): return self._enabled def setText(self, text): self._text = text def text(self): return self._text class _DummyCombo: def __init__(self): self.layer = None def setLayer(self, layer): self.layer = layer class _DummyGemFeature: def __init__(self, schluessel, name): self._schluessel = schluessel self._name = name def __getitem__(self, key): if key == "schluessel": return self._schluessel if key == "ave_name": return self._name return None class _DummyGemarkungenLayer: def __init__(self, selected): self._selected = list(selected) def selectedFeatureCount(self): return len(self._selected) def selectedFeatures(self): return list(self._selected) class TestWorkingTab(unittest.TestCase): def _make_tab(self): tab = WorkingTab.__new__(WorkingTab) tab.btn_verf_alkis = _DummyButton() tab.btn_flurst_alkis = _DummyButton() tab.btn_gem_alkis = _DummyButton() tab.btn_grenz_alkis = _DummyButton() tab.btn_verf_aus_auswahl = _DummyButton() tab.btn_verf_aus_liste = _DummyButton() tab.verf_layer_combo = _DummyCombo() tab.haken_verf = types.SimpleNamespace(setText=lambda _t: None, setStyleSheet=lambda _s: None) tab.haken_flurst = types.SimpleNamespace(setText=lambda _t: None, setStyleSheet=lambda _s: None) tab.haken_gem = types.SimpleNamespace(setText=lambda _t: None, setStyleSheet=lambda _s: None) tab.haken_grenz = types.SimpleNamespace(setText=lambda _t: None, setStyleSheet=lambda _s: None) tab.btn_flurst_aus_gemarkungen = _DummyButton() tab.setze_haken = lambda *_args, **_kwargs: None # Pruefmanager-Mock: verarbeite() wird im Test ggf. überschrieben tab.pruefmanager = types.SimpleNamespace( verarbeite=lambda ergebnis: ergebnis ) return tab def test_set_busy_updates_button_state(self): tab = self._make_tab() tab._set_busy(tab.btn_verf_alkis, True) self.assertFalse(tab.btn_verf_alkis.isEnabled()) self.assertEqual(tab.btn_verf_alkis.text(), "Lade ...") tab._set_busy(tab.btn_verf_alkis, False) self.assertTrue(tab.btn_verf_alkis.isEnabled()) self.assertEqual(tab.btn_verf_alkis.text(), "Aus ALKIS laden") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id") def test_on_verf_layer_changed_valid_sets_variable(self, mock_set_layer_id): tab = self._make_tab() layer = _DummyLayer(valid=True, layer_id="layer_123") tab._on_verf_layer_changed(layer) mock_set_layer_id.assert_called_once_with("layer_123") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id") def test_on_verf_layer_changed_none_clears_variable(self, mock_set_layer_id): tab = self._make_tab() tab._on_verf_layer_changed(None) mock_set_layer_id.assert_called_once_with("") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.success") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.apply_style") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_alkis_komplett") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id") def test_handle_verf_alkis_success_path( self, mock_set_layer_id, mock_iface, mock_alkis_komplett, mock_project_instance, mock_apply_style, mock_success, ): """verfahrensgebiet_alkis_komplett → 4-Tupel (enriched, flst, bror, FIRST).""" tab = self._make_tab() enriched = _DummyLayer(valid=True, layer_id="enriched_ok") flst = _DummyLayer(valid=True, layer_id="flst_ok") mock_alkis_komplett.return_value = (enriched, flst, None, LoadStatus.FIRST) proj = _DummyProject() proj.mapLayersByName = lambda _n: [] # kein bestehender Flst-Layer mock_project_instance.return_value = proj mock_iface.mapCanvas.return_value = _DummyCanvas() tab.handle_verf_alkis() mock_apply_style.assert_called() mock_success.assert_called_once() mock_set_layer_id.assert_called_with("enriched_ok") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id") @patch( "sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_alkis_komplett", return_value=(None, None, None, LoadStatus.NONE), ) def test_handle_verf_alkis_failure_clears_layer_var(self, _mock_alkis, mock_set_layer_id): """Status NONE → Layer-Variable wird auf '' gesetzt.""" tab = self._make_tab() tab.handle_verf_alkis() mock_set_layer_id.assert_called_once_with("") # ------------------------------------------------------------------ # handle_flurst_alkis / handle_flurst_aus_gemarkungen (#47: eine gemeinsame # Ladelogik in flurstueck_alkis(), hier wird nur noch die Anbindung geprüft) # ------------------------------------------------------------------ @patch("sn_verfahrensgebiet.ui.tabs.working_tab.success") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.apply_style") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.flurstueck_alkis") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface") def test_handle_flurst_alkis_first_load_uebernimmt_layer( self, mock_iface, mock_project_instance, mock_flurstueck_alkis, mock_apply_style, mock_success ): """Erstmaliges Laden über 'Flurstücke laden' fügt den Layer dem Projekt hinzu.""" tab = self._make_tab() mock_iface.mapCanvas.return_value = _DummyCanvas() project = _DummyProject() mock_project_instance.return_value = project layer = _DummyLayer(valid=True, layer_id="flst_first") mock_flurstueck_alkis.return_value = (layer, LoadStatus.FIRST) tab.handle_flurst_alkis() mock_flurstueck_alkis.assert_called_once_with(tab) self.assertIn(layer, project.added) mock_success.assert_called_once() @patch("sn_verfahrensgebiet.ui.tabs.working_tab.success") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.flurstueck_alkis") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface") def test_handle_flurst_aus_gemarkungen_ruft_flurstueck_alkis_ohne_schluessel_abfrage( self, mock_iface, mock_project_instance, mock_flurstueck_alkis, mock_success ): """Der Gemarkungen-Auswahl-Button ruft dieselbe Ladefunktion mit ohne_schluessel_abfrage=True auf (#47).""" tab = self._make_tab() mock_iface.mapCanvas.return_value = _DummyCanvas() gem_feat = _DummyGemFeature("110001", "Musterstadt") gem_layer = _DummyGemarkungenLayer([gem_feat]) project = types.SimpleNamespace( mapLayersByName=lambda name: [gem_layer] if name == "Gemarkungen" else [] ) mock_project_instance.return_value = project layer = _DummyLayer(valid=True, layer_id="flst_append") mock_flurstueck_alkis.return_value = (layer, LoadStatus.APPEND) tab.handle_flurst_aus_gemarkungen() mock_flurstueck_alkis.assert_called_once() _args, kwargs = mock_flurstueck_alkis.call_args self.assertEqual(kwargs.get("vorgabe_schluessel"), ["0001"]) self.assertEqual(kwargs.get("vorgabe_namen"), ["Musterstadt"]) self.assertTrue(kwargs.get("ohne_schluessel_abfrage")) mock_success.assert_called_once() @patch("sn_verfahrensgebiet.ui.tabs.working_tab.error") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.success") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.flurstueck_alkis") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance") def test_handle_flurst_aus_gemarkungen_abbruch_zeigt_keine_zusaetzliche_meldung( self, mock_project_instance, mock_flurstueck_alkis, mock_success, mock_error ): """Bricht flurstueck_alkis() ab (None, NONE), zeigt der Button keine eigene Meldung – flurstueck_alkis() hat das schon getan (#47).""" tab = self._make_tab() gem_feat = _DummyGemFeature("110001", "Musterstadt") gem_layer = _DummyGemarkungenLayer([gem_feat]) project = types.SimpleNamespace( mapLayersByName=lambda name: [gem_layer] if name == "Gemarkungen" else [] ) mock_project_instance.return_value = project mock_flurstueck_alkis.return_value = (None, LoadStatus.NONE) tab.handle_flurst_aus_gemarkungen() mock_success.assert_not_called() mock_error.assert_not_called() # ------------------------------------------------------------------ # handle_grenz_alkis # ------------------------------------------------------------------ @patch("sn_verfahrensgebiet.ui.tabs.working_tab.grenzpunkt_alkis") def test_handle_grenz_alkis_success_sets_haken(self, mock_grenz): """Grenzpunkte erfolgreich geladen → haken_grenz wird gesetzt.""" tab = self._make_tab() haken_calls = [] tab.setze_haken = lambda lbl, v: haken_calls.append((lbl, v)) pta = _DummyLayer(valid=True) gp = _DummyLayer(valid=True) mock_grenz.return_value = (pta, gp) tab.handle_grenz_alkis() self.assertEqual(len(haken_calls), 1) self.assertIs(haken_calls[0][0], tab.haken_grenz) self.assertTrue(haken_calls[0][1]) @patch("sn_verfahrensgebiet.ui.tabs.working_tab.grenzpunkt_alkis") def test_handle_grenz_alkis_none_result_sets_no_haken(self, mock_grenz): """grenzpunkt_alkis liefert (None, None) → kein Haken.""" tab = self._make_tab() haken_calls = [] tab.setze_haken = lambda lbl, v: haken_calls.append((lbl, v)) mock_grenz.return_value = (None, None) tab.handle_grenz_alkis() self.assertEqual(len(haken_calls), 0) # Button wieder aktiviert (finally) self.assertTrue(tab.btn_grenz_alkis.isEnabled()) # ------------------------------------------------------------------ # handle_flurst_alkis # ------------------------------------------------------------------ @patch("sn_verfahrensgebiet.ui.tabs.working_tab.success") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.apply_style") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.flurstueck_alkis") def test_handle_flurst_alkis_success_sets_haken_and_style( self, mock_alkis, mock_iface, mock_project, mock_apply_style, mock_success ): tab = self._make_tab() haken_calls = [] tab.setze_haken = lambda lbl, v: haken_calls.append((lbl, v)) layer = _DummyLayer(valid=True) mock_alkis.return_value = (layer, LoadStatus.FIRST) mock_project.return_value = _DummyProject() mock_iface.mapCanvas.return_value = _DummyCanvas() tab.handle_flurst_alkis() mock_apply_style.assert_called_once_with(layer, "GIS_Flst_Beschriftung_ALKIS_NAS.qml") mock_success.assert_called_once() self.assertEqual(len(haken_calls), 1) self.assertTrue(haken_calls[0][1]) # haken gesetzt @patch("sn_verfahrensgebiet.ui.tabs.working_tab.info") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.flurstueck_alkis") def test_handle_flurst_alkis_existing_layer_keep_path(self, mock_alkis, mock_info): tab = self._make_tab() haken_calls = [] tab.setze_haken = lambda lbl, v: haken_calls.append((lbl, v)) layer = _DummyLayer(valid=True) mock_alkis.return_value = (layer, LoadStatus.KEEP) tab.handle_flurst_alkis() mock_info.assert_called_once() self.assertEqual(len(haken_calls), 1) @patch("sn_verfahrensgebiet.ui.tabs.working_tab.flurstueck_alkis") def test_handle_flurst_alkis_invalid_input_returns_early(self, mock_alkis): """Status NONE (z. B. Nutzerabbruch) → kein Layer, kein Haken.""" tab = self._make_tab() haken_calls = [] tab.setze_haken = lambda lbl, v: haken_calls.append((lbl, v)) mock_alkis.return_value = (None, LoadStatus.NONE) tab.handle_flurst_alkis() self.assertEqual(len(haken_calls), 0) # Button muss am Ende wieder freigegeben sein (finally) self.assertTrue(tab.btn_flurst_alkis.isEnabled()) # ------------------------------------------------------------------ # handle_verf_aus_auswahl – Vorbedingungsprüfungen # ------------------------------------------------------------------ @patch("sn_verfahrensgebiet.ui.tabs.working_tab.warning") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface") def test_handle_verfahrensgebiet_from_selection_requires_active_layer( self, mock_iface, mock_warning ): """Kein aktiver Layer → warning, kein Layer wird erstellt.""" tab = self._make_tab() mock_iface.activeLayer.return_value = None tab.handle_verf_aus_auswahl() mock_warning.assert_called_once() self.assertIn("aktiver Layer", mock_warning.call_args[0][1]) @patch("sn_verfahrensgebiet.ui.tabs.working_tab.warning") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.pruefe_flurstueck_layer") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface") def test_handle_verfahrensgebiet_from_selection_requires_required_fields( self, mock_iface, mock_pruefe, mock_warning ): """Layer ohne Pflichtfelder → warning, kein Layer wird erstellt.""" from sn_basis.modules.pruef_ergebnis import pruef_ergebnis as PruefErg tab = self._make_tab() active = _DummyLayer(valid=True) mock_iface.activeLayer.return_value = active mock_pruefe.return_value = PruefErg( ok=False, aktion="felder_fehlen", meldung="Feld fehlt" ) tab.handle_verf_aus_auswahl() mock_warning.assert_called_once() @patch("sn_verfahrensgebiet.ui.tabs.working_tab.warning") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.pruefe_auswahl") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.pruefe_flurstueck_layer") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface") def test_handle_verfahrensgebiet_from_selection_requires_selection( self, mock_iface, mock_pruefe_layer, mock_pruefe_auswahl, mock_warning ): """Keine Flurstück-Auswahl → warning, kein Layer erstellt.""" from sn_basis.modules.pruef_ergebnis import pruef_ergebnis as PruefErg tab = self._make_tab() active = _DummyLayer(valid=True) mock_iface.activeLayer.return_value = active mock_pruefe_layer.return_value = PruefErg(ok=True, aktion="ok") mock_pruefe_auswahl.return_value = PruefErg( ok=False, aktion="layer_leer", meldung="Keine Auswahl" ) tab.handle_verf_aus_auswahl() mock_warning.assert_called_once() # ------------------------------------------------------------------ # handle_verf_aus_auswahl – Erstlauf (kein bestehender Layer) # ------------------------------------------------------------------ @patch("sn_verfahrensgebiet.ui.tabs.working_tab.QMessageBox") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.apply_style") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.erstelle_verfahrensgebiet_aus_auswahl") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.pruefe_auswahl") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.pruefe_flurstueck_layer") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface") def test_handle_verfahrensgebiet_from_selection_new_layer_success( self, mock_iface, mock_pruefe_layer, mock_pruefe_auswahl, mock_erstelle, mock_project_inst, mock_apply_style, mock_msgbox ): """Erstlauf ohne bestehenden Layer → Layer wird erstellt und hinzugefügt.""" from sn_basis.modules.pruef_ergebnis import pruef_ergebnis as PruefErg tab = self._make_tab() haken_calls = [] tab.setze_haken = lambda lbl, v: haken_calls.append(v) active = _DummyLayer(valid=True) mock_iface.activeLayer.return_value = active mock_iface.mapCanvas.return_value = _DummyCanvas() mock_pruefe_layer.return_value = PruefErg(ok=True, aktion="ok") mock_pruefe_auswahl.return_value = PruefErg(ok=True, aktion="ok", meldung="3 Flurstücke") new_layer = _DummyLayer(valid=True, layer_id="new_verf") mock_erstelle.return_value = (new_layer, 3, 0) project = _DummyProject() project.mapLayersByName = lambda _name: [] # kein bestehender Layer mock_project_inst.return_value = project tab.handle_verf_aus_auswahl() self.assertIn(new_layer, project.added) mock_apply_style.assert_called_once_with(new_layer, "verfahrensgebiet.qml") self.assertTrue(haken_calls[0]) # ------------------------------------------------------------------ # handle_verf_aus_auswahl – Ergänzen / Entfernen / Ersetzen # ------------------------------------------------------------------ def _make_entscheidungs_ergebnis(self, aktion: str): from sn_basis.modules.pruef_ergebnis import pruef_ergebnis as PruefErg return PruefErg(ok=True, aktion=aktion) def _setup_existierenden_layer(self, tab, mock_iface, mock_pruefe_layer, mock_pruefe_auswahl, mock_project_inst, mock_canvas, entscheidung: str): """Gemeinsames Setup für Delta-Operationen mit bestehendem Layer.""" from sn_basis.modules.pruef_ergebnis import pruef_ergebnis as PruefErg active = _DummyLayer(valid=True) mock_iface.activeLayer.return_value = active mock_iface.mapCanvas.return_value = mock_canvas mock_pruefe_layer.return_value = PruefErg(ok=True, aktion="ok") mock_pruefe_auswahl.return_value = PruefErg(ok=True, aktion="ok", meldung="2 Flurstücke") verf_layer = _DummyLayer(valid=True, layer_id="verf_existing") project = _DummyProject() project.added = [] project.mapLayersByName = lambda _: [verf_layer] project.removeMapLayer = lambda lid: None mock_project_inst.return_value = project # Pruefmanager gibt Entscheidung zurück tab.pruefmanager.verarbeite = lambda _e: self._make_entscheidungs_ergebnis(entscheidung) return active, verf_layer, project @patch("sn_verfahrensgebiet.ui.tabs.working_tab.QMessageBox") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.apply_style") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.ergaenze_verfahrensgebiet") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.pruefe_auswahl") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.pruefe_flurstueck_layer") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface") def test_handle_verfahrensgebiet_from_selection_existing_layer_ergaenzen_dispatch( self, mock_iface, mock_pruefe_layer, mock_pruefe_auswahl, mock_project_inst, mock_ergaenze, mock_apply_style, mock_msgbox ): """Entscheidung 'ergaenzen' → ergaenze_verfahrensgebiet wird aufgerufen.""" tab = self._make_tab() canvas = _DummyCanvas() active, verf_layer, _ = self._setup_existierenden_layer( tab, mock_iface, mock_pruefe_layer, mock_pruefe_auswahl, mock_project_inst, canvas, "verfahrensgebiet_ergaenzen" ) mock_ergaenze.return_value = (verf_layer, 2, 0, 0) tab.handle_verf_aus_auswahl() mock_ergaenze.assert_called_once_with(verf_layer, active) mock_apply_style.assert_called_once_with(verf_layer, "verfahrensgebiet.qml") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.QMessageBox") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.apply_style") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.entferne_aus_verfahrensgebiet") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.pruefe_auswahl") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.pruefe_flurstueck_layer") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface") def test_handle_verfahrensgebiet_from_selection_existing_layer_entfernen_dispatch( self, mock_iface, mock_pruefe_layer, mock_pruefe_auswahl, mock_project_inst, mock_entferne, mock_apply_style, mock_msgbox ): """Entscheidung 'entfernen' → entferne_aus_verfahrensgebiet wird aufgerufen.""" tab = self._make_tab() canvas = _DummyCanvas() active, verf_layer, _ = self._setup_existierenden_layer( tab, mock_iface, mock_pruefe_layer, mock_pruefe_auswahl, mock_project_inst, canvas, "verfahrensgebiet_entfernen" ) mock_entferne.return_value = (verf_layer, 1, 1, 0) tab.handle_verf_aus_auswahl() mock_entferne.assert_called_once_with(verf_layer, active) mock_apply_style.assert_called_once_with(verf_layer, "verfahrensgebiet.qml") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.QMessageBox") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.apply_style") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.ersetze_verfahrensgebiet") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.pruefe_auswahl") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.pruefe_flurstueck_layer") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface") def test_handle_verfahrensgebiet_from_selection_existing_layer_ersetzen_dispatch( self, mock_iface, mock_pruefe_layer, mock_pruefe_auswahl, mock_project_inst, mock_ersetze, mock_apply_style, mock_msgbox ): """Entscheidung 'ersetzen' → alter Layer entfernt, neuer hinzugefügt.""" tab = self._make_tab() canvas = _DummyCanvas() active, verf_layer, project = self._setup_existierenden_layer( tab, mock_iface, mock_pruefe_layer, mock_pruefe_auswahl, mock_project_inst, canvas, "verfahrensgebiet_ersetzen" ) removed = [] project.removeMapLayer = lambda lid: removed.append(lid) new_layer = _DummyLayer(valid=True, layer_id="new_verf_replaced") mock_ersetze.return_value = (new_layer, 3, 0) tab.handle_verf_aus_auswahl() mock_ersetze.assert_called_once_with(active) self.assertIn(verf_layer.id(), removed) self.assertIn(new_layer, project.added) mock_apply_style.assert_called_once_with(new_layer, "verfahrensgebiet.qml") # ------------------------------------------------------------------ # handle_verf_aus_liste # ------------------------------------------------------------------ @patch("sn_verfahrensgebiet.ui.tabs.working_tab.show_info_dialog") def test_aus_liste_button_shows_not_implemented_hint(self, mock_info): tab = self._make_tab() tab.handle_verf_aus_liste() mock_info.assert_called_once() # ------------------------------------------------------------------ # handle_gem_alkis # ------------------------------------------------------------------ @patch("sn_verfahrensgebiet.ui.tabs.working_tab.success") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.apply_style") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.lade_gemarkungen_layer") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance") def test_handle_gem_alkis_service_reachable_adds_layer( self, mock_project_inst, mock_lade, mock_apply_style, mock_success ): """Dienst erreichbar → Layer ins Projekt, Stil angewendet, Haken gesetzt.""" tab = self._make_tab() haken_calls = [] tab.setze_haken = lambda lbl, v: haken_calls.append((lbl, v)) layer = _DummyLayer(valid=True, layer_name="Gemarkungen") mock_lade.return_value = layer project = _DummyProject() project.mapLayersByName = lambda _n: [] mock_project_inst.return_value = project tab.handle_gem_alkis() self.assertIn(layer, project.added) mock_apply_style.assert_called_once_with(layer, "gemarkungen_beschriftet.qml") mock_success.assert_called_once() self.assertEqual(len(haken_calls), 1) self.assertIs(haken_calls[0][0], tab.haken_gem) self.assertTrue(haken_calls[0][1]) self.assertTrue(tab.btn_gem_alkis.isEnabled()) # finally @patch("sn_verfahrensgebiet.ui.tabs.working_tab.warning") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.lade_gemarkungen_layer") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance") def test_handle_gem_alkis_service_unreachable_shows_warning( self, mock_project_inst, mock_lade, mock_warning ): """Dienst nicht erreichbar (None) → warning, kein addMapLayer.""" tab = self._make_tab() haken_calls = [] tab.setze_haken = lambda lbl, v: haken_calls.append((lbl, v)) mock_lade.return_value = None project = _DummyProject() project.mapLayersByName = lambda _n: [] mock_project_inst.return_value = project tab.handle_gem_alkis() mock_warning.assert_called_once() self.assertEqual(len(project.added), 0) self.assertEqual(len(haken_calls), 0) self.assertTrue(tab.btn_gem_alkis.isEnabled()) # finally @patch("sn_verfahrensgebiet.ui.tabs.working_tab.info") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.lade_gemarkungen_layer") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance") def test_handle_gem_alkis_layer_already_exists_shows_info( self, mock_project_inst, mock_lade, mock_info ): """Layer 'Gemarkungen' bereits vorhanden → info, kein erneutes Laden.""" tab = self._make_tab() haken_calls = [] tab.setze_haken = lambda lbl, v: haken_calls.append((lbl, v)) existing_layer = _DummyLayer(valid=True, layer_name="Gemarkungen") project = _DummyProject() project.mapLayersByName = lambda _n: [existing_layer] mock_project_inst.return_value = project tab.handle_gem_alkis() mock_info.assert_called_once() mock_lade.assert_not_called() self.assertEqual(len(project.added), 0) self.assertTrue(tab.btn_gem_alkis.isEnabled()) # finally # --------------------------------------------------------------------------- # handle_verf_shape # --------------------------------------------------------------------------- class TestHandleVerfShape(unittest.TestCase): def _make_tab(self): tab = WorkingTab.__new__(WorkingTab) tab.btn_verf_alkis = _DummyButton() tab.btn_verf_shape = _DummyButton() tab.btn_flurst_alkis = _DummyButton() tab.btn_gem_alkis = _DummyButton() tab.btn_grenz_alkis = _DummyButton() tab.btn_verf_aus_auswahl = _DummyButton() tab.btn_verf_aus_liste = _DummyButton() tab.verf_layer_combo = _DummyCombo() tab.haken_verf = types.SimpleNamespace(setText=lambda _t: None, setStyleSheet=lambda _s: None) tab.haken_flurst = types.SimpleNamespace(setText=lambda _t: None, setStyleSheet=lambda _s: None) tab.haken_gem = types.SimpleNamespace(setText=lambda _t: None, setStyleSheet=lambda _s: None) tab.haken_grenz = types.SimpleNamespace(setText=lambda _t: None, setStyleSheet=lambda _s: None) tab.setze_haken = lambda *_args, **_kwargs: None tab.pruefmanager = types.SimpleNamespace(verarbeite=lambda ergebnis: ergebnis) return tab @patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id") @patch( "sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_shape_komplett", return_value=(None, None, None, LoadStatus.NONE), ) def test_status_none_clears_layer_variable(self, _mock_shape, mock_set_id): """Status NONE → Layer-Variable wird auf '' gesetzt.""" tab = self._make_tab() with patch("sn_verfahrensgebiet.ui.tabs.working_tab.get_variable", return_value="VF-123"), \ patch("sn_verfahrensgebiet.ui.tabs.working_tab.ask_yes_no"): tab.handle_verf_shape() mock_set_id.assert_called_once_with("") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id") @patch( "sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_shape_komplett", return_value=(None, None, None, LoadStatus.NONE), ) def test_button_always_re_enabled_on_failure(self, _mock_shape, _mock_set_id): """Button muss nach Status NONE wieder freigegeben sein (finally).""" tab = self._make_tab() with patch("sn_verfahrensgebiet.ui.tabs.working_tab.get_variable", return_value="VF-123"), \ patch("sn_verfahrensgebiet.ui.tabs.working_tab.ask_yes_no"): tab.handle_verf_shape() self.assertTrue(tab.btn_verf_shape.isEnabled()) self.assertEqual(tab.btn_verf_shape.text(), "Aus Shape laden") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.ask_yes_no", return_value=False) @patch("sn_verfahrensgebiet.ui.tabs.working_tab.get_variable", return_value="") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_shape_komplett") def test_verfahrensnummer_nicht_gesetzt_nein_bricht_ab( self, mock_shape, mock_get_var, mock_ask ): """Verfahrensnummer leer + Nutzer wählt 'Nein' → Vorgang abgebrochen.""" tab = self._make_tab() tab.handle_verf_shape() mock_ask.assert_called_once() mock_shape.assert_not_called() # Button darf nicht busy geblieben sein self.assertTrue(tab.btn_verf_shape.isEnabled()) @patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.ask_yes_no", return_value=True) @patch("sn_verfahrensgebiet.ui.tabs.working_tab.get_variable", return_value="") @patch( "sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_shape_komplett", return_value=(None, None, None, LoadStatus.NONE), ) def test_verfahrensnummer_nicht_gesetzt_ja_fuehrt_fort( self, mock_shape, mock_get_var, mock_ask, mock_set_id ): """Verfahrensnummer leer + Nutzer wählt 'Ja' → Pipeline wird aufgerufen.""" tab = self._make_tab() tab.handle_verf_shape() mock_ask.assert_called_once() mock_shape.assert_called_once_with(tab) @patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.ask_yes_no") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.get_variable", return_value="VF-2026-001") @patch( "sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_shape_komplett", return_value=(None, None, None, LoadStatus.NONE), ) def test_verfahrensnummer_gesetzt_kein_rueckfrage_dialog( self, mock_shape, mock_get_var, mock_ask, mock_set_id ): """Verfahrensnummer gesetzt → kein ask_yes_no-Dialog.""" tab = self._make_tab() tab.handle_verf_shape() mock_ask.assert_not_called() mock_shape.assert_called_once_with(tab) @patch("sn_verfahrensgebiet.ui.tabs.working_tab.get_variable", return_value="VF-123") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.success") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.apply_style") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_shape_komplett") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id") def test_success_path_applies_style_and_sets_haken( self, mock_set_layer_id, mock_iface, mock_shape_komplett, mock_project_instance, mock_apply_style, mock_success, mock_get_var, ): """Erfolg → verfahrensgebiet.qml angewendet, haken_verf gesetzt, success aufgerufen.""" tab = self._make_tab() haken_calls = [] tab.setze_haken = lambda lbl, v: haken_calls.append((lbl, v)) enriched = _DummyLayer(valid=True, layer_id="shape_vg") flst = _DummyLayer(valid=True, layer_id="flst_ok") mock_shape_komplett.return_value = (enriched, flst, None, LoadStatus.FIRST) proj = _DummyProject() proj.mapLayersByName = lambda _n: [] proj.mapLayer = lambda _lid: None mock_project_instance.return_value = proj mock_iface.mapCanvas.return_value = _DummyCanvas() tab.handle_verf_shape() # Style auf enriched layer angewendet apply_calls = [str(c) for c in mock_apply_style.call_args_list] self.assertTrue( any("verfahrensgebiet.qml" in c for c in apply_calls), "verfahrensgebiet.qml muss angewendet werden", ) # Flurstücke-Style angewendet self.assertTrue( any("GIS_Flst_Beschriftung_ALKIS_NAS.qml" in c for c in apply_calls), "Flurstücke-Style muss angewendet werden", ) # Haken gesetzt self.assertTrue(any(v for _, v in haken_calls), "Mindestens ein Haken muss gesetzt werden") # Layer-ID gespeichert mock_set_layer_id.assert_called_with("shape_vg") # Success-Meldung mock_success.assert_called_once() # Button wieder freigegeben self.assertTrue(tab.btn_verf_shape.isEnabled()) @patch("sn_verfahrensgebiet.ui.tabs.working_tab.get_variable", return_value="VF-123") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.success") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.apply_style") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_shape_komplett") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id") def test_bror_layer_erhaelt_qml_stil( self, mock_set_layer_id, mock_iface, mock_shape_komplett, mock_project_instance, mock_apply_style, mock_success, mock_get_var, ): """Wenn bror_layer zurückgegeben → BauRaumOderBodenordnungsrecht.qml angewendet.""" tab = self._make_tab() tab.setze_haken = lambda *a, **kw: None enriched = _DummyLayer(valid=True, layer_id="shape_vg") bror = _DummyLayer(valid=True, layer_id="bror_id") mock_shape_komplett.return_value = (enriched, None, bror, LoadStatus.FIRST) proj = _DummyProject() proj.mapLayersByName = lambda _n: [] proj.mapLayer = lambda lid: enriched if lid == "shape_vg" else None mock_project_instance.return_value = proj mock_iface.mapCanvas.return_value = _DummyCanvas() tab.handle_verf_shape() apply_calls = [str(c) for c in mock_apply_style.call_args_list] self.assertTrue( any("BauRaumOderBodenordnungsrecht.qml" in c for c in apply_calls), "BauRaumOderBodenordnungsrecht.qml muss auf bror_layer angewendet werden", ) @patch("sn_verfahrensgebiet.ui.tabs.working_tab.get_variable", return_value="VF-123") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.apply_style") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_shape_komplett") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id") def test_enriched_layer_already_in_project_not_added_again( self, mock_set_layer_id, mock_iface, mock_shape_komplett, mock_project_instance, mock_apply_style, mock_get_var, ): """Layer bereits im Projekt → kein erneutes addMapLayer für VG-Layer.""" tab = self._make_tab() tab.setze_haken = lambda *a, **kw: None enriched = _DummyLayer(valid=True, layer_id="shape_vg") mock_shape_komplett.return_value = (enriched, None, None, LoadStatus.FIRST) proj = _DummyProject() proj.mapLayersByName = lambda _n: [] proj.mapLayer = lambda lid: enriched if lid == "shape_vg" else None mock_project_instance.return_value = proj mock_iface.mapCanvas.return_value = _DummyCanvas() tab.handle_verf_shape() # Enriched-Layer darf NICHT erneut via addMapLayer hinzugefügt worden sein self.assertNotIn(enriched, proj.added) def test_busy_state_set_during_execution(self): """Button wird während der Ausführung auf busy gesetzt.""" tab = self._make_tab() busy_states = [] original_set_busy = WorkingTab._set_busy def capture_busy(self_ref, button, busy, original_text="Aus Shape laden"): busy_states.append(busy) original_set_busy(self_ref, button, busy, original_text) with patch.object(WorkingTab, "_set_busy", capture_busy), \ patch( "sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_shape_komplett", return_value=(None, None, None, LoadStatus.NONE), ), \ patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id"), \ patch("sn_verfahrensgebiet.ui.tabs.working_tab.get_variable", return_value="VF-123"): tab.handle_verf_shape() self.assertIn(True, busy_states, "Button muss mindestens einmal auf busy gesetzt werden") self.assertIn(False, busy_states, "Button muss am Ende wieder freigegeben werden") class TestZoomImProjektKbs(unittest.TestCase): """Zoom auf das neue Verfahrensgebiet rechnet in das KBS der Kartenansicht um (Basis #99).""" def _tab(self): tab = TestWorkingTab._make_tab(TestWorkingTab()) tab.setze_haken = lambda *_: None tab._on_verf_layer_changed = lambda *_: None return tab def _projekt(self, mock_project_instance): proj = _DummyProject() proj.mapLayersByName = lambda _n: [] proj.mapLayer = lambda _lid: None mock_project_instance.return_value = proj @patch("sn_verfahrensgebiet.ui.tabs.working_tab.zoom_auf_layer") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.success") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.apply_style") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_alkis_komplett") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id") def test_alkis_zoom_ueber_zoom_auf_layer(self, _set_id, mock_iface, mock_alkis, mock_projekt, _stil, _erfolg, mock_zoom): """Prüft, dass nach dem Laden aus ALKIS über zoom_auf_layer auf das Verfahrensgebiet gezoomt wird und nicht mit der unumgerechneten Layer-Ausdehnung.""" tab = self._tab() enriched = _DummyLayer(valid=True, layer_id="vg") mock_alkis.return_value = (enriched, _DummyLayer(layer_id="flst"), None, LoadStatus.FIRST) self._projekt(mock_projekt) canvas = _DummyCanvas() mock_iface.mapCanvas.return_value = canvas tab.handle_verf_alkis() mock_zoom.assert_called_once_with(enriched, canvas) self.assertIsNone(canvas.extent) @patch("sn_verfahrensgebiet.ui.tabs.working_tab.get_variable", return_value="VF-123") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.zoom_auf_layer") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.success") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.apply_style") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_shape_komplett") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id") def test_shape_zoom_ueber_zoom_auf_layer(self, _set_id, mock_iface, mock_shape, mock_projekt, _stil, _erfolg, mock_zoom, _var): """Prüft, dass nach dem Laden aus einem Shape über zoom_auf_layer auf das Verfahrensgebiet gezoomt wird und nicht mit der unumgerechneten Layer-Ausdehnung.""" tab = self._tab() tab.btn_verf_shape = _DummyButton() enriched = _DummyLayer(valid=True, layer_id="shape_vg") mock_shape.return_value = (enriched, _DummyLayer(layer_id="flst"), None, LoadStatus.FIRST) self._projekt(mock_projekt) canvas = _DummyCanvas() mock_iface.mapCanvas.return_value = canvas tab.handle_verf_shape() mock_zoom.assert_called_once_with(enriched, canvas) self.assertIsNone(canvas.extent) if __name__ == "__main__": unittest.main()