79 neue Testfälle: Verfahrensgebiet-Zustandsverwaltung (Listener-Setup/ -teardown, Signal-Handler), DockManager/BaseDockWidget, pruef_ergebnis- Persistenz-Gates, Pruefmanager-Entscheidungswege, restliche Dateipruefer- Zweige, Fundament-Wrapper (sys_wrapper, ly_existence_wrapper, ly_style_wrapper.apply_style_from_path, landkreise, message_wrapper, qgiscore_wrapper.polygon_zu_linie/layer_exists_in_gpkg, qgisui_wrapper.add_layer_to_project) und SettingsTab.save_data/load_data. Ausgeklammert: LayerLoader.apply_style/ly_style_wrapper.apply_style (Bug-Verdacht, hartkodierter Fachplugin-Pfad) und Pruefmanager._ask_use_or_replace_pufferlayer (bereits als Bug in #60 erfasst: ruft nicht existierendes self.pruefmanager auf). qt_wrapper._MockQDockWidget um closeEvent() ergänzt (fehlte, ließ jede BaseDockWidget.closeEvent()-Ausführung im Mock-Modus abstürzen). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
199 lines
8.4 KiB
Python
199 lines
8.4 KiB
Python
"""Tests für sn_basis/functions/verfahrensgebiet_manager.py (zentrale Layer-Verfolgung, Issue #65)."""
|
|
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
from sn_basis.functions import verfahrensgebiet_manager
|
|
import sn_basis.functions.verfahrensgebiet_manager as vgm
|
|
|
|
|
|
class _Scope:
|
|
def __init__(self, value: str):
|
|
self._value = value
|
|
|
|
def variable(self, _name: str) -> str:
|
|
return self._value
|
|
|
|
|
|
class TestVerfahrensgebietManager(unittest.TestCase):
|
|
|
|
@patch("sn_basis.functions.verfahrensgebiet_manager.get_variable", return_value="layer_123")
|
|
def test_get_verfahrensgebiet_layer_id_returns_value(self, _mock_get_variable):
|
|
"""Prüft, dass get_verfahrensgebiet_layer_id() die in der Projektvariable gespeicherte Layer-ID liefert."""
|
|
result = verfahrensgebiet_manager.get_verfahrensgebiet_layer_id()
|
|
|
|
self.assertEqual(result, "layer_123")
|
|
|
|
@patch("sn_basis.functions.verfahrensgebiet_manager.get_variable", return_value="")
|
|
def test_get_verfahrensgebiet_layer_id_returns_none_for_empty_value(self, _mock_get_variable):
|
|
"""Prüft, dass get_verfahrensgebiet_layer_id() bei leerer Projektvariable None statt eines leeren Strings liefert."""
|
|
result = verfahrensgebiet_manager.get_verfahrensgebiet_layer_id()
|
|
|
|
self.assertIsNone(result)
|
|
|
|
@patch("sn_basis.functions.verfahrensgebiet_manager.set_variable")
|
|
def test_set_verfahrensgebiet_layer_id_writes_project_variable(self, mock_set_variable):
|
|
"""Prüft, dass set_verfahrensgebiet_layer_id() die Layer-ID als Projektvariable 'verfahrensgebiet_layer' speichert."""
|
|
verfahrensgebiet_manager.set_verfahrensgebiet_layer_id("layer_abc")
|
|
|
|
mock_set_variable.assert_called_once_with("verfahrensgebiet_layer", "layer_abc", scope="project")
|
|
|
|
@patch("sn_basis.functions.verfahrensgebiet_manager._find_verfahrensgebiet_layer")
|
|
@patch("sn_basis.functions.variable_wrapper.QgsProject.instance")
|
|
@patch("sn_basis.functions.variable_wrapper.QgsExpressionContextUtils.setProjectVariable")
|
|
@patch("sn_basis.functions.variable_wrapper.QgsExpressionContextUtils.projectScope")
|
|
def test_update_without_layer_does_not_write_when_already_empty(
|
|
self,
|
|
mock_project_scope,
|
|
mock_set_project_variable,
|
|
mock_project_instance,
|
|
mock_find_layer,
|
|
):
|
|
"""Prüft, dass ohne gefundenen Verfahrensgebiet-Layer keine Schreibaktion erfolgt, wenn die Variable bereits leer ist."""
|
|
mock_find_layer.return_value = (None, None)
|
|
mock_project_instance.return_value = object()
|
|
mock_project_scope.return_value = _Scope("")
|
|
|
|
result = verfahrensgebiet_manager._update_verfahrensgebiet_from_project()
|
|
|
|
self.assertFalse(result)
|
|
mock_set_project_variable.assert_not_called()
|
|
|
|
@patch("sn_basis.functions.verfahrensgebiet_manager._find_verfahrensgebiet_layer")
|
|
@patch("sn_basis.functions.verfahrensgebiet_manager.set_variable")
|
|
def test_update_with_layer_writes_variable(self, mock_set_variable, mock_find_layer):
|
|
"""Prüft, dass ein gefundener Verfahrensgebiet-Layer seine ID als Projektvariable speichert."""
|
|
mock_layer = object()
|
|
mock_find_layer.return_value = (mock_layer, "layer_99")
|
|
|
|
result = verfahrensgebiet_manager._update_verfahrensgebiet_from_project()
|
|
|
|
self.assertTrue(result)
|
|
mock_set_variable.assert_called_once_with("verfahrensgebiet_layer", "layer_99", scope="project")
|
|
|
|
|
|
class _FakeSignal:
|
|
def __init__(self) -> None:
|
|
self.connected: list = []
|
|
|
|
def connect(self, fn) -> None:
|
|
self.connected.append(fn)
|
|
|
|
def disconnect(self, fn) -> None:
|
|
self.connected.remove(fn)
|
|
|
|
|
|
class _FakeLayer:
|
|
def __init__(self, layer_id: str, name: str = "Verfahrensgebiet") -> None:
|
|
self._id = layer_id
|
|
self._name = name
|
|
|
|
def id(self) -> str:
|
|
return self._id
|
|
|
|
|
|
class _FakeProject:
|
|
def __init__(self, layers=None) -> None:
|
|
self.layersAdded = _FakeSignal()
|
|
self.layerRemoved = _FakeSignal()
|
|
self.readProject = _FakeSignal()
|
|
self.newProjectCreated = _FakeSignal()
|
|
self.cleared = _FakeSignal()
|
|
self._layers = layers or []
|
|
|
|
def mapLayersByName(self, name: str):
|
|
return [l for l in self._layers if l._name == name]
|
|
|
|
|
|
def _mit_projekt(projekt: _FakeProject):
|
|
return patch.object(vgm, "QgsProject", type("_P", (), {"instance": staticmethod(lambda: projekt)}))
|
|
|
|
|
|
class OnHandlerTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
patch.object(vgm, "QGIS_AVAILABLE", True).start()
|
|
self.addCleanup(patch.stopall)
|
|
vgm.set_variable("verfahrensgebiet_layer", "", scope="project")
|
|
|
|
def test_on_project_changed_ohne_layer_setzt_variable_leer(self):
|
|
"""Prüft, dass _on_project_changed die Variable leert, wenn kein Verfahrensgebiet-Layer existiert."""
|
|
vgm.set_variable("verfahrensgebiet_layer", "alter_layer", scope="project")
|
|
with _mit_projekt(_FakeProject(layers=[])):
|
|
vgm._on_project_changed()
|
|
|
|
self.assertIsNone(vgm.get_verfahrensgebiet_layer_id())
|
|
|
|
def test_on_project_changed_mit_layer_setzt_variable(self):
|
|
"""Prüft, dass _on_project_changed die Layer-ID eines gefundenen Verfahrensgebiet-Layers übernimmt."""
|
|
with _mit_projekt(_FakeProject(layers=[_FakeLayer("layer123")])):
|
|
vgm._on_project_changed()
|
|
|
|
self.assertEqual(vgm.get_verfahrensgebiet_layer_id(), "layer123")
|
|
|
|
def test_on_layers_added_findet_neuen_layer(self):
|
|
"""Prüft, dass _on_layers_added nach neu hinzugefügten Layern sucht und die Variable aktualisiert."""
|
|
with _mit_projekt(_FakeProject(layers=[_FakeLayer("layer456")])):
|
|
vgm._on_layers_added(["irrelevant"])
|
|
|
|
self.assertEqual(vgm.get_verfahrensgebiet_layer_id(), "layer456")
|
|
|
|
@patch.object(vgm, "_update_verfahrensgebiet_from_project")
|
|
def test_on_layer_removed_bei_aktuellem_layer_sucht_neu(self, mock_update):
|
|
"""Prüft, dass das Entfernen des aktuell aktiven Verfahrensgebiet-Layers eine Neusuche auslöst."""
|
|
vgm.set_variable("verfahrensgebiet_layer", "layer123", scope="project")
|
|
|
|
vgm._on_layer_removed("layer123")
|
|
|
|
mock_update.assert_called_once()
|
|
|
|
@patch.object(vgm, "_update_verfahrensgebiet_from_project")
|
|
def test_on_layer_removed_bei_anderem_layer_aendert_nichts(self, mock_update):
|
|
"""Prüft, dass das Entfernen eines anderen Layers keine Neusuche auslöst."""
|
|
vgm.set_variable("verfahrensgebiet_layer", "layer123", scope="project")
|
|
|
|
vgm._on_layer_removed("ganz_anderer_layer")
|
|
|
|
mock_update.assert_not_called()
|
|
|
|
|
|
class ListenerSetupTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
patch.object(vgm, "_listener_installed", False).start()
|
|
self.addCleanup(patch.stopall)
|
|
|
|
def test_setup_ohne_qgis_tut_nichts(self):
|
|
"""Prüft, dass setup_verfahrensgebiet_listener ohne QGIS keine Signale registriert."""
|
|
with patch.object(vgm, "QGIS_AVAILABLE", False):
|
|
vgm.setup_verfahrensgebiet_listener()
|
|
|
|
self.assertFalse(vgm._listener_installed)
|
|
|
|
def test_setup_verbindet_signale_und_ist_idempotent(self):
|
|
"""Prüft, dass setup_verfahrensgebiet_listener die Projektsignale verbindet und ein zweiter Aufruf nichts doppelt registriert."""
|
|
projekt = _FakeProject(layers=[])
|
|
with patch.object(vgm, "QGIS_AVAILABLE", True), _mit_projekt(projekt):
|
|
vgm.setup_verfahrensgebiet_listener()
|
|
vgm.setup_verfahrensgebiet_listener()
|
|
|
|
self.assertEqual(len(projekt.layersAdded.connected), 1)
|
|
self.assertEqual(len(projekt.layerRemoved.connected), 1)
|
|
self.assertEqual(len(projekt.readProject.connected), 1)
|
|
self.assertTrue(vgm._listener_installed)
|
|
|
|
def test_teardown_trennt_alle_signale_und_ist_idempotent(self):
|
|
"""Prüft, dass teardown_verfahrensgebiet_listener alle Signale trennt und mehrfacher Aufruf nicht fehlschlägt."""
|
|
projekt = _FakeProject(layers=[])
|
|
with patch.object(vgm, "QGIS_AVAILABLE", True), _mit_projekt(projekt):
|
|
vgm.setup_verfahrensgebiet_listener()
|
|
vgm.teardown_verfahrensgebiet_listener()
|
|
vgm.teardown_verfahrensgebiet_listener()
|
|
|
|
self.assertEqual(projekt.layersAdded.connected, [])
|
|
self.assertEqual(projekt.layerRemoved.connected, [])
|
|
self.assertEqual(projekt.readProject.connected, [])
|
|
self.assertFalse(vgm._listener_installed)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|