305 lines
12 KiB
Python
305 lines
12 KiB
Python
"""sn_verfahrensgebiet/tests/test_beteiligung_pruefung.py
|
|||
|
|
|
||
|
|
Unit-Tests für :mod:`sn_verfahrensgebiet.functions.beteiligung_pruefung`.
|
||
|
|
|
||
|
|
Getestete Szenarien:
|
||
|
|
|
||
|
|
baue_flurstueck_csv:
|
||
|
|
- Normalfall mit führenden Nullen bei Zähler/Nenner
|
||
|
|
- Nenner = 0 → Platzhalter "____"
|
||
|
|
- Nenner leer → Platzhalter "____"
|
||
|
|
- Ungültiges Format (nicht 4 Teile) → None
|
||
|
|
- Leerer Wert → None
|
||
|
|
|
||
|
|
baue_flurstueck_csv_liste:
|
||
|
|
- Bildet Liste nur aus gültigen Zeilen, schreibt Flurstueck_csv in jede Zeile zurück
|
||
|
|
|
||
|
|
lade_beteiligung_csv:
|
||
|
|
- Dateidialog abgebrochen → None
|
||
|
|
- Spalte FLURSTUECK fehlt → BeteiligungPruefungFehler
|
||
|
|
- Leere Datei → BeteiligungPruefungFehler
|
||
|
|
- Erfolgsfall → (zeilen, spaltennamen)
|
||
|
|
|
||
|
|
try2/try3 (kein automatisches Laden mehr, siehe Modul-Docstring):
|
||
|
|
- Verfahrensgebiet-Layer ohne Kennzeichen vorhanden → Aufforderung statt Auto-Laden
|
||
|
|
- Kein Verfahrensgebiet-Layer vorhanden → Verfahrensnummer aus CSV wird gesetzt,
|
||
|
|
dann Aufforderung statt Auto-Laden
|
||
|
|
"""
|
||
|
|
|
||
|
|
import csv
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
import tempfile
|
||
|
|
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.beteiligung_pruefung import (
|
||
|
|
baue_flurstueck_csv,
|
||
|
|
baue_flurstueck_csv_liste,
|
||
|
|
lade_beteiligung_csv,
|
||
|
|
beteiligung_pruefen,
|
||
|
|
BeteiligungPruefungFehler,
|
||
|
|
FELD_FLURSTUECK_CSV,
|
||
|
|
CSV_SPALTE_FLURSTUECK,
|
||
|
|
CSV_SPALTE_VEF_VKZF,
|
||
|
|
_fordere_manuelles_laden_an,
|
||
|
|
_try3_bootstrap,
|
||
|
|
_hole_verfahrensgebiet_layer,
|
||
|
|
)
|
||
|
|
|
||
|
|
_MOD = "sn_verfahrensgebiet.functions.beteiligung_pruefung"
|
||
|
|
|
||
|
|
|
||
|
|
class TestBaueFlurstueckCsv(unittest.TestCase):
|
||
|
|
def test_normalfall_mit_fuehrenden_nullen(self):
|
||
|
|
ergebnis = baue_flurstueck_csv("1234/123/45/00", "14")
|
||
|
|
self.assertEqual(ergebnis, "141234___00123004500")
|
||
|
|
|
||
|
|
def test_nenner_null_wird_zu_unterstrichen(self):
|
||
|
|
ergebnis = baue_flurstueck_csv("1234/123/0/00", "14")
|
||
|
|
self.assertEqual(ergebnis, "141234___00123____00")
|
||
|
|
|
||
|
|
def test_nenner_leer_wird_zu_unterstrichen(self):
|
||
|
|
ergebnis = baue_flurstueck_csv("1234//45/00", "14")
|
||
|
|
# Gemarkung 1234, Zähler leer -> "00000"? -> zfill auf leeren String ergibt "00000"
|
||
|
|
self.assertTrue(ergebnis.startswith("141234___00000"))
|
||
|
|
|
||
|
|
def test_gemarkung_ohne_fuehrende_nullen_wird_aufgefuellt(self):
|
||
|
|
ergebnis = baue_flurstueck_csv("12/1/1/00", "14")
|
||
|
|
self.assertTrue(ergebnis.startswith("140012___"))
|
||
|
|
|
||
|
|
def test_ungueltiges_format_gibt_none(self):
|
||
|
|
self.assertIsNone(baue_flurstueck_csv("1234/123/45", "14"))
|
||
|
|
self.assertIsNone(baue_flurstueck_csv("1234/123/45/00/99", "14"))
|
||
|
|
|
||
|
|
def test_leerer_wert_gibt_none(self):
|
||
|
|
self.assertIsNone(baue_flurstueck_csv("", "14"))
|
||
|
|
self.assertIsNone(baue_flurstueck_csv(None, "14"))
|
||
|
|
|
||
|
|
def test_nenner_9001_wird_zu_buchstabe_a(self):
|
||
|
|
ergebnis = baue_flurstueck_csv("1234/123/9001/00", "14")
|
||
|
|
self.assertEqual(ergebnis, "141234___00123000a00")
|
||
|
|
|
||
|
|
def test_nenner_9002_wird_zu_buchstabe_b(self):
|
||
|
|
ergebnis = baue_flurstueck_csv("1234/123/9002/00", "14")
|
||
|
|
self.assertEqual(ergebnis, "141234___00123000b00")
|
||
|
|
|
||
|
|
def test_nenner_9026_wird_zu_buchstabe_z(self):
|
||
|
|
ergebnis = baue_flurstueck_csv("1234/123/9026/00", "14")
|
||
|
|
self.assertEqual(ergebnis, "141234___00123000z00")
|
||
|
|
|
||
|
|
def test_nenner_ausserhalb_buchstaben_bereich_bleibt_numerisch(self):
|
||
|
|
ergebnis = baue_flurstueck_csv("1234/123/9027/00", "14")
|
||
|
|
self.assertEqual(ergebnis, "141234___00123902700")
|
||
|
|
|
||
|
|
def test_nenner_9001_mit_fuehrenden_nullen_wird_erkannt(self):
|
||
|
|
ergebnis = baue_flurstueck_csv("1234/123/09001/00", "14")
|
||
|
|
self.assertEqual(ergebnis, "141234___00123000a00")
|
||
|
|
|
||
|
|
def test_einstellige_folge_wird_auf_2_stellen_aufgefuellt(self):
|
||
|
|
ergebnis = baue_flurstueck_csv("1234/123/45/5", "14")
|
||
|
|
self.assertEqual(ergebnis, "141234___00123004505")
|
||
|
|
|
||
|
|
|
||
|
|
class TestBaueFlurstueckCsvListe(unittest.TestCase):
|
||
|
|
def test_nur_gueltige_zeilen_in_liste(self):
|
||
|
|
zeilen = [
|
||
|
|
{CSV_SPALTE_FLURSTUECK: "1234/123/45/00"},
|
||
|
|
{CSV_SPALTE_FLURSTUECK: "ungueltig"},
|
||
|
|
{CSV_SPALTE_FLURSTUECK: "5678/1/0/01"},
|
||
|
|
]
|
||
|
|
ergebnis = baue_flurstueck_csv_liste(zeilen, "14")
|
||
|
|
self.assertEqual(len(ergebnis), 2)
|
||
|
|
self.assertEqual(zeilen[0][FELD_FLURSTUECK_CSV], "141234___00123004500")
|
||
|
|
self.assertEqual(zeilen[1][FELD_FLURSTUECK_CSV], "")
|
||
|
|
self.assertEqual(zeilen[2][FELD_FLURSTUECK_CSV], "145678___00001____01")
|
||
|
|
|
||
|
|
|
||
|
|
class TestHoleVerfahrensgebietLayer(unittest.TestCase):
|
||
|
|
@patch(f"{_MOD}.QgsProject")
|
||
|
|
def test_layer_per_name_hat_vorrang_vor_combo_auswahl(self, mock_project_cls):
|
||
|
|
namens_layer = MagicMock(name="verfahrensgebiet_layer")
|
||
|
|
mock_project = MagicMock()
|
||
|
|
mock_project.mapLayersByName.return_value = [namens_layer]
|
||
|
|
mock_project_cls.instance.return_value = mock_project
|
||
|
|
|
||
|
|
andere_auswahl = MagicMock(name="andere_layer_in_combo")
|
||
|
|
andere_auswahl.isValid.return_value = True
|
||
|
|
tab_widget = MagicMock()
|
||
|
|
tab_widget.verf_layer_combo.currentLayer.return_value = andere_auswahl
|
||
|
|
|
||
|
|
ergebnis = _hole_verfahrensgebiet_layer(tab_widget)
|
||
|
|
|
||
|
|
self.assertIs(ergebnis, namens_layer)
|
||
|
|
|
||
|
|
@patch(f"{_MOD}.QgsProject")
|
||
|
|
def test_combo_auswahl_als_fallback_ohne_namens_layer(self, mock_project_cls):
|
||
|
|
mock_project = MagicMock()
|
||
|
|
mock_project.mapLayersByName.return_value = []
|
||
|
|
mock_project_cls.instance.return_value = mock_project
|
||
|
|
|
||
|
|
combo_layer = MagicMock(name="combo_layer")
|
||
|
|
combo_layer.isValid.return_value = True
|
||
|
|
tab_widget = MagicMock()
|
||
|
|
tab_widget.verf_layer_combo.currentLayer.return_value = combo_layer
|
||
|
|
|
||
|
|
ergebnis = _hole_verfahrensgebiet_layer(tab_widget)
|
||
|
|
|
||
|
|
self.assertIs(ergebnis, combo_layer)
|
||
|
|
|
||
|
|
@patch(f"{_MOD}.QgsProject")
|
||
|
|
def test_kein_layer_gibt_none(self, mock_project_cls):
|
||
|
|
mock_project = MagicMock()
|
||
|
|
mock_project.mapLayersByName.return_value = []
|
||
|
|
mock_project_cls.instance.return_value = mock_project
|
||
|
|
|
||
|
|
tab_widget = MagicMock()
|
||
|
|
tab_widget.verf_layer_combo.currentLayer.return_value = None
|
||
|
|
|
||
|
|
self.assertIsNone(_hole_verfahrensgebiet_layer(tab_widget))
|
||
|
|
|
||
|
|
|
||
|
|
class TestFordereManuellesLadenAn(unittest.TestCase):
|
||
|
|
"""try2/try3 laden nicht mehr automatisch, sondern fordern den Nutzer auf,
|
||
|
|
den regulären interaktiven Ablauf ('Aus ALKIS laden') selbst zu starten."""
|
||
|
|
|
||
|
|
def test_ohne_vef_vkzf_nennt_fehlende_kennzeichen(self):
|
||
|
|
with self.assertRaises(BeteiligungPruefungFehler) as ctx:
|
||
|
|
_fordere_manuelles_laden_an()
|
||
|
|
self.assertIn("keine verwertbaren", str(ctx.exception))
|
||
|
|
self.assertIn("Aus ALKIS laden", str(ctx.exception))
|
||
|
|
|
||
|
|
def test_mit_vef_vkzf_nennt_verfahrensnummer(self):
|
||
|
|
with self.assertRaises(BeteiligungPruefungFehler) as ctx:
|
||
|
|
_fordere_manuelles_laden_an("12345")
|
||
|
|
self.assertIn("12345", str(ctx.exception))
|
||
|
|
self.assertIn("Aus ALKIS laden", str(ctx.exception))
|
||
|
|
|
||
|
|
|
||
|
|
class TestTry3Bootstrap(unittest.TestCase):
|
||
|
|
@patch(f"{_MOD}.set_variable")
|
||
|
|
def test_setzt_verfahrensnummer_und_fordert_manuelles_laden_an(self, mock_set_variable):
|
||
|
|
zeilen = [{CSV_SPALTE_VEF_VKZF: "12345", CSV_SPALTE_FLURSTUECK: "1234/1/1/00"}]
|
||
|
|
spaltennamen = [CSV_SPALTE_VEF_VKZF, CSV_SPALTE_FLURSTUECK]
|
||
|
|
|
||
|
|
with self.assertRaises(BeteiligungPruefungFehler) as ctx:
|
||
|
|
_try3_bootstrap(object(), zeilen, spaltennamen)
|
||
|
|
|
||
|
|
mock_set_variable.assert_called_once_with("verfahrensnummer", "12345", scope="project")
|
||
|
|
self.assertIn("12345", str(ctx.exception))
|
||
|
|
|
||
|
|
|
||
|
|
class TestBeteiligungPruefenDispatch(unittest.TestCase):
|
||
|
|
"""Dispatch-Logik in beteiligung_pruefen(): try1 unverändert, try2/try3
|
||
|
|
fordern jetzt auf statt automatisch zu laden."""
|
||
|
|
|
||
|
|
@patch(f"{_MOD}._hat_verwendbare_kennzeichen", return_value=False)
|
||
|
|
@patch(f"{_MOD}._hole_verfahrensgebiet_layer")
|
||
|
|
@patch(f"{_MOD}.baue_flurstueck_csv_liste")
|
||
|
|
@patch(f"{_MOD}.get_laenderkennung", return_value="14")
|
||
|
|
@patch(f"{_MOD}.lade_beteiligung_csv")
|
||
|
|
def test_layer_ohne_kennzeichen_fordert_manuelles_laden_an(
|
||
|
|
self, mock_lade_csv, mock_laenderkennung, mock_liste, mock_hole_layer, mock_hat_kz
|
||
|
|
):
|
||
|
|
mock_lade_csv.return_value = ([{}], ["FLURSTUECK"])
|
||
|
|
mock_liste.return_value = ["145678___00001____01"]
|
||
|
|
mock_hole_layer.return_value = MagicMock(name="verf_layer_ohne_kz")
|
||
|
|
|
||
|
|
with self.assertRaises(BeteiligungPruefungFehler) as ctx:
|
||
|
|
beteiligung_pruefen(object())
|
||
|
|
self.assertIn("keine verwertbaren", str(ctx.exception))
|
||
|
|
|
||
|
|
@patch(f"{_MOD}.set_variable")
|
||
|
|
@patch(f"{_MOD}._hole_verfahrensgebiet_layer", return_value=None)
|
||
|
|
@patch(f"{_MOD}.baue_flurstueck_csv_liste")
|
||
|
|
@patch(f"{_MOD}.get_laenderkennung", return_value="14")
|
||
|
|
@patch(f"{_MOD}.lade_beteiligung_csv")
|
||
|
|
def test_kein_layer_setzt_verfahrensnummer_und_fordert_laden_an(
|
||
|
|
self, mock_lade_csv, mock_laenderkennung, mock_liste, mock_hole_layer, mock_set_variable
|
||
|
|
):
|
||
|
|
zeilen = [{CSV_SPALTE_VEF_VKZF: "12345", CSV_SPALTE_FLURSTUECK: "1234/1/1/00"}]
|
||
|
|
mock_lade_csv.return_value = (zeilen, [CSV_SPALTE_VEF_VKZF, CSV_SPALTE_FLURSTUECK])
|
||
|
|
mock_liste.return_value = ["145678___00001____01"]
|
||
|
|
|
||
|
|
with self.assertRaises(BeteiligungPruefungFehler) as ctx:
|
||
|
|
beteiligung_pruefen(object())
|
||
|
|
|
||
|
|
mock_set_variable.assert_called_once_with("verfahrensnummer", "12345", scope="project")
|
||
|
|
self.assertIn("12345", str(ctx.exception))
|
||
|
|
|
||
|
|
|
||
|
|
class TestLadeBeteiligungCsv(unittest.TestCase):
|
||
|
|
def setUp(self):
|
||
|
|
self._tmp_files = []
|
||
|
|
|
||
|
|
def tearDown(self):
|
||
|
|
for pfad in self._tmp_files:
|
||
|
|
try:
|
||
|
|
os.remove(pfad)
|
||
|
|
except OSError:
|
||
|
|
pass
|
||
|
|
|
||
|
|
def _schreibe_csv(self, header, zeilen):
|
||
|
|
fd, pfad = tempfile.mkstemp(suffix=".csv")
|
||
|
|
self._tmp_files.append(pfad)
|
||
|
|
with os.fdopen(fd, "w", encoding="utf-8", newline="") as f:
|
||
|
|
writer = csv.writer(f)
|
||
|
|
writer.writerow(header)
|
||
|
|
for zeile in zeilen:
|
||
|
|
writer.writerow(zeile)
|
||
|
|
return pfad
|
||
|
|
|
||
|
|
@patch(f"{_MOD}.QFileDialog")
|
||
|
|
def test_dialog_abgebrochen_gibt_none(self, mock_fd):
|
||
|
|
mock_fd.getOpenFileName.return_value = ("", "")
|
||
|
|
self.assertIsNone(lade_beteiligung_csv(None))
|
||
|
|
|
||
|
|
@patch(f"{_MOD}.QFileDialog")
|
||
|
|
def test_fehlende_flurstueck_spalte_wirft_fehler(self, mock_fd):
|
||
|
|
pfad = self._schreibe_csv(["FOO", "BAR"], [["1", "2"]])
|
||
|
|
mock_fd.getOpenFileName.return_value = (pfad, "")
|
||
|
|
with self.assertRaises(BeteiligungPruefungFehler) as ctx:
|
||
|
|
lade_beteiligung_csv(None)
|
||
|
|
self.assertIn(CSV_SPALTE_FLURSTUECK, str(ctx.exception))
|
||
|
|
|
||
|
|
@patch(f"{_MOD}.QFileDialog")
|
||
|
|
def test_leere_datei_wirft_fehler(self, mock_fd):
|
||
|
|
fd, pfad = tempfile.mkstemp(suffix=".csv")
|
||
|
|
os.close(fd)
|
||
|
|
self._tmp_files.append(pfad)
|
||
|
|
mock_fd.getOpenFileName.return_value = (pfad, "")
|
||
|
|
with self.assertRaises(BeteiligungPruefungFehler):
|
||
|
|
lade_beteiligung_csv(None)
|
||
|
|
|
||
|
|
@patch(f"{_MOD}.QFileDialog")
|
||
|
|
def test_erfolgsfall_gibt_zeilen_und_spalten_zurueck(self, mock_fd):
|
||
|
|
pfad = self._schreibe_csv(
|
||
|
|
[CSV_SPALTE_VEF_VKZF, "FLA_Beteiligung", CSV_SPALTE_FLURSTUECK],
|
||
|
|
[["12345", "1", "1234/123/45/00"]],
|
||
|
|
)
|
||
|
|
mock_fd.getOpenFileName.return_value = (pfad, "")
|
||
|
|
zeilen, spalten = lade_beteiligung_csv(None)
|
||
|
|
self.assertEqual(len(zeilen), 1)
|
||
|
|
self.assertIn(CSV_SPALTE_FLURSTUECK, spalten)
|
||
|
|
self.assertEqual(zeilen[0][CSV_SPALTE_FLURSTUECK], "1234/123/45/00")
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
unittest.main()
|