Files
Plugin_SN_Verfahrensgebiet/tests/test_verfahrensgebiet_shape_komplett.py
T

933 lines
37 KiB
Python

"""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()