Files
Plugin_SN_Basis/tests/test_datenschreiber.py
T
DanielandClaude Sonnet 5 60d5bd7cb5 Tests für die vier Kernmodule der Datenpipeline ergänzt (#63)
70 neue Testfälle für Datenabruf.py, Datenschreiber.py, LayerLoader.py und
excel_importer.py - die einzigen Module ohne jede Testdatei laut
Testkatalog-Abschnitt "Ungetestete Funktionen". Schwerpunkt: Provider-URL-
Bau (WFS/REST/OGR), Raumfilter, CRS-Transformation, GPKG-Schreib-/Ladepfad
und Excel-Import-Fehlerzweige.

qgiscore_wrapper._MockQgsProject um transformContext() und addMapLayer()
ergänzt - beide fehlten im Mock und ließen die betroffenen Funktionen im
Mock-Modus für jeden gültigen Layer scheitern (siehe fallstricke.md).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-18 09:48:31 +02:00

198 lines
8.2 KiB
Python

"""Tests für sn_basis/modules/Datenschreiber.py (Schreib-/Ladepipeline, Issue #63)."""
import json
import tempfile
import unittest
from pathlib import Path
from typing import Any, List, cast
from unittest.mock import patch
from sn_basis.modules.Datenschreiber import Datenschreiber
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
class _AufzeichnenderPruefmanager:
def __init__(self) -> None:
self.ergebnisse: List[Any] = []
def verarbeite(self, ergebnis: Any):
self.ergebnisse.append(ergebnis)
return ergebnis
class _FakeLayer:
def __init__(self, valid: bool = True) -> None:
self._valid = valid
def isValid(self) -> bool:
return self._valid
def loadNamedStyle(self, path: str):
return True, ""
def triggerRepaint(self) -> None:
return None
def dataProvider(self):
return None
def _make_schreiber(gpkg_path: str = "C:/tmp/ziel.gpkg") -> tuple[Datenschreiber, _AufzeichnenderPruefmanager]:
pruefmanager = _AufzeichnenderPruefmanager()
return Datenschreiber(pruefmanager=cast(Any, pruefmanager), gpkg_path=gpkg_path), pruefmanager
class WriteLayerToGpkgTests(unittest.TestCase):
def test_ungueltiger_layer_liefert_fehlermeldung(self):
"""Prüft, dass ein ungültiger Layer eine Fehlermeldung statt eines Absturzes liefert."""
schreiber, _pm = _make_schreiber()
fehler = schreiber._write_layer_to_gpkg(layer_name="test", layer=_FakeLayer(valid=False))
self.assertIsNotNone(fehler)
def test_gueltiger_layer_wird_ohne_fehler_geschrieben(self):
"""Prüft, dass ein gültiger Layer im Mock-Modus ohne Fehlermeldung geschrieben wird."""
schreiber, _pm = _make_schreiber()
fehler = schreiber._write_layer_to_gpkg(layer_name="test", layer=_FakeLayer(valid=True))
self.assertIsNone(fehler)
class ResolveStylePathTests(unittest.TestCase):
def test_leerer_stilpfad_liefert_none(self):
"""Prüft, dass ein leerer oder fehlender Stilpfad None liefert."""
schreiber, _pm = _make_schreiber()
self.assertIsNone(schreiber._resolve_style_path(None))
self.assertIsNone(schreiber._resolve_style_path(" "))
def test_absoluter_vorhandener_stilpfad_wird_aufgeloest(self):
"""Prüft, dass ein absoluter, vorhandener Stilpfad normalisiert und unverändert erkannt wird."""
schreiber, _pm = _make_schreiber()
with tempfile.TemporaryDirectory() as tmp:
stil_datei = Path(tmp) / "stil.qml"
stil_datei.write_text("<qgis/>", encoding="utf-8")
ergebnis = schreiber._resolve_style_path(str(stil_datei))
self.assertIsNotNone(ergebnis)
self.assertTrue(str(ergebnis).endswith("stil.qml"))
def test_nicht_vorhandener_stilpfad_liefert_none(self):
"""Prüft, dass ein nicht vorhandener Stilpfad None liefert, statt einen ungültigen Pfad zurückzugeben."""
schreiber, _pm = _make_schreiber()
self.assertIsNone(schreiber._resolve_style_path("C:/nicht/vorhanden/stil.qml"))
class SchreibeDatenTests(unittest.TestCase):
def test_ohne_speicherort_wirft_valueerror(self):
"""Prüft, dass schreibe_Daten ohne Speicherort einen ValueError wirft."""
schreiber, _pm = _make_schreiber(gpkg_path="")
with self.assertRaises(ValueError):
schreiber.schreibe_Daten(daten_dict={"daten": {}}, processed_results=[], speicherort="")
def test_eintrag_ohne_gueltigen_layer_wird_uebersprungen(self):
"""Prüft, dass ein Eintrag ohne gültigen Layer übersprungen wird und ein Fehlerergebnis meldet."""
schreiber, pruefmanager = _make_schreiber()
daten_dict = {"daten": {"1": {"layer": None}}}
ergebnisse = schreiber.schreibe_Daten(daten_dict=daten_dict, processed_results=[], speicherort="C:/tmp/ziel.gpkg")
self.assertEqual(ergebnisse, [])
aktionen = [getattr(e, "aktion", None) for e in pruefmanager.ergebnisse]
self.assertIn("save_exception", aktionen)
def test_erfolgreicher_eintrag_liefert_layer_path_und_thema(self):
"""Prüft, dass ein gültiger Layer einen Layer-Path mit GPKG-Pfad und Layername liefert."""
schreiber, _pm = _make_schreiber(gpkg_path="C:/tmp/ziel.gpkg")
daten_dict = {"daten": {"1": {"layer": _FakeLayer(valid=True)}}}
processed_results = [
pruef_ergebnis(ok=True, meldung="ok", aktion="datenabruf", kontext={"ident": "1", "thema": "Testthema"})
]
ergebnisse = schreiber.schreibe_Daten(
daten_dict=daten_dict, processed_results=processed_results, speicherort="C:/tmp/ziel.gpkg"
)
self.assertEqual(len(ergebnisse), 1)
eintrag = ergebnisse[0]
self.assertEqual(eintrag["thema"], "Testthema")
self.assertEqual(eintrag["ident"], "1")
self.assertEqual(eintrag["layer_path"], "C:/tmp/ziel.gpkg|layername=Testthema")
def test_ohne_thema_wird_ident_als_layername_verwendet(self):
"""Prüft, dass ohne passendes Thema in processed_results der ident als Layername verwendet wird."""
schreiber, _pm = _make_schreiber(gpkg_path="C:/tmp/ziel.gpkg")
daten_dict = {"daten": {"42": {"layer": _FakeLayer(valid=True)}}}
ergebnisse = schreiber.schreibe_Daten(daten_dict=daten_dict, processed_results=[], speicherort="C:/tmp/ziel.gpkg")
self.assertEqual(ergebnisse[0]["thema"], "42")
class LadeLayerTests(unittest.TestCase):
def test_ohne_layer_path_wird_uebersprungen(self):
"""Prüft, dass ein Eintrag ohne layer_path übersprungen wird, ohne einen Fehler zu melden."""
schreiber, pruefmanager = _make_schreiber()
schreiber.lade_Layer([{"thema": "ohne_pfad"}])
self.assertEqual(pruefmanager.ergebnisse, [])
def test_gueltiger_layer_wird_dem_projekt_hinzugefuegt(self):
"""Prüft, dass ein gültig ladbarer Layer ohne Fehlermeldung verarbeitet wird."""
schreiber, pruefmanager = _make_schreiber()
schreiber.lade_Layer([{"layer_path": "C:/tmp/ziel.gpkg|layername=Testthema", "thema": "Testthema"}])
fehler = [e for e in pruefmanager.ergebnisse if not getattr(e, "ok", True)]
self.assertEqual(fehler, [])
@patch("sn_basis.modules.Datenschreiber.qgiscore.QgsVectorLayer")
def test_ungueltiger_layer_meldet_layer_nicht_gefunden(self, mock_layer_cls):
"""Prüft, dass ein ungültig ladbarer Layer als 'layer_nicht_gefunden' gemeldet wird."""
mock_layer_cls.return_value = _FakeLayer(valid=False)
schreiber, pruefmanager = _make_schreiber()
schreiber.lade_Layer([{"layer_path": "C:/tmp/ziel.gpkg|layername=Testthema", "thema": "Testthema"}])
aktionen = [getattr(e, "aktion", None) for e in pruefmanager.ergebnisse]
self.assertIn("layer_nicht_gefunden", aktionen)
class SchreibeLogTests(unittest.TestCase):
def test_ohne_speicherort_wirft_valueerror(self):
"""Prüft, dass schreibe_log ohne Speicherort einen ValueError wirft."""
schreiber, _pm = _make_schreiber()
with self.assertRaises(ValueError):
schreiber.schreibe_log(processed_results=[], speicherort="")
def test_schreibt_json_datei_mit_ergebnissen_und_gibt_pfad_zurueck(self):
"""Prüft, dass schreibe_log eine lesbare JSON-Log-Datei erzeugt und deren Pfad zurückgibt."""
schreiber, pruefmanager = _make_schreiber()
processed_results = [
pruef_ergebnis(ok=True, meldung="alles ok", aktion="datenabruf", kontext={"ident": "1"}),
pruef_ergebnis(ok=False, meldung="Fehler", aktion="url_nicht_erreichbar", kontext={"ident": "2"}),
]
with tempfile.TemporaryDirectory() as tmp:
log_path = schreiber.schreibe_log(processed_results=processed_results, speicherort=tmp)
self.assertTrue(Path(log_path).is_file())
inhalt = json.loads(Path(log_path).read_text(encoding="utf-8"))
self.assertEqual(len(inhalt), 2)
self.assertEqual(inhalt[0]["meldung"], "alles ok")
self.assertEqual(inhalt[1]["kontext"], {"ident": "2"})
aktionen = [getattr(e, "aktion", None) for e in pruefmanager.ergebnisse]
self.assertIn("standarddatei_vorschlagen", aktionen)
if __name__ == "__main__":
unittest.main()