"""sn_verfahrensgebiet/tests/test_knickpunkt_pruefung.py Unit-Tests für :mod:`sn_verfahrensgebiet.functions.knickpunkt_pruefung`. Getestete Szenarien: _extrahiere_knickpunkte: - Leerer Layer → leere Liste - Polygon mit 4 Knickpunkten → korrekte Anzahl (ohne doppelten Schlusspunkt) - Keine Geometrie → leere Liste knickpunkt_pruefung: - Leere Knickpunktliste → (None, 0, 0, 0) - Kein PunktortTA-Layer → (None, 0, 0, 0) - Identischer Punkt → grün gezählt - Kein passender PunktortTA → rot gezählt - PunktortTA nahe Umring auf Flurstücksgrenze → orange gezählt - Abbruch via progress.is_canceled() → (None, 0, 0, 0) """ import sys import types import unittest from unittest.mock import MagicMock, patch # --------------------------------------------------------------------------- # Minimales QGIS-Mock-Setup (identisch mit test_verfahrensgebiet_alkis_komplett) # --------------------------------------------------------------------------- if "qgis" not in sys.modules: qgis_mod = types.ModuleType("qgis") qgis_core = types.ModuleType("qgis.core") # Attribute vorab setzen, damit patch() sie überschreiben kann qgis_core.QgsPointXY = None qgis_core.QgsRectangle = None qgis_core.QgsSpatialIndex = None qgis_core.QgsMarkerSymbol = None qgis_core.QgsCategorizedSymbolRenderer = None qgis_core.QgsRendererCategory = None 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.knickpunkt_pruefung import ( knickpunkt_pruefung, _extrahiere_knickpunkte, WERT_IDENTISCH, WERT_KEIN_PTA, WERT_FEHLENDER_KNICK, ) _MOD = "sn_verfahrensgebiet.functions.knickpunkt_pruefung" # --------------------------------------------------------------------------- # Hilfsfunktionen # --------------------------------------------------------------------------- def _make_layer(valid=True, crs="EPSG:25833", lid="L1"): layer = MagicMock() layer.isValid.return_value = valid layer.id.return_value = lid mock_crs = MagicMock() mock_crs.authid.return_value = crs layer.crs.return_value = mock_crs layer.getFeatures.return_value = [] return layer def _make_progress(canceled_after: int = 9999): """Progress-Mock: gibt is_canceled() nach N Aufrufen True zurück.""" call_count = [0] def _is_canceled(): call_count[0] += 1 return call_count[0] > canceled_after prog = MagicMock() prog.is_canceled.side_effect = _is_canceled return prog # --------------------------------------------------------------------------- # Tests: _extrahiere_knickpunkte # --------------------------------------------------------------------------- class TestExtrahiereKnickpunkte(unittest.TestCase): def test_leerer_layer_gibt_leere_liste(self): layer = _make_layer() layer.getFeatures.return_value = [] result = _extrahiere_knickpunkte(layer) self.assertEqual(result, []) def test_keine_gueltige_geometrie_gibt_leere_liste(self): feat = MagicMock() feat.geometry.return_value = None layer = _make_layer() layer.getFeatures.return_value = [feat] result = _extrahiere_knickpunkte(layer) self.assertEqual(result, []) def test_feature_mit_leerer_geometrie_gibt_leere_liste(self): feat = MagicMock() geom = MagicMock() geom.isEmpty.return_value = True feat.geometry.return_value = geom layer = _make_layer() layer.getFeatures.return_value = [feat] result = _extrahiere_knickpunkte(layer) self.assertEqual(result, []) def test_qgis_nicht_verfuegbar_gibt_leere_liste(self): """Wenn qgis.core kein QgsPointXY hat (ImportError in Funktion), leere Liste.""" layer = _make_layer() feat = MagicMock() geom = MagicMock() geom.isEmpty.return_value = False feat.geometry.return_value = geom layer.getFeatures.return_value = [feat] # QgsGeometry.unaryUnion wirft eine Exception → leere Liste erwartet with patch(f"{_MOD}.QgsGeometry") as mock_geom_cls: mock_geom_cls.unaryUnion.side_effect = ImportError("kein QGIS") result = _extrahiere_knickpunkte(layer) self.assertEqual(result, []) # --------------------------------------------------------------------------- # Tests: knickpunkt_pruefung – Eingabevalidierung # --------------------------------------------------------------------------- class TestKnickpunktPruefungInputvalidierung(unittest.TestCase): def test_keine_knickpunkte_gibt_none_zurueck(self): bror = _make_layer() bror.getFeatures.return_value = [] result = knickpunkt_pruefung(bror, MagicMock(), None) layer, identisch, kein_pta, fehlend = result self.assertIsNone(layer) self.assertEqual(identisch, 0) self.assertEqual(kein_pta, 0) self.assertEqual(fehlend, 0) def test_kein_punktort_layer_gibt_none_zurueck(self): bror = _make_layer() result = knickpunkt_pruefung(bror, None, None, knickpunkte=[MagicMock()]) layer, identisch, kein_pta, fehlend = result self.assertIsNone(layer) def test_abbruch_via_progress_gibt_none_zurueck(self): bror = _make_layer() pta = _make_layer() prog = _make_progress(canceled_after=0) # sofort abbrechen result = knickpunkt_pruefung( bror, pta, None, progress=prog, knickpunkte=[MagicMock()], ) self.assertIsNone(result[0]) # --------------------------------------------------------------------------- # Tests: knickpunkt_pruefung – Klassifizierungslogik (mit QGIS-Mocks) # --------------------------------------------------------------------------- class TestKnickpunktPruefungKlassifizierung(unittest.TestCase): """Testet die Grün/Rot/Orange-Klassifizierung mit gesteuertem QGIS-Mock.""" def _make_pta_layer_mit_punkt(self, x: float, y: float, fid: int = 1): """Erstellt einen PunktortTA-Layer-Mock mit einem Punkt bei (x, y).""" layer = _make_layer(lid=f"PTA_{fid}") geom = MagicMock() geom.isEmpty.return_value = False pt = MagicMock() pt.x.return_value = x pt.y.return_value = y geom.asPoint.return_value = pt feat = MagicMock() feat.id.return_value = fid feat.geometry.return_value = geom layer.getFeatures.return_value = [feat] return layer def _make_bror_layer(self, x: float = 0.0, y: float = 0.0): """BROR-Layer mit einer minimalen Polygon-Geometrie.""" layer = _make_layer() geom = MagicMock() geom.isEmpty.return_value = False geom.area.return_value = 10000.0 feat = MagicMock() feat.geometry.return_value = geom layer.getFeatures.return_value = [feat] return layer @patch(f"{_MOD}.QgsGeometry") @patch(f"{_MOD}._baue_pta_index") @patch(f"{_MOD}._erstelle_ergebnis_layer") @patch(f"qgis.core.QgsRectangle", create=True) @patch(f"qgis.core.QgsPointXY", create=True) def test_identischer_punkt_wird_gruen_gezaehlt( self, mock_ptxy, mock_rect, mock_erstelle, mock_index, mock_geom_cls ): """PunktortTA exakt am Knickpunkt → identisch_count == 1.""" # Knickpunkt bei (100, 200) knick = MagicMock() knick.x.return_value = 100.0 knick.y.return_value = 200.0 # PunktortTA ebenfalls bei (100, 200) – exakter Treffer pta_geom = MagicMock() pta_geom.isEmpty.return_value = False pta_pt = MagicMock() pta_pt.x.return_value = 100.0 pta_pt.y.return_value = 200.0 pta_geom.asPoint.return_value = pta_pt # Spatial-Index gibt Feature-ID 1 zurück mock_idx = MagicMock() mock_idx.intersects.return_value = [1] mock_index.return_value = (mock_idx, {1: pta_geom}) # Ergebnis-Layer pr = MagicMock() pr.addFeatures.return_value = True ergebnis = MagicMock() ergebnis.dataProvider.return_value = pr ergebnis.isValid.return_value = True mock_erstelle.return_value = ergebnis bror = self._make_bror_layer() pta = self._make_pta_layer_mit_punkt(100.0, 200.0) result = knickpunkt_pruefung( bror, pta, None, knickpunkte=[knick] ) _, identisch, kein_pta, _ = result self.assertEqual(identisch, 1) self.assertEqual(kein_pta, 0) @patch(f"{_MOD}.QgsGeometry") @patch(f"{_MOD}._baue_pta_index") @patch(f"{_MOD}._erstelle_ergebnis_layer") @patch(f"qgis.core.QgsRectangle", create=True) @patch(f"qgis.core.QgsPointXY", create=True) def test_kein_pta_an_knickpunkt_wird_rot_gezaehlt( self, mock_ptxy, mock_rect, mock_erstelle, mock_index, mock_geom_cls ): """Kein PunktortTA am Knickpunkt → kein_pta_count == 1.""" knick = MagicMock() knick.x.return_value = 100.0 knick.y.return_value = 200.0 # Spatial-Index gibt leere Liste zurück → kein Treffer mock_idx = MagicMock() mock_idx.intersects.return_value = [] mock_index.return_value = (mock_idx, {}) pr = MagicMock() pr.addFeatures.return_value = True ergebnis = MagicMock() ergebnis.dataProvider.return_value = pr ergebnis.isValid.return_value = True mock_erstelle.return_value = ergebnis bror = self._make_bror_layer() pta = self._make_pta_layer_mit_punkt(999.0, 999.0) # weit weg result = knickpunkt_pruefung( bror, pta, None, knickpunkte=[knick] ) _, identisch, kein_pta, _ = result self.assertEqual(identisch, 0) self.assertEqual(kein_pta, 1) # --------------------------------------------------------------------------- # Tests: _baue_flst_vertex_set # --------------------------------------------------------------------------- class TestBaueFlstVertexSet(unittest.TestCase): def test_none_layer_gibt_leeres_set(self): from sn_verfahrensgebiet.functions.knickpunkt_pruefung import _baue_flst_vertex_set result = _baue_flst_vertex_set(None, MagicMock()) self.assertEqual(result, set()) def test_layer_ohne_schnitt_mit_puffer_ignoriert(self): from sn_verfahrensgebiet.functions.knickpunkt_pruefung import _baue_flst_vertex_set layer = _make_layer() geom = MagicMock() geom.isEmpty.return_value = False geom.intersects.return_value = False # kein Schnitt feat = MagicMock() feat.geometry.return_value = geom layer.getFeatures.return_value = [feat] buffer_geom = MagicMock() result = _baue_flst_vertex_set(layer, buffer_geom) self.assertEqual(result, set()) if __name__ == "__main__": unittest.main()