Restliche "ungeklärt"-Funktionen in sn_basis abschließend bewertet (#70)
Tests für Dateipruefer._ist_leer (None/Whitespace), Dateipruefer._handle_leere_eingabe (Priorität bei mehreren Flags), Datenschreiber._store_style_in_gpkg (nachgezogen aus #63, siehe #67) und Pruefmanager._handle_verfahrensgebiet_layer_existiert (entfernen/ersetzen) ergänzt. Linkpruefer._ist_url/_pruefe_url/_pruefe_dateipfad und Pruefmanager._handle_datei_existiert sind bereits über pruefe()/verarbeite() vollständig abgedeckt und bekommen den Vermerk "von Test abgesehen". Pruefmanager._ask_use_or_replace_pufferlayer ist toter Code (Bug #60) und bleibt bis zur Behebung unmarkiert getestet, nur vermerkt. sn_basis steht damit laut Prüfstand bei 0 ungeklärten Funktionen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
- Restliche „ungeklärt"-Funktionen aus dem Prüfstand entschieden: Tests für Dateipruefer (`_ist_leer`, Priorität in `_handle_leere_eingabe`), Datenschreiber (`_store_style_in_gpkg` – Tabelle anlegen, Default-Stil zurücksetzen, Geometriespalte übernehmen) und Pruefmanager (`_handle_verfahrensgebiet_layer_existiert` „entfernen"/„ersetzen"); Linkpruefer (`_ist_url`, `_pruefe_url`, `_pruefe_dateipfad`), `Pruefmanager._handle_datei_existiert` und `Pruefmanager._ask_use_or_replace_pufferlayer` (toter Code, Bug #60) bekamen den Vermerk „von Test abgesehen" – sn_basis steht jetzt bei 0 ungeklärten Funktionen (#70)
|
||||||
- Kategorie „vielleicht testen" abgeschlossen: 30 weitere Funktionen bekamen Tests (os_wrapper, qgiscore_wrapper, qgisui_wrapper, DataGrabber, pruef_ergebnis, Navigation, SettingsTab, PrintTab – Zustandslogik, Signal-Handling, Atlas-/Theme-Verwaltung), `PrintTab._build_ui` bekam den Vermerk „Test ausgesetzt, zunächst Refactor" (#67)
|
- Kategorie „vielleicht testen" abgeschlossen: 30 weitere Funktionen bekamen Tests (os_wrapper, qgiscore_wrapper, qgisui_wrapper, DataGrabber, pruef_ergebnis, Navigation, SettingsTab, PrintTab – Zustandslogik, Signal-Handling, Atlas-/Theme-Verwaltung), `PrintTab._build_ui` bekam den Vermerk „Test ausgesetzt, zunächst Refactor" (#67)
|
||||||
- Testkatalog-Klassifizierung in Docstrings festgehalten: 43 als „vernachlässigbar" eingestufte Funktionen tragen jetzt den Vermerk „von Test abgesehen, weil vernachlässigbar", 41 Print-Bereich-Funktionen (`print_logic.py`, `print_layout.py`, `print_layout_baufreigabe.py`) den Vermerk „Test ausgesetzt, zunächst Refactor" – keine Verhaltensänderung
|
- Testkatalog-Klassifizierung in Docstrings festgehalten: 43 als „vernachlässigbar" eingestufte Funktionen tragen jetzt den Vermerk „von Test abgesehen, weil vernachlässigbar", 41 Print-Bereich-Funktionen (`print_logic.py`, `print_layout.py`, `print_layout_baufreigabe.py`) den Vermerk „Test ausgesetzt, zunächst Refactor" – keine Verhaltensänderung
|
||||||
- Tests für weitere zentrale Funktionen ergänzt: Verfahrensgebiet-Zustandsverwaltung, Dock-Verwaltung, Pruefmanager-/Dateipruefer-Entscheidungswege, Fundament-Wrapper (Pfade, Landkreise, Nachrichten, Layer-Existenz/-Stil, GPKG-Layer-Prüfung, QGIS-3/4-Geometriekompatibilität) und Projekteigenschaften-Tab (#65)
|
- Tests für weitere zentrale Funktionen ergänzt: Verfahrensgebiet-Zustandsverwaltung, Dock-Verwaltung, Pruefmanager-/Dateipruefer-Entscheidungswege, Fundament-Wrapper (Pfade, Landkreise, Nachrichten, Layer-Existenz/-Stil, GPKG-Layer-Prüfung, QGIS-3/4-Geometriekompatibilität) und Projekteigenschaften-Tab (#65)
|
||||||
|
|||||||
@@ -104,6 +104,9 @@ class Pruefmanager:
|
|||||||
- ``datei_existiert_ueberschreiben``
|
- ``datei_existiert_ueberschreiben``
|
||||||
- ``datei_existiert_anhaengen``
|
- ``datei_existiert_anhaengen``
|
||||||
- ``datei_existiert_ueberspringen`` (für Cancel-Fall)
|
- ``datei_existiert_ueberspringen`` (für Cancel-Fall)
|
||||||
|
|
||||||
|
von Test abgesehen, weil bereits über verarbeite() in test_pruefmanager.py vollständig
|
||||||
|
abgedeckt (overwrite/append/cancel/non-qgis); HD 21.09.2026
|
||||||
"""
|
"""
|
||||||
if self.ui_modus != "qgis":
|
if self.ui_modus != "qgis":
|
||||||
return ergebnis
|
return ergebnis
|
||||||
@@ -283,6 +286,10 @@ class Pruefmanager:
|
|||||||
-------
|
-------
|
||||||
str
|
str
|
||||||
"verwenden", "ersetzen" oder "abbrechen"
|
"verwenden", "ersetzen" oder "abbrechen"
|
||||||
|
|
||||||
|
von Test abgesehen, weil toter Code: referenziert self.pruefmanager, das auf
|
||||||
|
Pruefmanager-Instanzen nie existiert (AttributeError bei jedem Aufruf), bereits als
|
||||||
|
Bug in #60 erfasst; erst nach dessen Behebung testen; HD 21.09.2026
|
||||||
"""
|
"""
|
||||||
ergebnis = pruef_ergebnis(
|
ergebnis = pruef_ergebnis(
|
||||||
ok=False,
|
ok=False,
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ class Linkpruefer:
|
|||||||
def _ist_url(self, text: str) -> bool:
|
def _ist_url(self, text: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Einfache URL-Erkennung.
|
Einfache URL-Erkennung.
|
||||||
|
|
||||||
|
von Test abgesehen, weil bereits über pruefe() in test_linkpruefer.py indirekt
|
||||||
|
abgedeckt (http-URL vs. lokaler Pfad); HD 21.09.2026
|
||||||
"""
|
"""
|
||||||
return text.startswith("http://") or text.startswith("https://")
|
return text.startswith("http://") or text.startswith("https://")
|
||||||
|
|
||||||
@@ -81,6 +84,9 @@ class Linkpruefer:
|
|||||||
def _pruefe_url(self, url: str) -> pruef_ergebnis:
|
def _pruefe_url(self, url: str) -> pruef_ergebnis:
|
||||||
"""
|
"""
|
||||||
Prüft eine URL über einen HEAD-Request.
|
Prüft eine URL über einen HEAD-Request.
|
||||||
|
|
||||||
|
von Test abgesehen, weil bereits über pruefe() in test_linkpruefer.py vollständig
|
||||||
|
abgedeckt (ok/Fehlerantwort/kein Netz); HD 21.09.2026
|
||||||
"""
|
"""
|
||||||
|
|
||||||
reply = network_head(url)
|
reply = network_head(url)
|
||||||
@@ -115,6 +121,9 @@ class Linkpruefer:
|
|||||||
def _pruefe_dateipfad(self, eingabe: str) -> pruef_ergebnis:
|
def _pruefe_dateipfad(self, eingabe: str) -> pruef_ergebnis:
|
||||||
"""
|
"""
|
||||||
Prüft einen lokalen Pfad.
|
Prüft einen lokalen Pfad.
|
||||||
|
|
||||||
|
von Test abgesehen, weil bereits über pruefe() in test_linkpruefer.py vollständig
|
||||||
|
abgedeckt (gefunden/nicht gefunden); HD 21.09.2026
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pfad = self._pfad(eingabe)
|
pfad = self._pfad(eingabe)
|
||||||
|
|||||||
@@ -160,6 +160,41 @@ class TestDateipruefer(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertFalse(pruefer._ist_gueltiger_gpkg_pfad(Path("C:/nicht/vorhanden/verfahren.gpkg")))
|
self.assertFalse(pruefer._ist_gueltiger_gpkg_pfad(Path("C:/nicht/vorhanden/verfahren.gpkg")))
|
||||||
|
|
||||||
|
# -----------------------------------------------------
|
||||||
|
# 10. _ist_leer: Sonderfälle jenseits von ""
|
||||||
|
# -----------------------------------------------------
|
||||||
|
def test_ist_leer_erkennt_none_und_nicht_string_als_leer(self):
|
||||||
|
"""Prüft, dass _ist_leer sowohl None als auch einen Nicht-String-Wert als leer meldet."""
|
||||||
|
self.assertTrue(Dateipruefer(pfad=None)._ist_leer())
|
||||||
|
self.assertTrue(Dateipruefer(pfad=123)._ist_leer())
|
||||||
|
|
||||||
|
def test_ist_leer_erkennt_reinen_whitespace_als_leer(self):
|
||||||
|
"""Prüft, dass _ist_leer eine Eingabe aus reinen Leerzeichen als leer meldet."""
|
||||||
|
self.assertTrue(Dateipruefer(pfad=" ")._ist_leer())
|
||||||
|
|
||||||
|
def test_ist_leer_meldet_pfad_mit_sichtbaren_zeichen_nicht_als_leer(self):
|
||||||
|
"""Prüft, dass _ist_leer einen Pfad mit sichtbaren Zeichen nicht als leer meldet."""
|
||||||
|
self.assertFalse(Dateipruefer(pfad=" a.gpkg ")._ist_leer())
|
||||||
|
|
||||||
|
# -----------------------------------------------------
|
||||||
|
# 11. _handle_leere_eingabe: Priorität bei mehreren gesetzten Flags
|
||||||
|
# -----------------------------------------------------
|
||||||
|
def test_leere_eingabe_bevorzugt_leereingabe_erlaubt_vor_standarddatei(self):
|
||||||
|
"""Prüft, dass bei leerer Eingabe leereingabe_erlaubt Vorrang vor einer gesetzten Standarddatei hat."""
|
||||||
|
pruefer = Dateipruefer(pfad="", leereingabe_erlaubt=True, standarddatei="/tmp/std.txt")
|
||||||
|
|
||||||
|
result = pruefer._handle_leere_eingabe()
|
||||||
|
|
||||||
|
self.assertEqual(result.aktion, "leereingabe_erlaubt")
|
||||||
|
|
||||||
|
def test_leere_eingabe_bevorzugt_standarddatei_vor_temporaer_erlaubt(self):
|
||||||
|
"""Prüft, dass bei leerer Eingabe eine gesetzte Standarddatei Vorrang vor temporaer_erlaubt hat."""
|
||||||
|
pruefer = Dateipruefer(pfad="", standarddatei="/tmp/std.txt", temporaer_erlaubt=True)
|
||||||
|
|
||||||
|
result = pruefer._handle_leere_eingabe()
|
||||||
|
|
||||||
|
self.assertEqual(result.aktion, "standarddatei_vorschlagen")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
"""Tests für sn_basis/modules/Datenschreiber.py (Schreib-/Ladepipeline, Issue #63)."""
|
"""Tests für sn_basis/modules/Datenschreiber.py (Schreib-/Ladepipeline, Issue #63)."""
|
||||||
|
|
||||||
|
import gc
|
||||||
import json
|
import json
|
||||||
|
import shutil
|
||||||
|
import sqlite3
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -193,5 +196,103 @@ class SchreibeLogTests(unittest.TestCase):
|
|||||||
self.assertIn("standarddatei_vorschlagen", aktionen)
|
self.assertIn("standarddatei_vorschlagen", aktionen)
|
||||||
|
|
||||||
|
|
||||||
|
class StoreStyleInGpkgTests(unittest.TestCase):
|
||||||
|
"""Tests für Datenschreiber._store_style_in_gpkg (Issue #70, nachgezogen aus #63)."""
|
||||||
|
|
||||||
|
def test_legt_tabelle_an_und_speichert_stil_als_default(self):
|
||||||
|
"""Prüft, dass _store_style_in_gpkg die layer_styles-Tabelle anlegt und den Stil als Default speichert."""
|
||||||
|
tmp = tempfile.mkdtemp()
|
||||||
|
try:
|
||||||
|
gpkg_path = str(Path(tmp) / "ziel.gpkg")
|
||||||
|
style_path = Path(tmp) / "stil.qml"
|
||||||
|
style_path.write_text("<qgis>Test-QML</qgis>", encoding="utf-8")
|
||||||
|
schreiber, _pm = _make_schreiber(gpkg_path=gpkg_path)
|
||||||
|
|
||||||
|
schreiber._store_style_in_gpkg("mein_layer", str(style_path))
|
||||||
|
|
||||||
|
with sqlite3.connect(gpkg_path) as conn:
|
||||||
|
zeile = conn.execute(
|
||||||
|
"SELECT f_table_name, styleName, styleQML, useAsDefault FROM layer_styles"
|
||||||
|
).fetchone()
|
||||||
|
conn.close()
|
||||||
|
finally:
|
||||||
|
# _store_style_in_gpkg schließt seine eigene sqlite3-Verbindung nicht (Produktionscode,
|
||||||
|
# nicht Teil von Issue #70) – ohne gc.collect() bleibt die .gpkg-Datei unter Windows
|
||||||
|
# gesperrt und shutil.rmtree schlägt fehl.
|
||||||
|
gc.collect()
|
||||||
|
shutil.rmtree(tmp, ignore_errors=True)
|
||||||
|
|
||||||
|
self.assertEqual(zeile[0], "mein_layer")
|
||||||
|
self.assertEqual(zeile[1], "stil.qml")
|
||||||
|
self.assertEqual(zeile[2], "<qgis>Test-QML</qgis>")
|
||||||
|
self.assertEqual(zeile[3], 1)
|
||||||
|
|
||||||
|
def test_setzt_bisherigen_default_stil_desselben_layers_zurueck(self):
|
||||||
|
"""Prüft, dass _store_style_in_gpkg den useAsDefault-Wert vorhandener Stile desselben Layers zurücksetzt."""
|
||||||
|
tmp = tempfile.mkdtemp()
|
||||||
|
try:
|
||||||
|
gpkg_path = str(Path(tmp) / "ziel.gpkg")
|
||||||
|
style_path = Path(tmp) / "stil.qml"
|
||||||
|
style_path.write_text("<qgis>Neuer Stil</qgis>", encoding="utf-8")
|
||||||
|
schreiber, _pm = _make_schreiber(gpkg_path=gpkg_path)
|
||||||
|
|
||||||
|
schreiber._store_style_in_gpkg("mein_layer", str(style_path))
|
||||||
|
schreiber._store_style_in_gpkg("mein_layer", str(style_path))
|
||||||
|
|
||||||
|
with sqlite3.connect(gpkg_path) as conn:
|
||||||
|
werte = [
|
||||||
|
row[0]
|
||||||
|
for row in conn.execute(
|
||||||
|
"SELECT useAsDefault FROM layer_styles WHERE f_table_name = ? ORDER BY id",
|
||||||
|
("mein_layer",),
|
||||||
|
).fetchall()
|
||||||
|
]
|
||||||
|
conn.close()
|
||||||
|
finally:
|
||||||
|
gc.collect()
|
||||||
|
shutil.rmtree(tmp, ignore_errors=True)
|
||||||
|
|
||||||
|
self.assertEqual(werte, [0, 1])
|
||||||
|
|
||||||
|
def test_liest_geometriespalte_ueber_geometrycolumn_aus(self):
|
||||||
|
"""Prüft, dass _store_style_in_gpkg die Geometriespalte eines Layers mit geometryColumn() übernimmt."""
|
||||||
|
|
||||||
|
class _LayerMitGeometrieSpalte:
|
||||||
|
def geometryColumn(self) -> str:
|
||||||
|
return "geom"
|
||||||
|
|
||||||
|
tmp = tempfile.mkdtemp()
|
||||||
|
try:
|
||||||
|
gpkg_path = str(Path(tmp) / "ziel.gpkg")
|
||||||
|
style_path = Path(tmp) / "stil.qml"
|
||||||
|
style_path.write_text("<qgis/>", encoding="utf-8")
|
||||||
|
schreiber, _pm = _make_schreiber(gpkg_path=gpkg_path)
|
||||||
|
|
||||||
|
schreiber._store_style_in_gpkg("mein_layer", str(style_path), layer=_LayerMitGeometrieSpalte())
|
||||||
|
|
||||||
|
with sqlite3.connect(gpkg_path) as conn:
|
||||||
|
spalte = conn.execute(
|
||||||
|
"SELECT f_geometry_column FROM layer_styles WHERE f_table_name = ?",
|
||||||
|
("mein_layer",),
|
||||||
|
).fetchone()[0]
|
||||||
|
conn.close()
|
||||||
|
finally:
|
||||||
|
gc.collect()
|
||||||
|
shutil.rmtree(tmp, ignore_errors=True)
|
||||||
|
|
||||||
|
self.assertEqual(spalte, "geom")
|
||||||
|
|
||||||
|
def test_fehlender_stilpfad_meldet_fehler_ueber_pruefmanager_statt_absturz(self):
|
||||||
|
"""Prüft, dass ein nicht lesbarer Stilpfad über den Pruefmanager gemeldet wird statt eine Exception zu werfen."""
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
gpkg_path = str(Path(tmp) / "ziel.gpkg")
|
||||||
|
schreiber, pruefmanager = _make_schreiber(gpkg_path=gpkg_path)
|
||||||
|
|
||||||
|
schreiber._store_style_in_gpkg("mein_layer", str(Path(tmp) / "nicht_vorhanden.qml"))
|
||||||
|
|
||||||
|
aktionen = [getattr(e, "aktion", None) for e in pruefmanager.ergebnisse]
|
||||||
|
self.assertIn("style_gpkg_speichern_fehlgeschlagen", aktionen)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -210,6 +210,24 @@ class TestPruefmanager(unittest.TestCase):
|
|||||||
self.assertFalse(entscheidung.ok)
|
self.assertFalse(entscheidung.ok)
|
||||||
self.assertEqual(entscheidung.aktion, "verfahrensgebiet_abbrechen")
|
self.assertEqual(entscheidung.aktion, "verfahrensgebiet_abbrechen")
|
||||||
|
|
||||||
|
@patch("sn_basis.modules.Pruefmanager.ask_ergaenzen_entfernen_ersetzen_abbrechen", return_value="entfernen")
|
||||||
|
def test_verfahrensgebiet_layer_existiert_entfernen(self, _mock_choice):
|
||||||
|
"""Prüft, dass die Nutzerwahl 'entfernen' bei bestehendem Verfahrensgebiet zur Aktion 'verfahrensgebiet_entfernen' führt."""
|
||||||
|
ergebnis = pruef_ergebnis(False, "VG existiert", "verfahrensgebiet_layer_existiert", None)
|
||||||
|
entscheidung = self.manager._handle_verfahrensgebiet_layer_existiert(ergebnis)
|
||||||
|
|
||||||
|
self.assertTrue(entscheidung.ok)
|
||||||
|
self.assertEqual(entscheidung.aktion, "verfahrensgebiet_entfernen")
|
||||||
|
|
||||||
|
@patch("sn_basis.modules.Pruefmanager.ask_ergaenzen_entfernen_ersetzen_abbrechen", return_value="ersetzen")
|
||||||
|
def test_verfahrensgebiet_layer_existiert_ersetzen(self, _mock_choice):
|
||||||
|
"""Prüft, dass die Nutzerwahl 'ersetzen' bei bestehendem Verfahrensgebiet zur Aktion 'verfahrensgebiet_ersetzen' führt."""
|
||||||
|
ergebnis = pruef_ergebnis(False, "VG existiert", "verfahrensgebiet_layer_existiert", None)
|
||||||
|
entscheidung = self.manager._handle_verfahrensgebiet_layer_existiert(ergebnis)
|
||||||
|
|
||||||
|
self.assertTrue(entscheidung.ok)
|
||||||
|
self.assertEqual(entscheidung.aktion, "verfahrensgebiet_ersetzen")
|
||||||
|
|
||||||
def test_verfahrensgebiet_layer_existiert_in_non_qgis_mode_returns_input(self):
|
def test_verfahrensgebiet_layer_existiert_in_non_qgis_mode_returns_input(self):
|
||||||
"""Prüft, dass außerhalb des QGIS-Modus bei bestehendem Verfahrensgebiet keine Rückfrage erfolgt."""
|
"""Prüft, dass außerhalb des QGIS-Modus bei bestehendem Verfahrensgebiet keine Rückfrage erfolgt."""
|
||||||
manager = Pruefmanager(ui_modus="test")
|
manager = Pruefmanager(ui_modus="test")
|
||||||
|
|||||||
Reference in New Issue
Block a user