test überarbeitet

This commit is contained in:
2026-04-20 15:36:29 +02:00
parent 8f9cf4c9fb
commit 600a3c7aff
3 changed files with 261 additions and 110 deletions
+84
View File
@@ -0,0 +1,84 @@
import unittest
from typing import Any
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
from sn_plan41.modules.listenauswerter import Listenauswerter
class _DummyPruefmanager:
def __init__(self) -> None:
self.seen = []
def verarbeite(self, ergebnis: Any):
self.seen.append(ergebnis)
return ergebnis
class _DummyStilPruefer:
def __init__(self, ok: bool = True) -> None:
self.ok = ok
def pruefe(self, _path: str) -> pruef_ergebnis:
if self.ok:
return pruef_ergebnis(ok=True, meldung="ok", aktion="ok", kontext=None)
return pruef_ergebnis(ok=False, meldung="stil fehler", aktion="falsche_endung", kontext=None)
class TestListenauswerter(unittest.TestCase):
def test_validate_rows_accepts_valid_row_and_normalizes_provider(self):
manager = _DummyPruefmanager()
validator = Listenauswerter(manager, _DummyStilPruefer(ok=True))
data = {"rows": [{"ident": "A1", "Inhalt": "Test", "Link": "http://x", "Provider": "wfs"}]}
validated, processed = validator.validate_rows(data)
self.assertEqual(len(validated["rows"]), 1)
self.assertEqual(validated["rows"][0]["Provider"], "WFS")
self.assertEqual(processed, [])
def test_validate_rows_rejects_missing_required_fields(self):
manager = _DummyPruefmanager()
validator = Listenauswerter(manager, _DummyStilPruefer(ok=True))
data = {"rows": [{"ident": "", "Link": "", "Provider": ""}]}
validated, processed = validator.validate_rows(data)
self.assertEqual(validated["rows"], [])
self.assertEqual(len(processed), 1)
self.assertEqual(processed[0].aktion, "pflichtfelder_fehlen")
def test_validate_rows_sets_stildatei_none_on_style_failure(self):
manager = _DummyPruefmanager()
validator = Listenauswerter(manager, _DummyStilPruefer(ok=False))
data = {
"rows": [
{
"ident": "A1",
"Inhalt": "Thema",
"Link": "http://x",
"Provider": "WMS",
"Stildatei": "broken.qml",
}
]
}
validated, processed = validator.validate_rows(data)
self.assertEqual(len(validated["rows"]), 1)
self.assertIsNone(validated["rows"][0]["stildatei"])
self.assertEqual(len(processed), 1)
self.assertEqual(processed[0].aktion, "falsche_endung")
def test_validate_rows_handles_non_mapping_rows_edge_case(self):
manager = _DummyPruefmanager()
validator = Listenauswerter(manager, _DummyStilPruefer(ok=True))
validated, processed = validator.validate_rows({"rows": ["invalid", 123, None]})
self.assertEqual(validated, {"rows": []})
self.assertEqual(processed, [])
if __name__ == "__main__":
unittest.main()
+83 -73
View File
@@ -1,99 +1,109 @@
import unittest import unittest
from pathlib import Path from pathlib import Path
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
from typing import Any, cast
from unittest.mock import patch
from sn_plan41.ui.tab_a_logic import TabALogic # type: ignore from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
from sn_basis.functions.variable_wrapper import get_variable # type: ignore from sn_basis.functions.variable_wrapper import get_variable
from sn_basis.functions.sys_wrapper import file_exists # type: ignore from sn_plan41.ui.tab_a_logic import TabALogic
class _DummyPruefmanager:
def verarbeite(self, ergebnis):
return ergebnis
class TestTabALogic(unittest.TestCase): class TestTabALogic(unittest.TestCase):
# ----------------------------------------------------- def _make_logic(self) -> TabALogic:
# 1. Verfahrens-DB setzen und laden return TabALogic(
# ----------------------------------------------------- pruefmanager=cast(Any, _DummyPruefmanager()),
def test_verfahrens_db_set_and_load(self): link_pruefer=cast(Any, object()),
logic = TabALogic() stil_pruefer=cast(Any, object()),
)
with TemporaryDirectory() as tmp: def test_verfahrens_db_set_and_load_roundtrip(self):
db_path = Path(tmp) / "test.gpkg" logic = self._make_logic()
db_path.write_text("") logic.set_verfahrens_db("C:/tmp/v_db.gpkg")
self.assertEqual(logic.load_verfahrens_db(), "C:/tmp/v_db.gpkg")
logic.set_verfahrens_db(str(db_path))
stored = get_variable("verfahrens_db", scope="project")
self.assertEqual(stored, str(db_path))
loaded = logic.load_verfahrens_db()
self.assertEqual(loaded, str(db_path))
# -----------------------------------------------------
# 2. Verfahrens-DB löschen
# -----------------------------------------------------
def test_verfahrens_db_clear(self): def test_verfahrens_db_clear(self):
logic = TabALogic() logic = self._make_logic()
logic.set_verfahrens_db(None) logic.set_verfahrens_db(None)
self.assertEqual(get_variable("verfahrens_db", scope="project"), "")
stored = get_variable("verfahrens_db", scope="project") def test_linkliste_set_and_load_existing_path(self):
self.assertEqual(stored, "") logic = self._make_logic()
# -----------------------------------------------------
# 3. Neue Verfahrens-DB anlegen
# -----------------------------------------------------
def test_create_new_verfahrens_db(self):
logic = TabALogic()
with TemporaryDirectory() as tmp:
db_path = Path(tmp) / "neu.gpkg"
result = logic.create_new_verfahrens_db(str(db_path))
self.assertTrue(result)
self.assertTrue(file_exists(db_path))
stored = get_variable("verfahrens_db", scope="project")
self.assertEqual(stored, str(db_path))
def test_create_new_verfahrens_db_with_none_path(self):
logic = TabALogic()
result = logic.create_new_verfahrens_db(None)
self.assertFalse(result)
# -----------------------------------------------------
# 4. Linkliste setzen und laden
# -----------------------------------------------------
def test_linkliste_set_and_load(self):
logic = TabALogic()
with TemporaryDirectory() as tmp: with TemporaryDirectory() as tmp:
link_path = Path(tmp) / "links.xlsx" link_path = Path(tmp) / "links.xlsx"
link_path.write_text("dummy") link_path.write_text("x", encoding="utf-8")
logic.set_linkliste(str(link_path)) logic.set_linkliste(str(link_path))
self.assertEqual(logic.load_linkliste(), str(link_path))
stored = get_variable("linkliste", scope="project") def test_linkliste_load_returns_none_when_file_missing(self):
self.assertEqual(stored, str(link_path)) logic = self._make_logic()
logic.set_linkliste("C:/tmp/missing.xlsx")
self.assertIsNone(logic.load_linkliste())
loaded = logic.load_linkliste() @patch("sn_plan41.ui.tab_a_logic.get_plugin_root", return_value="C:/plugins")
self.assertEqual(loaded, str(link_path)) @patch("sn_plan41.ui.tab_a_logic.join_path", return_value="C:/plugins/sn_plan41/assets/Linkliste.xlsx")
def test_resolve_linkliste_empty_uses_standard_and_persists(self, _mock_join_path, _mock_root):
logic = self._make_logic()
resolved = logic._resolve_linkliste(None)
# ----------------------------------------------------- self.assertEqual(resolved, "C:/plugins/sn_plan41/assets/Linkliste.xlsx")
# 5. Linkliste löschen self.assertEqual(get_variable("linkliste", scope="project"), "C:/plugins/sn_plan41/assets/Linkliste.xlsx")
# -----------------------------------------------------
def test_linkliste_clear(self):
logic = TabALogic()
logic.set_linkliste(None) @patch("sn_plan41.ui.tab_a_logic.Dateipruefer")
def test_resolve_linkliste_returns_none_when_pruefmanager_rejects(self, mock_dateipruefer):
logic = self._make_logic()
mock_dateipruefer.return_value.pruefe.return_value = pruef_ergebnis(
ok=False,
meldung="Ungültig",
aktion="datei_nicht_gefunden",
kontext=None,
)
stored = get_variable("linkliste", scope="project") resolved = logic._resolve_linkliste("C:/tmp/missing.xlsx")
self.assertEqual(stored, "") self.assertIsNone(resolved)
# ----------------------------------------------------- def test_resolve_raumfilter_without_filter_returns_none(self):
# 6. Layer-ID speichern logic = self._make_logic()
# ----------------------------------------------------- result = logic._resolve_raumfilter("ohne", "C:/tmp/v_db.gpkg")
self.assertIsNone(result)
@patch.object(TabALogic, "_get_verfahrensgebiet_layer", return_value="layer_obj")
def test_resolve_raumfilter_verfahrensgebiet_uses_layer_lookup(self, mock_get_layer):
logic = self._make_logic()
result = logic._resolve_raumfilter("Verfahrensgebiet", "C:/tmp/v_db.gpkg")
self.assertEqual(result, "layer_obj")
mock_get_layer.assert_called_once()
def test_extract_datenabruf_error_maps_merges_reason_maps(self):
logic = self._make_logic()
results = [
pruef_ergebnis(
ok=False,
meldung="Head failed",
aktion="url_nicht_erreichbar",
kontext={"ident": "A1", "thema": "Dienst A", "error": "Timeout"},
),
pruef_ergebnis(
ok=False,
meldung="Fetch failed",
aktion="datenabruf",
kontext={"fehler": {"Dienst B": "HTTP 500", "Dienst A": "ignored"}},
),
]
merged = logic._extract_datenabruf_error_maps(results)
self.assertEqual(merged["by_ident"]["A1"], "Timeout")
self.assertEqual(merged["by_thema"]["Dienst A"], "Timeout")
self.assertEqual(merged["by_thema"]["Dienst B"], "HTTP 500")
if __name__ == "__main__": if __name__ == "__main__":
+94 -37
View File
@@ -1,56 +1,113 @@
"""
Smoke-Tests für TabA UI (sn_plan41/ui/tab_a_ui.py)
Ziel:
- UI kann erstellt werden
- Callbacks crashen nicht
- Keine Qt-Verhaltensprüfung
"""
import unittest import unittest
from unittest.mock import patch
from sn_plan41.ui.tab_a_ui import TabA #type:ignore from sn_basis.functions.variable_wrapper import get_variable
from sn_plan41.ui.tab_a_ui import TabA
class _DummySignal:
def connect(self, _callback):
return None
class _DummyToggleButton:
def __init__(self):
self.arrow = None
self.style = ""
def setArrowType(self, arrow):
self.arrow = arrow
def setStyleSheet(self, style):
self.style = style
class _DummyWidget:
def __init__(self):
self.visible = None
def setVisible(self, visible):
self.visible = visible
class _DummyFileWidget:
def __init__(self):
self._path = ""
def setFilePath(self, path):
self._path = path
def filePath(self):
return self._path
class _DummyCombo:
def __init__(self, value="Pufferlayer"):
self._value = value
def currentText(self):
return self._value
class _DummyPruefmanager:
pass
class _DummyDataGrabber:
pass
class TestTabAUI(unittest.TestCase): class TestTabAUI(unittest.TestCase):
# ----------------------------------------------------- def _make_tab_without_qt(self):
# 1. UI kann erstellt werden tab = TabA.__new__(TabA)
# ----------------------------------------------------- tab.group_button = _DummyToggleButton()
def test_tab_a_ui_can_be_created(self): tab.group_content = _DummyWidget()
tab = TabA(parent=None,build_ui=False) tab.optional_button = _DummyToggleButton()
tab.optional_content = _DummyWidget()
tab.file_widget = _DummyFileWidget()
tab.linkliste_widget = _DummyFileWidget()
tab._raumfilter_combo = _DummyCombo("ohne")
tab.verfahrens_db = None
tab.lokale_linkliste = None
tab.logic = None
tab.pruefmanager = None
tab.data_grabber = None
return tab
self.assertIsNotNone(tab) def test_tab_a_callbacks_can_be_called_without_crash(self):
self.assertEqual(tab.tab_title, "Daten") tab = self._make_tab_without_qt()
# -----------------------------------------------------
# 2. Toggle-Callbacks crashen nicht
# -----------------------------------------------------
def test_tab_a_toggle_callbacks_do_not_crash(self):
tab = TabA(parent=None,build_ui=False)
tab._toggle_group(True) tab._toggle_group(True)
tab._toggle_group(False) tab._toggle_group(False)
tab._toggle_optional(True) tab._toggle_optional(True)
tab._toggle_optional(False) tab._toggle_optional(False)
# -----------------------------------------------------
# 3. Datei-Callbacks akzeptieren leere Eingaben
# -----------------------------------------------------
def test_tab_a_file_callbacks_accept_empty_input(self):
tab = TabA(parent=None,build_ui=False)
tab._on_verfahrens_db_changed("") tab._on_verfahrens_db_changed("")
tab._on_linkliste_changed("") tab._on_linkliste_changed("")
# ----------------------------------------------------- self.assertEqual(tab.group_content.visible, False)
# 4. Layer-Callback akzeptiert None self.assertEqual(tab.optional_content.visible, False)
# -----------------------------------------------------
def test_tab_a_layer_callback_accepts_none(self):
tab = TabA(parent=None,build_ui=False)
tab._on_layer_changed(None) def test_tab_a_raumfilter_callback_persists_value(self):
tab = self._make_tab_without_qt()
tab._on_raumfilter_changed("ohne")
self.assertEqual(get_variable("Raumfilter", scope="project"), "ohne")
@patch("sn_plan41.ui.tab_a_ui.TabALogic")
def test_set_services_initializes_logic_and_injects_data_grabber(self, mock_logic_cls):
tab = self._make_tab_without_qt()
mock_logic = mock_logic_cls.return_value
pm = _DummyPruefmanager()
dg = _DummyDataGrabber()
tab.set_services(pruefmanager=pm, data_grabber=dg)
self.assertIs(tab.pruefmanager, pm)
self.assertIs(tab.data_grabber, dg)
self.assertIs(tab.logic, mock_logic)
self.assertIs(tab.logic.data_grabber, dg)
if __name__ == "__main__": if __name__ == "__main__":