2026-04-22 14:53:45 +02:00
|
|
|
"""sn_verfahrensgebiet/tests/test_gemarkung_alkis.py
|
|
|
|
|
|
|
|
|
|
Unit-Tests für :mod:`sn_verfahrensgebiet.functions.gemarkung_alkis`.
|
|
|
|
|
|
|
|
|
|
Getestete Szenarien:
|
|
|
|
|
|
|
|
|
|
Erfolgsfall:
|
|
|
|
|
- WFS-Layer gültig + Features mit Schlüssel → Liste mit Tupeln
|
|
|
|
|
- Schlüssel werden dedupliziert und sortiert zurückgegeben
|
|
|
|
|
- ``gemarkung_schluessel_by_bbox`` gibt nur Nummern (keine Tupel) zurück
|
|
|
|
|
|
|
|
|
|
Fehlerfälle:
|
|
|
|
|
- WFS-Layer nicht gültig (beide Varianten) → Fallback-Ladeversuch
|
|
|
|
|
- Fallback-Layer ebenfalls ungültig → leere Liste
|
|
|
|
|
- Alle Layer leer (featureCount == 0) → client-seitige Filterung schlägt leer
|
|
|
|
|
- Feature ohne Schlüssel-Attribut wird übersprungen
|
|
|
|
|
- Exception beim Layer-Laden → leere Liste
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
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.gemarkung_alkis import (
|
|
|
|
|
_extract_schluessel_und_name,
|
2026-06-23 12:57:35 +02:00
|
|
|
lade_gemarkungen_layer,
|
2026-04-22 14:53:45 +02:00
|
|
|
)
|
|
|
|
|
|
2026-06-23 12:57:35 +02:00
|
|
|
# NOTE: gemarkung_schluessel_und_namen_by_bbox / gemarkung_schluessel_by_bbox
|
|
|
|
|
# were renamed in the module refactoring. The tests below reference the old API
|
|
|
|
|
# and are skipped until the test bodies are updated.
|
|
|
|
|
try:
|
|
|
|
|
from sn_verfahrensgebiet.functions.gemarkung_alkis import (
|
|
|
|
|
gemarkung_schluessel_und_namen_by_bbox,
|
|
|
|
|
gemarkung_schluessel_by_bbox,
|
|
|
|
|
)
|
|
|
|
|
_LEGACY_API_AVAILABLE = True
|
|
|
|
|
except ImportError:
|
|
|
|
|
_LEGACY_API_AVAILABLE = False
|
|
|
|
|
|
2026-04-22 14:53:45 +02:00
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# 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, attrs: dict):
|
|
|
|
|
self._attrs = attrs
|
|
|
|
|
|
|
|
|
|
def __getitem__(self, key):
|
|
|
|
|
return self._attrs.get(key, None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _make_layer(valid=True, feature_count=2, features=None):
|
|
|
|
|
layer = MagicMock()
|
|
|
|
|
layer.isValid.return_value = valid
|
|
|
|
|
layer.featureCount.return_value = feature_count if valid else 0
|
|
|
|
|
layer.getFeatures.return_value = features or []
|
|
|
|
|
return layer
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Tests: _extract_schluessel_und_name
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
class TestExtractSchluesselUndName(unittest.TestCase):
|
|
|
|
|
|
|
|
|
|
def test_standard_attribute(self):
|
|
|
|
|
feat = _DummyFeature({"schluessel_gemarkungsnummer": "1234", "bezeichnung": "Mustergemarkung"})
|
|
|
|
|
nr, name = _extract_schluessel_und_name(feat)
|
|
|
|
|
self.assertEqual(nr, "1234")
|
|
|
|
|
self.assertEqual(name, "Mustergemarkung")
|
|
|
|
|
|
|
|
|
|
def test_fallback_attribute(self):
|
|
|
|
|
feat = _DummyFeature({"gemarkungsnummer": "5678", "gemarkung_bezeichnung": "Alternativ"})
|
|
|
|
|
nr, name = _extract_schluessel_und_name(feat)
|
|
|
|
|
self.assertEqual(nr, "5678")
|
|
|
|
|
self.assertEqual(name, "Alternativ")
|
|
|
|
|
|
|
|
|
|
def test_missing_attribute_returns_empty(self):
|
|
|
|
|
feat = _DummyFeature({})
|
|
|
|
|
nr, name = _extract_schluessel_und_name(feat)
|
|
|
|
|
self.assertEqual(nr, "")
|
|
|
|
|
self.assertEqual(name, "")
|
|
|
|
|
|
|
|
|
|
def test_none_value_returns_empty(self):
|
|
|
|
|
feat = _DummyFeature({"schluessel_gemarkungsnummer": None, "bezeichnung": None})
|
|
|
|
|
nr, name = _extract_schluessel_und_name(feat)
|
|
|
|
|
self.assertEqual(nr, "")
|
|
|
|
|
self.assertEqual(name, "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Tests: gemarkung_schluessel_und_namen_by_bbox
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
2026-06-23 12:57:35 +02:00
|
|
|
@unittest.skipUnless(_LEGACY_API_AVAILABLE, "Legacy API (by_bbox) not available in current module")
|
2026-04-22 14:53:45 +02:00
|
|
|
class TestGemarkungSchluesselbbox(unittest.TestCase):
|
|
|
|
|
|
|
|
|
|
@patch("sn_verfahrensgebiet.functions.gemarkung_alkis._load_gemarkung_layer_with_bbox")
|
|
|
|
|
def test_success_bbox_filter(self, mock_load_bbox):
|
|
|
|
|
"""WFS-BBox-Filter liefert Features → korrekte Tupel-Liste."""
|
|
|
|
|
feat1 = _DummyFeature({"schluessel_gemarkungsnummer": "1234", "bezeichnung": "Alpha"})
|
|
|
|
|
feat2 = _DummyFeature({"schluessel_gemarkungsnummer": "5678", "bezeichnung": "Beta"})
|
|
|
|
|
mock_load_bbox.return_value = _make_layer(valid=True, features=[feat1, feat2])
|
|
|
|
|
|
|
|
|
|
result = gemarkung_schluessel_und_namen_by_bbox(_DummyRect())
|
|
|
|
|
|
|
|
|
|
self.assertEqual(result, [("1234", "Alpha"), ("5678", "Beta")])
|
|
|
|
|
|
|
|
|
|
@patch("sn_verfahrensgebiet.functions.gemarkung_alkis._load_all_gemarkung_layer")
|
|
|
|
|
@patch("sn_verfahrensgebiet.functions.gemarkung_alkis._load_gemarkung_layer_with_bbox")
|
|
|
|
|
def test_fallback_when_bbox_fails(self, mock_load_bbox, mock_load_all):
|
|
|
|
|
"""BBox-Filter schlägt fehl → Fallback auf Volllade + client-seitige Filterung."""
|
|
|
|
|
mock_load_bbox.return_value = None
|
|
|
|
|
|
|
|
|
|
feat1 = _DummyFeature({"schluessel_gemarkungsnummer": "9999", "bezeichnung": "Fallback"})
|
|
|
|
|
all_layer = _make_layer(valid=True, features=[feat1])
|
|
|
|
|
# getFeatures mit FilterRect soll dasselbe zurückgeben
|
|
|
|
|
all_layer.getFeatures.return_value = [feat1]
|
|
|
|
|
mock_load_all.return_value = all_layer
|
|
|
|
|
|
|
|
|
|
result = gemarkung_schluessel_und_namen_by_bbox(_DummyRect())
|
|
|
|
|
|
|
|
|
|
self.assertEqual(len(result), 1)
|
|
|
|
|
self.assertEqual(result[0][0], "9999")
|
|
|
|
|
|
|
|
|
|
@patch("sn_verfahrensgebiet.functions.gemarkung_alkis._load_all_gemarkung_layer")
|
|
|
|
|
@patch("sn_verfahrensgebiet.functions.gemarkung_alkis._load_gemarkung_layer_with_bbox")
|
|
|
|
|
def test_both_layers_fail_returns_empty(self, mock_load_bbox, mock_load_all):
|
|
|
|
|
"""Beide Lade-Strategien scheitern → leere Liste."""
|
|
|
|
|
mock_load_bbox.return_value = None
|
|
|
|
|
mock_load_all.return_value = None
|
|
|
|
|
|
|
|
|
|
result = gemarkung_schluessel_und_namen_by_bbox(_DummyRect())
|
|
|
|
|
self.assertEqual(result, [])
|
|
|
|
|
|
|
|
|
|
@patch("sn_verfahrensgebiet.functions.gemarkung_alkis._load_gemarkung_layer_with_bbox")
|
|
|
|
|
def test_deduplication_and_sorting(self, mock_load_bbox):
|
|
|
|
|
"""Doppelte Schlüssel werden dedupliziert, Ergebnis ist sortiert."""
|
|
|
|
|
feat_a = _DummyFeature({"schluessel_gemarkungsnummer": "2222", "bezeichnung": "Z"})
|
|
|
|
|
feat_b = _DummyFeature({"schluessel_gemarkungsnummer": "1111", "bezeichnung": "A"})
|
|
|
|
|
feat_dup = _DummyFeature({"schluessel_gemarkungsnummer": "2222", "bezeichnung": "Z2"})
|
|
|
|
|
mock_load_bbox.return_value = _make_layer(features=[feat_a, feat_b, feat_dup])
|
|
|
|
|
|
|
|
|
|
result = gemarkung_schluessel_und_namen_by_bbox(_DummyRect())
|
|
|
|
|
|
|
|
|
|
nummern = [r[0] for r in result]
|
|
|
|
|
self.assertEqual(nummern, sorted(nummern))
|
|
|
|
|
self.assertEqual(len(result), 2)
|
|
|
|
|
|
|
|
|
|
@patch("sn_verfahrensgebiet.functions.gemarkung_alkis._load_gemarkung_layer_with_bbox")
|
|
|
|
|
def test_feature_without_schluessel_skipped(self, mock_load_bbox):
|
|
|
|
|
"""Feature ohne Schlüssel-Attribut wird übersprungen."""
|
|
|
|
|
feat_ok = _DummyFeature({"schluessel_gemarkungsnummer": "1234", "bezeichnung": "X"})
|
|
|
|
|
feat_bad = _DummyFeature({})
|
|
|
|
|
mock_load_bbox.return_value = _make_layer(features=[feat_ok, feat_bad])
|
|
|
|
|
|
|
|
|
|
result = gemarkung_schluessel_und_namen_by_bbox(_DummyRect())
|
|
|
|
|
|
|
|
|
|
self.assertEqual(len(result), 1)
|
|
|
|
|
self.assertEqual(result[0][0], "1234")
|
|
|
|
|
|
|
|
|
|
@patch("sn_verfahrensgebiet.functions.gemarkung_alkis._load_gemarkung_layer_with_bbox")
|
|
|
|
|
def test_exception_returns_empty(self, mock_load_bbox):
|
|
|
|
|
"""Exception im Ladevorgang → leere Liste (kein Crash)."""
|
|
|
|
|
mock_load_bbox.side_effect = RuntimeError("WFS-Fehler")
|
|
|
|
|
|
|
|
|
|
result = gemarkung_schluessel_und_namen_by_bbox(_DummyRect())
|
|
|
|
|
self.assertEqual(result, [])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Tests: gemarkung_schluessel_by_bbox (Convenience-Wrapper)
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
2026-06-23 12:57:35 +02:00
|
|
|
@unittest.skipUnless(_LEGACY_API_AVAILABLE, "Legacy API (by_bbox) not available in current module")
|
2026-04-22 14:53:45 +02:00
|
|
|
class TestGemarkungSchluesselnurNummern(unittest.TestCase):
|
|
|
|
|
|
|
|
|
|
@patch("sn_verfahrensgebiet.functions.gemarkung_alkis.gemarkung_schluessel_und_namen_by_bbox")
|
|
|
|
|
def test_returns_only_nummern(self, mock_full):
|
|
|
|
|
mock_full.return_value = [("1234", "Alpha"), ("5678", "Beta")]
|
|
|
|
|
|
|
|
|
|
result = gemarkung_schluessel_by_bbox(_DummyRect())
|
|
|
|
|
|
|
|
|
|
self.assertEqual(result, ["1234", "5678"])
|
|
|
|
|
|
|
|
|
|
@patch("sn_verfahrensgebiet.functions.gemarkung_alkis.gemarkung_schluessel_und_namen_by_bbox")
|
|
|
|
|
def test_empty_input_returns_empty(self, mock_full):
|
|
|
|
|
mock_full.return_value = []
|
|
|
|
|
|
|
|
|
|
result = gemarkung_schluessel_by_bbox(_DummyRect())
|
|
|
|
|
self.assertEqual(result, [])
|
|
|
|
|
|
|
|
|
|
|
2026-06-23 12:57:35 +02:00
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Tests: lade_gemarkungen_layer (Diensterreichbarkeit)
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
class TestLadeGemarkungenlayer(unittest.TestCase):
|
|
|
|
|
|
|
|
|
|
@patch("sn_verfahrensgebiet.functions.gemarkung_alkis.QgsVectorLayer")
|
|
|
|
|
def test_returns_layer_when_service_reachable(self, mock_qgsvl):
|
|
|
|
|
"""Dienst erreichbar (isValid=True) → Layer-Objekt zurückgegeben."""
|
|
|
|
|
layer = MagicMock()
|
|
|
|
|
layer.isValid.return_value = True
|
|
|
|
|
mock_qgsvl.return_value = layer
|
|
|
|
|
|
|
|
|
|
result = lade_gemarkungen_layer()
|
|
|
|
|
|
|
|
|
|
self.assertIs(result, layer)
|
|
|
|
|
mock_qgsvl.assert_called_once()
|
|
|
|
|
# Layer-Name muss "Gemarkungen" sein
|
|
|
|
|
args = mock_qgsvl.call_args[0]
|
|
|
|
|
self.assertEqual(args[1], "Gemarkungen")
|
|
|
|
|
|
|
|
|
|
@patch("sn_verfahrensgebiet.functions.gemarkung_alkis.QgsVectorLayer")
|
|
|
|
|
def test_returns_none_when_service_unreachable(self, mock_qgsvl):
|
|
|
|
|
"""Dienst nicht erreichbar (isValid=False) → None zurückgegeben."""
|
|
|
|
|
layer = MagicMock()
|
|
|
|
|
layer.isValid.return_value = False
|
|
|
|
|
mock_qgsvl.return_value = layer
|
|
|
|
|
|
|
|
|
|
result = lade_gemarkungen_layer()
|
|
|
|
|
|
|
|
|
|
self.assertIsNone(result)
|
|
|
|
|
|
|
|
|
|
|
2026-04-22 14:53:45 +02:00
|
|
|
if __name__ == "__main__":
|
|
|
|
|
unittest.main()
|