2026-04-22 14:53:45 +02:00
"""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 ,
2026-09-17 16:29:19 +02:00
grenzpunkte_gemarkungsweise ,
2026-04-22 14:53:45 +02:00
_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 )
2026-09-17 16:29:19 +02:00
# ---------------------------------------------------------------------------
# Tests: grenzpunkte_gemarkungsweise – Stilanbringung (#53)
# ---------------------------------------------------------------------------
_G_PATCH_EXTENTS = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._lade_gemarkung_extents"
_G_PATCH_HITS = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._wfs_hits_count"
_G_PATCH_GET_GML = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._wfs_get_gml"
_G_PATCH_PARSE_GML = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._parse_alkis_gml_memory_layer"
_G_PATCH_ERKENNE_FELD = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._erkenne_gehoert_zu_feld"
_G_PATCH_EXTRAHIERE_ID = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._extrahiere_feature_gml_id"
_G_PATCH_LADE_GP_DICT = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._lade_grenzpunkte_als_dict"
_G_PATCH_ERSTELLE_JOINED = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._erstelle_joined_layer"
_G_PATCH_WFS_LAYER = "sn_verfahrensgebiet.functions.grenzpunkt_alkis.QgsVectorLayer"
_G_PATCH_PROJECT = "sn_verfahrensgebiet.functions.grenzpunkt_alkis.QgsProject"
_G_PATCH_PROGRESS = "sn_verfahrensgebiet.functions.grenzpunkt_alkis.create_progress_dialog"
_G_PATCH_APPLY_STIL = "sn_verfahrensgebiet.functions.grenzpunkt_alkis.apply_style"
# QgsField/QgsFeature werden innerhalb der Funktion per lokalem Import geholt
# (from sn_basis.functions import QgsField/QgsFeature) - Patch-Ziel ist daher
# die Quelle, nicht der Aufrufort.
_G_PATCH_QGSFIELD = "sn_basis.functions.QgsField"
_G_PATCH_QGSFEATURE = "sn_basis.functions.QgsFeature"
class _DummyProgress :
def set_value ( self , wert ): pass
def set_label ( self , text ): pass
def set_total ( self , wert ): pass
def is_canceled ( self ): return False
def close ( self ): pass
class _DummyBbox :
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 _DummyGmlFeld :
def __init__ ( self , name ):
self . _name = name
def name ( self ):
return self . _name
class _DummyGmlFeature :
def __init__ ( self , gehoert_zu_wert ):
self . _wert = gehoert_zu_wert
def geometry ( self ):
return object ()
def __getitem__ ( self , key ):
if key == "gehoertZu" :
return self . _wert
raise KeyError ( key )
class _DummyGmlLayer :
"""Minimaler Ersatz für das Ergebnis von _parse_alkis_gml_memory_layer()."""
def __init__ ( self , feature ):
self . _feature = feature
def fields ( self ):
return [ _DummyGmlFeld ( "gehoertZu" )]
def getFeatures ( self ):
return [ self . _feature ]
class TestGrenzpunkteGemarkungsweiseStil ( unittest . TestCase ):
@patch ( _G_PATCH_APPLY_STIL )
@patch ( _G_PATCH_QGSFEATURE )
@patch ( _G_PATCH_QGSFIELD )
@patch ( _G_PATCH_PROGRESS )
@patch ( _G_PATCH_PROJECT )
@patch ( _G_PATCH_WFS_LAYER )
@patch ( _G_PATCH_ERSTELLE_JOINED )
@patch ( _G_PATCH_LADE_GP_DICT )
@patch ( _G_PATCH_EXTRAHIERE_ID )
@patch ( _G_PATCH_ERKENNE_FELD )
@patch ( _G_PATCH_PARSE_GML )
@patch ( _G_PATCH_GET_GML )
@patch ( _G_PATCH_HITS )
@patch ( _G_PATCH_EXTENTS )
def test_stil_wird_nach_dem_laden_auf_den_ergebnis_layer_angewendet (
self ,
mock_extents ,
mock_hits ,
mock_get_gml ,
mock_parse_gml ,
mock_erkenne_feld ,
mock_extrahiere_id ,
mock_lade_gp_dict ,
mock_erstelle_joined ,
mock_wfs_layer_cls ,
mock_project_cls ,
mock_progress ,
mock_qgsfield ,
mock_qgsfeature ,
mock_apply_style ,
):
"""Prüft, dass grenzpunkte_gemarkungsweise() nach dem Laden apply_style() mit dem neuen ALKIS-Grenzpunkt-Stil auf den fertigen Join-Layer anwendet (#53)."""
mock_extents . return_value = { "1234" : _DummyBbox ()}
mock_hits . return_value = 1
mock_get_gml . return_value = b "<fake/>"
mock_parse_gml . return_value = _DummyGmlLayer ( _DummyGmlFeature ( "urn:adv:oid:GP1" ))
mock_erkenne_feld . return_value = "gehoertZu"
mock_extrahiere_id . return_value = "PTA1"
mock_lade_gp_dict . return_value = ({ "GP1" : object ()}, [ "gp_feld" ])
joined_layer = MagicMock ()
mock_erstelle_joined . return_value = joined_layer
mock_wfs_layer_cls . return_value = MagicMock ()
project = MagicMock ()
mock_project_cls . instance . return_value = project
mock_progress . return_value = _DummyProgress ()
ergebnis = grenzpunkte_gemarkungsweise ( None , [ "1234" ])
self . assertIs ( ergebnis , joined_layer )
project . addMapLayer . assert_called_once_with ( joined_layer )
mock_apply_style . assert_called_once_with ( joined_layer , "alkis_grenzpunkte.qml" )
2026-04-22 14:53:45 +02:00
if __name__ == "__main__" :
unittest . main ()