Files
Plugin_SN_Verfahrensgebiet/tests/test_working_tab.py
T
DanielandClaude Sonnet 5 003ad1957b Zweite Anhaengen-Pipeline (Gemarkungen-Button) auf Dedup umgestellt (#28)
handle_flurst_aus_gemarkungen in working_tab.py hatte eine eigene,
von flurstueck_alkis() unabhaengige Anhaengen-Logik, die
lade_flurstuecke_per_gemarkung direkt aufrief und ungefiltert
anhaengte. Nutzt jetzt dieselbe flurstueckskennzeichen-Pruefung wie
der Fix im vorigen Commit; dafuer vorhandene_kennzeichen() in
flurstueck_alkis.py oeffentlich gemacht.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 09:17:30 +02:00

965 lines
39 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
from sn_verfahrensgebiet.functions.flurstueck_alkis import FLST_LAYER_NAME, FELD_FLURSTUECKSKENNZEICHEN
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 _DummyFlstFeatureWT:
def __init__(self, kennzeichen):
self._kennzeichen = kennzeichen
def __getitem__(self, key):
if key == FELD_FLURSTUECKSKENNZEICHEN:
return self._kennzeichen
return None
class _DummyFlstFieldsWT:
def indexFromName(self, name):
return 0 if name == FELD_FLURSTUECKSKENNZEICHEN else -1
class _DummyTempLayer:
"""Rückgabe von lade_flurstuecke_per_gemarkung in Tests."""
def __init__(self, features):
self._features = list(features)
def getFeatures(self):
return list(self._features)
def fields(self):
return _DummyFlstFieldsWT()
def isValid(self):
return True
def featureCount(self):
return len(self._features)
class _DummyExistingFlstLayer:
"""Bereits im Projekt vorhandener Flurstück-Layer, zeichnet addFeatures() auf."""
def __init__(self, features):
self._features = list(features)
self.added = None
def getFeatures(self):
return list(self._features)
def fields(self):
return _DummyFlstFieldsWT()
def dataProvider(self):
layer = self
class _Provider:
def addFeatures(self, feats):
layer.added = list(feats)
layer._features.extend(feats)
return True
return _Provider()
def updateExtents(self):
pass
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_aus_gemarkungen
# ------------------------------------------------------------------
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.success")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.lade_flurstuecke_per_gemarkung")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.QMessageBox")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface")
def test_handle_flurst_aus_gemarkungen_anhaengen_ueberspringt_vorhandene(
self, mock_iface, mock_project_instance, mock_msgbox_cls, mock_lade, mock_success
):
"""Anhängen über den Gemarkungen-Button überspringt bereits vorhandene flurstueckskennzeichen (#28)."""
tab = self._make_tab()
mock_iface.mapCanvas.return_value = _DummyCanvas()
gem_feat = _DummyGemFeature("110001", "Musterstadt")
gem_layer = _DummyGemarkungenLayer([gem_feat])
bestehend = _DummyExistingFlstLayer([_DummyFlstFeatureWT("110001_0001__00001")])
def map_layers_by_name(name):
if name == "Gemarkungen":
return [gem_layer]
if name == FLST_LAYER_NAME:
return [bestehend]
return []
project = types.SimpleNamespace(mapLayersByName=map_layers_by_name)
mock_project_instance.return_value = project
btn_anhaengen = object()
msg_instance = mock_msgbox_cls.return_value
msg_instance.addButton.side_effect = (
lambda text, role: btn_anhaengen if text == "Anhängen" else object()
)
msg_instance.clickedButton.return_value = btn_anhaengen
neu_geladen = _DummyTempLayer([
_DummyFlstFeatureWT("110001_0001__00001"), # bereits vorhanden
_DummyFlstFeatureWT("110001_0002__00007"), # neu
])
mock_lade.return_value = neu_geladen
tab.handle_flurst_aus_gemarkungen()
self.assertEqual(len(bestehend.added), 1)
self.assertEqual(bestehend.added[0][FELD_FLURSTUECKSKENNZEICHEN], "110001_0002__00007")
mock_success.assert_called_once()
# ------------------------------------------------------------------
# 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")
if __name__ == "__main__":
unittest.main()