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"): self._valid = valid self._id = layer_id def isValid(self): return self._valid def id(self): return self._id 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 TestWorkingTab(unittest.TestCase): def _make_tab(self): tab = WorkingTab.__new__(WorkingTab) tab.btn_verf_alkis = _DummyButton() tab.btn_flurst_alkis = _DummyButton() tab.btn_verf_aus_auswahl = _DummyButton() tab.btn_verf_aus_liste = _DummyButton() tab.verf_layer_combo = _DummyCombo() tab.haken_verf = object() tab.haken_flurst = object() 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") @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, mock_project_instance, mock_apply_style, mock_success, ): tab = self._make_tab() layer = _DummyLayer(valid=True, layer_id="layer_ok") mock_alkis.return_value = (layer, LoadStatus.FIRST) mock_project_instance.return_value = _DummyProject() mock_iface.mapCanvas.return_value = _DummyCanvas() tab.handle_verf_alkis() mock_apply_style.assert_called_once() mock_success.assert_called_once() mock_set_layer_id.assert_called_with("layer_ok") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id") @patch("sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_alkis", return_value=(None, LoadStatus.NONE)) def test_handle_verf_alkis_failure_clears_layer_var(self, _mock_alkis, mock_set_layer_id): tab = self._make_tab() tab.handle_verf_alkis() mock_set_layer_id.assert_called_once_with("") # ------------------------------------------------------------------ # 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() if __name__ == "__main__": unittest.main()