302 lines
11 KiB
Python
302 lines
11 KiB
Python
"""sn_verfahrensgebiet/tests/test_grenzpunkt_alkis.py
|
|
|
|
Unit-Tests für :mod:`sn_verfahrensgebiet.functions.grenzpunkt_alkis`.
|
|
|
|
Getestete Szenarien:
|
|
|
|
Erfolgsfall:
|
|
- Vollständiger Workflow: PunktortTA laden → GML-IDs extrahieren →
|
|
Grenzpunkte laden → beide Layer zurückgegeben
|
|
|
|
Fehlerfälle:
|
|
- PunktortTA-Layer ungültig → (None, None)
|
|
- PunktortTA leer (featureCount == 0) → Warnung + (None, None)
|
|
- Keine gehoertZu-Referenzen → (punktort_layer, None) + Warnung
|
|
- Grenzpunkt-Layer nach IDs leer → (punktort_layer, None) + Warnung
|
|
- Kein Verfahrensgebiet-Layer für BBox-Ableitung → (None, None)
|
|
|
|
Hilfsfunktionen:
|
|
- _extrahiere_gml_ids: xlink:href-Format wird korrekt geparst
|
|
- _extrahiere_gml_ids: plain GML-ID ohne Doppelpunkt wird direkt verwendet
|
|
- _extrahiere_gml_ids: leeres Feld wird übersprungen
|
|
"""
|
|
|
|
import sys
|
|
import types
|
|
import unittest
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 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.grenzpunkt_alkis import (
|
|
grenzpunkt_alkis,
|
|
_extrahiere_gml_ids,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Dummies
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class _DummyRect:
|
|
def xMinimum(self): return 414000.0
|
|
def yMinimum(self): return 5657000.0
|
|
def xMaximum(self): return 415000.0
|
|
def yMaximum(self): return 5658000.0
|
|
|
|
|
|
class _DummyFeature:
|
|
def __init__(self, gehoert_zu_val=None):
|
|
self._val = gehoert_zu_val
|
|
|
|
def __getitem__(self, key):
|
|
if key == "gehoertZu":
|
|
return self._val
|
|
raise KeyError(key)
|
|
|
|
|
|
def _make_layer(valid=True, feature_count=3, features=None, name="Layer"):
|
|
layer = MagicMock()
|
|
layer.isValid.return_value = valid
|
|
layer.featureCount.return_value = feature_count if valid else 0
|
|
layer.getFeatures.return_value = features or []
|
|
layer.name.return_value = name
|
|
layer.id.return_value = "LYR1"
|
|
return layer
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests: _extrahiere_gml_ids
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestExtrahiereGmlIds(unittest.TestCase):
|
|
|
|
def test_xlink_href_format(self):
|
|
"""URN-Format 'urn:adv:oid:DEABC123' → nur 'DEABC123' extrahiert."""
|
|
feat = _DummyFeature("urn:adv:oid:DEABC123")
|
|
layer = _make_layer(features=[feat])
|
|
result = _extrahiere_gml_ids(layer)
|
|
self.assertEqual(result, ["DEABC123"])
|
|
|
|
def test_plain_gml_id(self):
|
|
"""Plain-GML-ID ohne Doppelpunkt → direkt übernommen."""
|
|
feat = _DummyFeature("DEPLAIN456")
|
|
layer = _make_layer(features=[feat])
|
|
result = _extrahiere_gml_ids(layer)
|
|
self.assertEqual(result, ["DEPLAIN456"])
|
|
|
|
def test_empty_value_skipped(self):
|
|
"""Leerer gehoertZu-Wert wird übersprungen."""
|
|
feat = _DummyFeature(None)
|
|
layer = _make_layer(features=[feat])
|
|
result = _extrahiere_gml_ids(layer)
|
|
self.assertEqual(result, [])
|
|
|
|
def test_deduplication(self):
|
|
"""Doppelte Referenzen werden dedupliziert."""
|
|
f1 = _DummyFeature("urn:adv:oid:DEDUP001")
|
|
f2 = _DummyFeature("urn:adv:oid:DEDUP001")
|
|
f3 = _DummyFeature("urn:adv:oid:DEOTHER")
|
|
layer = _make_layer(features=[f1, f2, f3])
|
|
result = _extrahiere_gml_ids(layer)
|
|
self.assertEqual(len(result), 2)
|
|
self.assertIn("DEDUP001", result)
|
|
self.assertIn("DEOTHER", result)
|
|
|
|
def test_sorted_output(self):
|
|
"""Rückgabe ist aufsteigend sortiert."""
|
|
f1 = _DummyFeature("urn:adv:oid:ZZZ")
|
|
f2 = _DummyFeature("urn:adv:oid:AAA")
|
|
layer = _make_layer(features=[f1, f2])
|
|
result = _extrahiere_gml_ids(layer)
|
|
self.assertEqual(result, sorted(result))
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests: grenzpunkt_alkis
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_PATCH_PROJECT = "sn_verfahrensgebiet.functions.grenzpunkt_alkis.QgsProject"
|
|
_PATCH_MSGBOX = "sn_verfahrensgebiet.functions.grenzpunkt_alkis.QMessageBox"
|
|
_PATCH_WFS = "sn_verfahrensgebiet.functions.grenzpunkt_alkis.QgsVectorLayer"
|
|
_PATCH_EXTRAHIERE = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._extrahiere_gml_ids"
|
|
_PATCH_LADE_GP = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._lade_grenzpunkte_per_ids"
|
|
_PATCH_INFO = "sn_verfahrensgebiet.functions.grenzpunkt_alkis.info"
|
|
_PATCH_WARNING = "sn_verfahrensgebiet.functions.grenzpunkt_alkis.warning"
|
|
|
|
|
|
class TestGrenzpunktAlkis(unittest.TestCase):
|
|
|
|
def _proj(self, vg_layer=None):
|
|
proj = MagicMock()
|
|
proj.mapLayersByName.return_value = [vg_layer] if vg_layer else []
|
|
return proj
|
|
|
|
# ------------------------------------------------------------------ #
|
|
# 1. Kein VG-Layer wenn extent=None → (None, None) #
|
|
# ------------------------------------------------------------------ #
|
|
@patch(_PATCH_MSGBOX)
|
|
@patch(_PATCH_PROJECT)
|
|
def test_no_vg_layer_returns_none(self, mock_proj, mock_msgbox):
|
|
mock_proj.instance.return_value = self._proj(vg_layer=None)
|
|
|
|
pta, gp = grenzpunkt_alkis(None, extent=None)
|
|
|
|
self.assertIsNone(pta)
|
|
self.assertIsNone(gp)
|
|
mock_msgbox.warning.assert_called_once()
|
|
|
|
# ------------------------------------------------------------------ #
|
|
# 2. PunktortTA-Layer ungültig → (None, None) #
|
|
# ------------------------------------------------------------------ #
|
|
@patch(_PATCH_MSGBOX)
|
|
@patch(_PATCH_WFS)
|
|
@patch(_PATCH_PROJECT)
|
|
def test_invalid_punktort_layer(self, mock_proj, mock_wfs, mock_msgbox):
|
|
proj = MagicMock()
|
|
proj.mapLayersByName.return_value = []
|
|
mock_proj.instance.return_value = proj
|
|
mock_wfs.return_value = _make_layer(valid=False)
|
|
|
|
pta, gp = grenzpunkt_alkis(None, extent=_DummyRect())
|
|
|
|
self.assertIsNone(pta)
|
|
self.assertIsNone(gp)
|
|
mock_msgbox.critical.assert_called_once()
|
|
|
|
# ------------------------------------------------------------------ #
|
|
# 3. PunktortTA leer → Warnung + (None, None) #
|
|
# ------------------------------------------------------------------ #
|
|
@patch(_PATCH_MSGBOX)
|
|
@patch(_PATCH_WFS)
|
|
@patch(_PATCH_PROJECT)
|
|
def test_empty_punktort_layer(self, mock_proj, mock_wfs, mock_msgbox):
|
|
mock_proj.instance.return_value = MagicMock()
|
|
mock_wfs.return_value = _make_layer(valid=True, feature_count=0)
|
|
|
|
pta, gp = grenzpunkt_alkis(None, extent=_DummyRect())
|
|
|
|
self.assertIsNone(pta)
|
|
self.assertIsNone(gp)
|
|
mock_msgbox.warning.assert_called_once()
|
|
|
|
# ------------------------------------------------------------------ #
|
|
# 4. Keine gehoertZu-Referenzen → (punktort_layer, None) + Warnung #
|
|
# ------------------------------------------------------------------ #
|
|
@patch(_PATCH_WARNING)
|
|
@patch(_PATCH_EXTRAHIERE)
|
|
@patch(_PATCH_WFS)
|
|
@patch(_PATCH_PROJECT)
|
|
def test_no_gml_ids_returns_pta_only(
|
|
self, mock_proj, mock_wfs, mock_extrahiere, mock_warning
|
|
):
|
|
proj = MagicMock()
|
|
mock_proj.instance.return_value = proj
|
|
pta_layer = _make_layer(valid=True, feature_count=5)
|
|
mock_wfs.return_value = pta_layer
|
|
mock_extrahiere.return_value = []
|
|
|
|
pta, gp = grenzpunkt_alkis(None, extent=_DummyRect())
|
|
|
|
self.assertIs(pta, pta_layer)
|
|
self.assertIsNone(gp)
|
|
mock_warning.assert_called_once()
|
|
proj.addMapLayer.assert_called_once_with(pta_layer)
|
|
|
|
# ------------------------------------------------------------------ #
|
|
# 5. Grenzpunkte-Batch liefert None → (punktort_layer, None) + Warn #
|
|
# ------------------------------------------------------------------ #
|
|
@patch(_PATCH_WARNING)
|
|
@patch(_PATCH_LADE_GP)
|
|
@patch(_PATCH_EXTRAHIERE)
|
|
@patch(_PATCH_WFS)
|
|
@patch(_PATCH_PROJECT)
|
|
def test_grenzpunkt_batch_empty(
|
|
self, mock_proj, mock_wfs, mock_extrahiere, mock_lade_gp, mock_warning
|
|
):
|
|
proj = MagicMock()
|
|
mock_proj.instance.return_value = proj
|
|
pta_layer = _make_layer(valid=True, feature_count=3)
|
|
mock_wfs.return_value = pta_layer
|
|
mock_extrahiere.return_value = ["DE0001", "DE0002"]
|
|
mock_lade_gp.return_value = None # Batch leer
|
|
|
|
pta, gp = grenzpunkt_alkis(None, extent=_DummyRect())
|
|
|
|
self.assertIs(pta, pta_layer)
|
|
self.assertIsNone(gp)
|
|
mock_warning.assert_called_once()
|
|
|
|
# ------------------------------------------------------------------ #
|
|
# 6. Vollständiger Erfolgsfall #
|
|
# ------------------------------------------------------------------ #
|
|
@patch(_PATCH_LADE_GP)
|
|
@patch(_PATCH_EXTRAHIERE)
|
|
@patch(_PATCH_WFS)
|
|
@patch(_PATCH_PROJECT)
|
|
def test_success_returns_both_layers(
|
|
self, mock_proj, mock_wfs, mock_extrahiere, mock_lade_gp
|
|
):
|
|
proj = MagicMock()
|
|
mock_proj.instance.return_value = proj
|
|
pta_layer = _make_layer(valid=True, feature_count=10, name="PunktortTA")
|
|
gp_layer = _make_layer(valid=True, feature_count=10, name="Grenzpunkte")
|
|
mock_wfs.return_value = pta_layer
|
|
mock_extrahiere.return_value = ["DE001", "DE002", "DE003"]
|
|
mock_lade_gp.return_value = gp_layer
|
|
|
|
pta, gp = grenzpunkt_alkis(None, extent=_DummyRect())
|
|
|
|
self.assertIs(pta, pta_layer)
|
|
self.assertIs(gp, gp_layer)
|
|
# Beide Layer müssen ins Projekt eingefügt worden sein
|
|
calls = [c[0][0] for c in proj.addMapLayer.call_args_list]
|
|
self.assertIn(pta_layer, calls)
|
|
self.assertIn(gp_layer, calls)
|
|
|
|
# ------------------------------------------------------------------ #
|
|
# 7. Extent aus vorhandenem VG-Layer ableiten #
|
|
# ------------------------------------------------------------------ #
|
|
@patch(_PATCH_LADE_GP)
|
|
@patch(_PATCH_EXTRAHIERE)
|
|
@patch(_PATCH_WFS)
|
|
@patch(_PATCH_PROJECT)
|
|
def test_extent_derived_from_vg_layer(
|
|
self, mock_proj, mock_wfs, mock_extrahiere, mock_lade_gp
|
|
):
|
|
"""Wenn kein extent übergeben → Extent aus VG-Layer holen."""
|
|
vg_layer = MagicMock()
|
|
vg_layer.extent.return_value = _DummyRect()
|
|
proj = MagicMock()
|
|
proj.mapLayersByName.return_value = [vg_layer]
|
|
mock_proj.instance.return_value = proj
|
|
|
|
pta_layer = _make_layer(valid=True, feature_count=2)
|
|
gp_layer = _make_layer(valid=True, feature_count=2)
|
|
mock_wfs.return_value = pta_layer
|
|
mock_extrahiere.return_value = ["DE999"]
|
|
mock_lade_gp.return_value = gp_layer
|
|
|
|
pta, gp = grenzpunkt_alkis(None, extent=None)
|
|
|
|
vg_layer.extent.assert_called_once()
|
|
self.assertIs(pta, pta_layer)
|
|
self.assertIs(gp, gp_layer)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|