Anzeige für den Eigentümer und Korrekturen im Tab „Eigentümer“ (#56)
„Daten anzeigen“ in der Trefferliste zeigt Name, Anschrift und E-Mail der gewählten Einträge in einem eigenen Fenster: auf einem zweiten Bildschirm im Vollbild, auf dem QGIS-Bildschirm als normales Fenster. Die Sitzung wird von Hand begonnen und beendet; „Anzeige leeren“ entfernt die Daten, ohne das Fenster zu schließen. Konten, IDs und Kennzeichen erscheinen dort nicht, alle Werte werden maskiert. „Korrigieren …“ ändert angezeigte Daten. Die Eigentümerdatei bleibt unverändert; jede Änderung wird mit Zeitpunkt, Konto, Personen-ID, altem und neuem Wert an <Eigentümerdatei>_korrekturen.csv angehängt und sofort angezeigt. Ohne beschreibbaren Ordner wird nach dem Speicherort gefragt. In test_eigentuemer_tab.py sind die beim letzten Mal zusammengezogenen with-Zeilen wieder umbrochen; die Trefferliste bekommt jetzt den Anzeige-Rückruf, der Test dazu ist angepasst. Dritter und letzter Schritt von #56. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
"""Tests für das Anzeigefenster des Eigentümer-Datenabgleichs (#56).
|
||||
|
||||
Alle Namen und Anschriften sind erfunden.
|
||||
"""
|
||||
|
||||
import types
|
||||
import unittest
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from sn_verfahrensgebiet.ui import eigentuemer_anzeige as ea
|
||||
from sn_verfahrensgebiet.functions.eigentuemer_suche import Eigentuemer
|
||||
|
||||
ANNA = Eigentuemer(anrede="Frau", titel="Dr.", vorname="Anna", nachname="Beispiel", strasse="Hauptstraße 1",
|
||||
plz="01234", wohnort="Musterstadt", ortsteil="Unterdorf", emails=("anna@example.org",),
|
||||
verstorben=True, rechtsperson=("0",), per_ids=("4711",), konten=("98765",))
|
||||
|
||||
|
||||
def _rechteck(x, y, b, h):
|
||||
return types.SimpleNamespace(x=lambda: x, y=lambda: y, width=lambda: b, height=lambda: h)
|
||||
|
||||
|
||||
class TestAnzeigetext(unittest.TestCase):
|
||||
|
||||
def test_name_anschrift_email(self):
|
||||
"""Prüft, dass die Anzeige Name mit Anrede und Titel, die mehrzeilige Anschrift mit Ortsteil und die E-Mail zeigt."""
|
||||
text = ea.anzeige_html([ANNA])
|
||||
self.assertIn("Frau Dr. Anna Beispiel", text)
|
||||
self.assertIn("Hauptstraße 1<br/>01234 Musterstadt<br/>OT Unterdorf", text)
|
||||
self.assertIn("anna@example.org", text)
|
||||
|
||||
def test_keine_internen_angaben(self):
|
||||
"""Prüft, dass der Eigentümer weder Konten, Personen-IDs, Rechtsperson-Wert noch das Kennzeichen „verstorben“ sieht."""
|
||||
text = ea.anzeige_html([ANNA])
|
||||
for intern in ("98765", "4711", "verstorben", "Rechtsperson"):
|
||||
self.assertNotIn(intern, text)
|
||||
|
||||
def test_fehlende_email_wird_genannt(self):
|
||||
"""Prüft, dass eine fehlende E-Mail ausdrücklich als „keine angegeben“ erscheint."""
|
||||
self.assertIn(ea.OHNE_EMAIL, ea.anzeige_html([Eigentuemer(nachname="Muster")]))
|
||||
|
||||
def test_werte_werden_maskiert(self):
|
||||
"""Prüft, dass Sonderzeichen aus den Daten maskiert werden und kein HTML einschleusen."""
|
||||
text = ea.anzeige_html([Eigentuemer(nachname="<b>Muster</b> & Co")])
|
||||
self.assertIn("<b>Muster</b> & Co", text)
|
||||
self.assertNotIn("<b>Muster", text)
|
||||
|
||||
def test_mehrere_eintraege_getrennt(self):
|
||||
"""Prüft, dass mehrere Einträge untereinander, durch eine Linie getrennt, erscheinen."""
|
||||
text = ea.anzeige_html([ANNA, Eigentuemer(vorname="Bernd", nachname="Muster")])
|
||||
self.assertEqual(text.count("<hr/>"), 1)
|
||||
self.assertIn("Bernd Muster", text)
|
||||
|
||||
def test_leer_nur_platzhalter(self):
|
||||
"""Prüft, dass die leere Anzeige nur den neutralen Platzhalter zeigt."""
|
||||
text = ea.anzeige_html([])
|
||||
self.assertIn(ea.PLATZHALTER, text)
|
||||
self.assertNotIn("Name", text)
|
||||
|
||||
|
||||
class TestBildschirme(unittest.TestCase):
|
||||
|
||||
def test_vorgabe_ist_nicht_der_qgis_bildschirm(self):
|
||||
"""Prüft, dass als Vorgabe der erste Bildschirm ohne QGIS gewählt wird, bei nur einem Bildschirm dieser."""
|
||||
a, b, c = object(), object(), object()
|
||||
self.assertEqual(ea.standard_index([a, b, c], a), 1)
|
||||
self.assertEqual(ea.standard_index([a, b], b), 0)
|
||||
self.assertEqual(ea.standard_index([a], a), 0)
|
||||
self.assertEqual(ea.standard_index([], None), 0)
|
||||
|
||||
def test_bildschirm_text(self):
|
||||
"""Prüft, dass ein Bildschirm mit Nummer und Auflösung beschriftet wird."""
|
||||
bildschirm = types.SimpleNamespace(geometry=lambda: _rechteck(0, 0, 1920, 1080))
|
||||
self.assertEqual(ea.bildschirm_text(bildschirm, 2), "Bildschirm 2 (1920 × 1080)")
|
||||
self.assertEqual(ea.bildschirm_text(object(), 3), "Bildschirm 3")
|
||||
|
||||
def test_ohne_qt_anwendung_keine_bildschirme(self):
|
||||
"""Prüft, dass ohne Qt-Anwendung (Mock-Modus) eine leere Bildschirmliste kommt statt eines Fehlers."""
|
||||
self.assertEqual(ea.bildschirme(), [])
|
||||
|
||||
def test_vollbild_auf_gewaehltem_bildschirm(self):
|
||||
"""Prüft, dass das Fenster im Vollbild dem gewählten Bildschirm zugeordnet und auf dessen Fläche gelegt wird."""
|
||||
geo = _rechteck(1920, 0, 1920, 1080)
|
||||
bildschirm = types.SimpleNamespace(geometry=lambda: geo)
|
||||
fenster = MagicMock()
|
||||
ea.zeige_auf_bildschirm(fenster, bildschirm, vollbild=True)
|
||||
fenster.windowHandle.return_value.setScreen.assert_called_once_with(bildschirm)
|
||||
fenster.setGeometry.assert_called_once_with(geo)
|
||||
fenster.showFullScreen.assert_called_once()
|
||||
fenster.showNormal.assert_not_called()
|
||||
|
||||
def test_normales_fenster_mittig(self):
|
||||
"""Prüft, dass auf dem QGIS-Bildschirm ein normales Fenster mittig im freien Bereich öffnet."""
|
||||
bildschirm = types.SimpleNamespace(availableGeometry=lambda: _rechteck(0, 40, 1600, 1000))
|
||||
fenster = MagicMock()
|
||||
ea.zeige_auf_bildschirm(fenster, bildschirm, vollbild=False)
|
||||
fenster.setGeometry.assert_called_once_with(350, 215, 900, 650)
|
||||
fenster.showNormal.assert_called_once()
|
||||
fenster.showFullScreen.assert_not_called()
|
||||
|
||||
def test_ohne_bildschirm_einfach_zeigen(self):
|
||||
"""Prüft, dass ohne bekannten Bildschirm das Fenster einfach gezeigt wird."""
|
||||
fenster = MagicMock()
|
||||
ea.zeige_auf_bildschirm(fenster, None, vollbild=True)
|
||||
fenster.show.assert_called_once()
|
||||
fenster.showFullScreen.assert_not_called()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,134 @@
|
||||
"""Tests für die Korrekturen beim Eigentümer-Datenabgleich (#56).
|
||||
|
||||
Alle Namen und Anschriften sind erfunden.
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import csv
|
||||
import io
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
|
||||
from sn_verfahrensgebiet.functions import eigentuemer_korrektur as ek
|
||||
from sn_verfahrensgebiet.functions.eigentuemer_suche import Eigentuemer
|
||||
|
||||
ANNA = Eigentuemer(anrede="Frau", vorname="Anna", nachname="Beispiel", strasse="Hauptstraße 1", plz="01234",
|
||||
wohnort="Musterstadt", emails=("anna@example.org",), per_ids=("7",), konten=("100", "200"))
|
||||
|
||||
|
||||
class TestAenderungen(unittest.TestCase):
|
||||
|
||||
def test_feldwerte(self):
|
||||
"""Prüft, dass alle korrigierbaren Felder vorbelegt werden, mehrere E-Mails durch Komma getrennt."""
|
||||
werte = ek.feldwerte(Eigentuemer(nachname="Beispiel", emails=("a@example.org", "b@example.org")))
|
||||
self.assertEqual(set(werte), {k for k, _ in ek.FELDER})
|
||||
self.assertEqual(werte["email"], "a@example.org, b@example.org")
|
||||
self.assertEqual(werte["nachname"], "Beispiel")
|
||||
|
||||
def test_nur_echte_aenderungen(self):
|
||||
"""Prüft, dass nur geänderte Felder zählen und Leerraum am Rand oder doppelte Leerzeichen keine Änderung sind."""
|
||||
werte = ek.feldwerte(ANNA)
|
||||
werte.update(strasse=" Hauptstraße 1 ", wohnort="Neustadt", email="anna.neu@example.org")
|
||||
self.assertEqual(ek.ermittle_aenderungen(ANNA, werte), [
|
||||
("wohnort", "Musterstadt", "Neustadt"),
|
||||
("email", "anna@example.org", "anna.neu@example.org"),
|
||||
])
|
||||
self.assertEqual(ek.ermittle_aenderungen(ANNA, ek.feldwerte(ANNA)), [])
|
||||
|
||||
def test_feld_leeren_ist_aenderung(self):
|
||||
"""Prüft, dass das Leeren eines Feldes als Änderung zählt."""
|
||||
self.assertEqual(ek.ermittle_aenderungen(ANNA, {"email": ""}), [("email", "anna@example.org", "")])
|
||||
|
||||
def test_wende_an(self):
|
||||
"""Prüft, dass die Änderungen im angezeigten Eintrag landen und E-Mails wieder getrennt werden; Konten bleiben."""
|
||||
neu = ek.wende_an(ANNA, [("wohnort", "Musterstadt", "Neustadt"),
|
||||
("email", "", "a@example.org, b@example.org")])
|
||||
self.assertEqual(neu.wohnort, "Neustadt")
|
||||
self.assertEqual(neu.emails, ("a@example.org", "b@example.org"))
|
||||
self.assertEqual(neu.konten, ANNA.konten)
|
||||
self.assertEqual(ANNA.wohnort, "Musterstadt")
|
||||
|
||||
|
||||
class TestKorrekturPfad(unittest.TestCase):
|
||||
|
||||
def test_textdatei_provider(self):
|
||||
"""Prüft, dass aus der Quelle eines Textdatei-Layers (file-URI, Leerzeichen kodiert) der Pfad neben der CSV wird."""
|
||||
quelle = "file:///D:/Verfahren/Eigent%C3%BCmer%20Export.csv?type=csv&detectTypes=yes&geomType=none"
|
||||
self.assertEqual(ek.korrektur_pfad(quelle), "D:/Verfahren/Eigentümer Export_korrekturen.csv")
|
||||
self.assertEqual(ek.korrektur_pfad("file:///home/test/export.CSV?type=csv"),
|
||||
"/home/test/export_korrekturen.csv")
|
||||
|
||||
def test_ogr_provider(self):
|
||||
"""Prüft, dass auch eine OGR-Quelle mit Layernamen den Pfad neben der CSV ergibt."""
|
||||
self.assertEqual(ek.korrektur_pfad("D:/Verfahren/export.csv|layername=export"),
|
||||
"D:/Verfahren/export_korrekturen.csv")
|
||||
|
||||
def test_keine_csv(self):
|
||||
"""Prüft, dass für Quellen ohne CSV-Datei kein Pfad vorgeschlagen wird."""
|
||||
self.assertIsNone(ek.korrektur_pfad(""))
|
||||
self.assertIsNone(ek.korrektur_pfad("D:/Verfahren/daten.gpkg|layername=export"))
|
||||
self.assertIsNone(ek.korrektur_pfad("memory?geometry=None"))
|
||||
|
||||
|
||||
class TestSchreiben(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self._ordner = tempfile.TemporaryDirectory()
|
||||
self.pfad = os.path.join(self._ordner.name, "export_korrekturen.csv")
|
||||
|
||||
def tearDown(self):
|
||||
self._ordner.cleanup()
|
||||
|
||||
def _zeilen(self):
|
||||
with open(self.pfad, encoding="utf-8-sig", newline="") as datei:
|
||||
return list(csv.reader(datei))
|
||||
|
||||
def test_neue_datei_mit_kopfzeile_und_bom(self):
|
||||
"""Prüft, dass eine neue Korrektur-CSV mit BOM, Kopfzeile und je Änderung einer Zeile mit Exportspaltennamen entsteht."""
|
||||
anzahl = ek.schreibe_korrekturen(self.pfad, ANNA, [
|
||||
("strasse", "Hauptstraße 1", "Am Anger 3, Haus B"),
|
||||
("email", "anna@example.org", ""),
|
||||
], zeitpunkt=datetime(2026, 10, 6, 14, 30, 5))
|
||||
self.assertEqual(anzahl, 2)
|
||||
with open(self.pfad, "rb") as datei:
|
||||
self.assertTrue(datei.read().startswith(b"\xef\xbb\xbf"))
|
||||
self.assertEqual(self._zeilen(), [
|
||||
ek.KOPFZEILE,
|
||||
["2026-10-06 14:30:05", "100 200", "7", "STRASSE", "Hauptstraße 1", "Am Anger 3, Haus B"],
|
||||
["2026-10-06 14:30:05", "100 200", "7", "Email", "anna@example.org", ""],
|
||||
])
|
||||
|
||||
def test_anhaengen_ohne_zweite_kopfzeile(self):
|
||||
"""Prüft, dass weitere Korrekturen angehängt werden, ohne Kopfzeile oder BOM zu wiederholen."""
|
||||
ek.schreibe_korrekturen(self.pfad, ANNA, [("wohnort", "Musterstadt", "Neustadt")])
|
||||
ek.schreibe_korrekturen(self.pfad, ANNA, [("plz", "01234", "01235")])
|
||||
zeilen = self._zeilen()
|
||||
self.assertEqual(len(zeilen), 3)
|
||||
self.assertEqual(zeilen[0], ek.KOPFZEILE)
|
||||
self.assertEqual([z[3] for z in zeilen[1:]], ["WOHNORT", "PLZ"])
|
||||
with open(self.pfad, "rb") as datei:
|
||||
self.assertEqual(datei.read().count(b"\xef\xbb\xbf"), 1)
|
||||
|
||||
def test_ohne_aenderung_keine_datei(self):
|
||||
"""Prüft, dass ohne Änderung keine Datei angelegt wird."""
|
||||
self.assertEqual(ek.schreibe_korrekturen(self.pfad, ANNA, []), 0)
|
||||
self.assertFalse(os.path.exists(self.pfad))
|
||||
|
||||
def test_schreibfehler_wird_weitergereicht(self):
|
||||
"""Prüft, dass ein nicht schreibbarer Pfad als OSError beim Aufrufer ankommt."""
|
||||
with self.assertRaises(OSError):
|
||||
ek.schreibe_korrekturen(os.path.join(self._ordner.name, "fehlt", "x.csv"), ANNA,
|
||||
[("wohnort", "Musterstadt", "Neustadt")])
|
||||
|
||||
def test_schreibt_nichts_auf_die_konsole(self):
|
||||
"""Prüft, dass das Festhalten von Korrekturen keine Personendaten auf die Konsole schreibt (Datenschutz)."""
|
||||
ausgabe = io.StringIO()
|
||||
with contextlib.redirect_stdout(ausgabe), contextlib.redirect_stderr(ausgabe):
|
||||
ek.schreibe_korrekturen(self.pfad, ANNA, [("wohnort", "Musterstadt", "Neustadt")])
|
||||
self.assertEqual(ausgabe.getvalue(), "")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,11 +1,14 @@
|
||||
"""Tests für den Tab „Eigentümer“ – Flurstückssuche und Eigentümersuche (#56).
|
||||
"""Tests für den Tab „Eigentümer“ – Flurstückssuche, Eigentümersuche, Anzeige und Korrektur (#56).
|
||||
|
||||
Alle Namen und Anschriften sind erfunden.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import types
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
if "qgis" not in sys.modules:
|
||||
@@ -114,7 +117,8 @@ class TestEigentuemerSuchen(unittest.TestCase):
|
||||
def test_ohne_eigentuemerdatei_warnung(self, warnung, hinweis):
|
||||
"""Prüft, dass ohne gewählte Eigentümerdatei die Liste aufgefrischt und dann auf das Laden der CSV hingewiesen wird."""
|
||||
tab = _eig_tab(layer_id=None)
|
||||
with patch.object(et, "hole_layer", return_value=None), patch.object(et, "suche_eigentuemer") as suche:
|
||||
with patch.object(et, "hole_layer", return_value=None), \
|
||||
patch.object(et, "suche_eigentuemer") as suche:
|
||||
tab._on_eigentuemer_suchen()
|
||||
tab.eigentuemer_layer_combo.aktualisieren.assert_called_once()
|
||||
warnung.assert_called_once()
|
||||
@@ -123,31 +127,37 @@ class TestEigentuemerSuchen(unittest.TestCase):
|
||||
|
||||
def test_suchfehler_als_warnung(self, warnung, hinweis):
|
||||
"""Prüft, dass ein Fehler der Suche (z. B. leere Eingabe) als Warnung erscheint."""
|
||||
with patch.object(et, "hole_layer", return_value=object()), patch.object(et, "suche_eigentuemer", side_effect=EigentuemerSucheFehler("Bitte eingeben")):
|
||||
with patch.object(et, "hole_layer", return_value=object()), \
|
||||
patch.object(et, "suche_eigentuemer", side_effect=EigentuemerSucheFehler("Bitte eingeben")):
|
||||
_eig_tab()._on_eigentuemer_suchen()
|
||||
warnung.assert_called_once_with("Eigentümersuche", "Bitte eingeben")
|
||||
|
||||
def test_kein_treffer_ohne_namen_in_meldung(self, warnung, hinweis):
|
||||
"""Prüft, dass ohne Treffer ein Hinweis erscheint, der den gesuchten Namen nicht wiederholt (Datenschutz)."""
|
||||
with patch.object(et, "hole_layer", return_value=object()), patch.object(et, "suche_eigentuemer", return_value=[]), patch.object(et, "EigentuemerTrefferFenster") as fenster:
|
||||
with patch.object(et, "hole_layer", return_value=object()), \
|
||||
patch.object(et, "suche_eigentuemer", return_value=[]), \
|
||||
patch.object(et, "EigentuemerTrefferFenster") as fenster:
|
||||
_eig_tab(nachname="Beispiel")._on_eigentuemer_suchen()
|
||||
hinweis.assert_called_once()
|
||||
self.assertNotIn("Beispiel", hinweis.call_args[0][1])
|
||||
fenster.assert_not_called()
|
||||
|
||||
def test_treffer_oeffnen_trefferliste_und_ersetzen_die_alte(self, warnung, hinweis):
|
||||
"""Prüft, dass Treffer in einer neuen Trefferliste erscheinen, eine offene alte Liste geschlossen wird und die Eingaben an die Suche gehen."""
|
||||
layer = object()
|
||||
"""Prüft, dass Treffer in einer neuen Trefferliste mit Anzeige-Rückruf erscheinen, eine offene alte Liste geschlossen wird und die Eingaben an die Suche gehen."""
|
||||
layer = MagicMock()
|
||||
layer.id.return_value = "csv1"
|
||||
tab = _eig_tab(nachname="Beisp", vorname="An")
|
||||
altes_fenster = MagicMock()
|
||||
tab._trefferfenster = altes_fenster
|
||||
with patch.object(et, "hole_layer", return_value=layer), patch.object(et, "suche_eigentuemer", return_value=[_ANNA]) as suche, patch.object(et, "EigentuemerTrefferFenster") as fenster:
|
||||
with patch.object(et, "hole_layer", return_value=layer), \
|
||||
patch.object(et, "suche_eigentuemer", return_value=[_ANNA]) as suche, patch.object(et, "EigentuemerTrefferFenster") as fenster:
|
||||
tab._on_eigentuemer_suchen()
|
||||
suche.assert_called_once_with(layer, "Beisp", "An")
|
||||
altes_fenster.close.assert_called_once()
|
||||
fenster.assert_called_once_with([_ANNA], tab)
|
||||
fenster.assert_called_once_with([_ANNA], tab, beim_anzeigen=tab._daten_anzeigen)
|
||||
fenster.return_value.show.assert_called_once()
|
||||
self.assertIs(tab._trefferfenster, fenster.return_value)
|
||||
self.assertEqual(tab._treffer_layer_id, "csv1")
|
||||
|
||||
|
||||
class TestTrefferliste(unittest.TestCase):
|
||||
@@ -169,6 +179,201 @@ class TestTrefferliste(unittest.TestCase):
|
||||
self.assertEqual(fenster.ausgewaehlte_eigentuemer(), [bernd])
|
||||
indizes.assert_called_once_with(fenster.tabelle)
|
||||
|
||||
def test_daten_anzeigen_gibt_auswahl_weiter(self):
|
||||
"""Prüft, dass „Daten anzeigen“ die Auswahl der Trefferliste an den Tab übergibt."""
|
||||
fenster = EigentuemerTrefferFenster.__new__(EigentuemerTrefferFenster)
|
||||
fenster.treffer = [_ANNA]
|
||||
fenster.tabelle = object()
|
||||
fenster.beim_anzeigen = MagicMock()
|
||||
with patch.object(treffer_modul, "ausgewaehlte_indizes", return_value=[0]):
|
||||
fenster._on_anzeigen()
|
||||
fenster.beim_anzeigen.assert_called_once_with([_ANNA])
|
||||
|
||||
|
||||
def _anz_tab(angezeigt=None, anzeige=None, layer_id="csv1"):
|
||||
tab = EigentuemerTab.__new__(EigentuemerTab)
|
||||
for name in ("btn_sitzung", "btn_anzeige_leeren", "btn_korrigieren", "sitzung_label", "bildschirm_combo"):
|
||||
setattr(tab, name, MagicMock())
|
||||
tab._anzeige = anzeige
|
||||
tab._sitzung_seit = datetime(2026, 10, 6, 9, 5) if anzeige is not None else None
|
||||
tab._angezeigt = list(angezeigt or [])
|
||||
tab._treffer_layer_id = layer_id
|
||||
tab._anzeige_layer_id = layer_id
|
||||
tab._korrektur_pfade = {}
|
||||
return tab
|
||||
|
||||
|
||||
_BERND = Eigentuemer(vorname="Bernd", nachname="Muster", konten=("300",), per_ids=("8",))
|
||||
|
||||
|
||||
@patch.object(et, "zeige_auf_bildschirm")
|
||||
@patch.object(et, "qgis_bildschirm")
|
||||
@patch.object(et, "EigentuemerAnzeige")
|
||||
class TestSitzung(unittest.TestCase):
|
||||
|
||||
def test_sitzung_beginnen_im_vollbild_auf_zweitem_bildschirm(self, anzeige_klasse, qgis, zeige):
|
||||
"""Prüft, dass „Sitzung beginnen“ ein leeres Anzeigefenster auf dem gewählten Bildschirm öffnet, im Vollbild, wenn QGIS woanders liegt."""
|
||||
tab = _anz_tab()
|
||||
zweiter = object()
|
||||
tab.bildschirm_combo.gewaehlter_bildschirm.return_value = zweiter
|
||||
qgis.return_value = object()
|
||||
tab._on_sitzung_umschalten()
|
||||
anzeige_klasse.assert_called_once_with(tab)
|
||||
zeige.assert_called_once_with(anzeige_klasse.return_value, zweiter, True)
|
||||
self.assertIs(tab._anzeige, anzeige_klasse.return_value)
|
||||
self.assertEqual(tab._angezeigt, [])
|
||||
tab.btn_sitzung.setText.assert_called_with("Sitzung beenden")
|
||||
self.assertIn("leer", tab.sitzung_label.setText.call_args[0][0])
|
||||
|
||||
def test_auf_qgis_bildschirm_kein_vollbild(self, anzeige_klasse, qgis, zeige):
|
||||
"""Prüft, dass das Anzeigefenster auf dem QGIS-Bildschirm als normales Fenster öffnet."""
|
||||
tab = _anz_tab()
|
||||
einziger = object()
|
||||
tab.bildschirm_combo.gewaehlter_bildschirm.return_value = einziger
|
||||
qgis.return_value = einziger
|
||||
tab._sitzung_beginnen()
|
||||
self.assertFalse(zeige.call_args[0][2])
|
||||
|
||||
def test_daten_anzeigen_beginnt_sitzung_bei_bedarf(self, anzeige_klasse, qgis, zeige):
|
||||
"""Prüft, dass „Daten anzeigen“ ohne laufende Sitzung diese beginnt und die Einträge zeigt; die Statuszeile nennt nur die Anzahl."""
|
||||
tab = _anz_tab()
|
||||
tab._daten_anzeigen([_ANNA, _BERND])
|
||||
anzeige_klasse.return_value.zeige.assert_called_once_with([_ANNA, _BERND])
|
||||
self.assertEqual(tab._angezeigt, [_ANNA, _BERND])
|
||||
status = tab.sitzung_label.setText.call_args[0][0]
|
||||
self.assertIn("2 Einträge", status)
|
||||
self.assertNotIn("Beispiel", status)
|
||||
|
||||
def test_daten_anzeigen_ohne_auswahl(self, anzeige_klasse, qgis, zeige):
|
||||
"""Prüft, dass „Daten anzeigen“ ohne Auswahl nur einen Hinweis gibt und keine Sitzung beginnt."""
|
||||
tab = _anz_tab()
|
||||
with patch.object(et, "warning") as warnung:
|
||||
tab._daten_anzeigen([])
|
||||
warnung.assert_called_once()
|
||||
anzeige_klasse.assert_not_called()
|
||||
|
||||
def test_anzeige_leeren_laesst_fenster_offen(self, anzeige_klasse, qgis, zeige):
|
||||
"""Prüft, dass „Anzeige leeren“ die Daten entfernt, das Fenster aber nicht schließt."""
|
||||
anzeige = MagicMock()
|
||||
tab = _anz_tab(angezeigt=[_ANNA], anzeige=anzeige)
|
||||
tab._on_anzeige_leeren()
|
||||
anzeige.leeren.assert_called_once()
|
||||
anzeige.close.assert_not_called()
|
||||
self.assertEqual(tab._angezeigt, [])
|
||||
self.assertIs(tab._anzeige, anzeige)
|
||||
tab.btn_anzeige_leeren.setEnabled.assert_called_with(False)
|
||||
|
||||
def test_sitzung_beenden_schliesst_fenster(self, anzeige_klasse, qgis, zeige):
|
||||
"""Prüft, dass „Sitzung beenden“ das Anzeigefenster schließt und die angezeigten Daten vergisst."""
|
||||
anzeige = MagicMock()
|
||||
tab = _anz_tab(angezeigt=[_ANNA], anzeige=anzeige)
|
||||
tab._on_sitzung_umschalten()
|
||||
anzeige.close.assert_called_once()
|
||||
self.assertIsNone(anzeige.beim_schliessen)
|
||||
self.assertIsNone(tab._anzeige)
|
||||
self.assertEqual(tab._angezeigt, [])
|
||||
tab.btn_sitzung.setText.assert_called_with("Sitzung beginnen")
|
||||
|
||||
def test_von_aussen_geschlossen_beendet_sitzung(self, anzeige_klasse, qgis, zeige):
|
||||
"""Prüft, dass ein von außen geschlossenes Anzeigefenster (z. B. Alt+F4) die Sitzung im Tab beendet."""
|
||||
tab = _anz_tab()
|
||||
tab._sitzung_beginnen()
|
||||
rueckruf = anzeige_klasse.return_value.beim_schliessen
|
||||
rueckruf()
|
||||
self.assertIsNone(tab._anzeige)
|
||||
|
||||
|
||||
@patch.object(et, "success")
|
||||
@patch.object(et, "info")
|
||||
@patch.object(et, "warning")
|
||||
class TestKorrektur(unittest.TestCase):
|
||||
|
||||
def test_korrektur_wird_festgehalten_und_angezeigt(self, warnung, hinweis, erfolg):
|
||||
"""Prüft, dass eine Korrektur in die Korrektur-CSV geschrieben, sofort angezeigt und ohne Namen gemeldet wird."""
|
||||
anzeige = MagicMock()
|
||||
tab = _anz_tab(angezeigt=[_ANNA, _BERND], anzeige=anzeige)
|
||||
tab._korrektur_pfade["csv1"] = "D:/Verfahren/export_korrekturen.csv"
|
||||
with patch.object(et, "frage_korrektur", return_value=(1, {"wohnort": "Neustadt"})), \
|
||||
patch.object(et, "schreibe_korrekturen") as schreiben:
|
||||
tab._on_korrigieren()
|
||||
schreiben.assert_called_once_with("D:/Verfahren/export_korrekturen.csv", _BERND,
|
||||
[("wohnort", "", "Neustadt")])
|
||||
self.assertEqual(tab._angezeigt[1].wohnort, "Neustadt")
|
||||
self.assertIs(tab._angezeigt[0], _ANNA)
|
||||
anzeige.zeige.assert_called_once_with(tab._angezeigt)
|
||||
meldung = erfolg.call_args[0][1]
|
||||
self.assertIn("export_korrekturen.csv", meldung)
|
||||
self.assertNotIn("Muster", meldung)
|
||||
|
||||
def test_ohne_aenderung_nichts_schreiben(self, warnung, hinweis, erfolg):
|
||||
"""Prüft, dass ein unverändert gespeicherter Dialog nichts schreibt."""
|
||||
tab = _anz_tab(angezeigt=[_ANNA], anzeige=MagicMock())
|
||||
with patch.object(et, "frage_korrektur", return_value=(0, {"nachname": "Beispiel"})), \
|
||||
patch.object(et, "schreibe_korrekturen") as schreiben:
|
||||
tab._on_korrigieren()
|
||||
schreiben.assert_not_called()
|
||||
hinweis.assert_called_once()
|
||||
|
||||
def test_abbruch_nichts_schreiben(self, warnung, hinweis, erfolg):
|
||||
"""Prüft, dass ein abgebrochener Korrekturdialog nichts schreibt."""
|
||||
tab = _anz_tab(angezeigt=[_ANNA], anzeige=MagicMock())
|
||||
with patch.object(et, "frage_korrektur", return_value=None), \
|
||||
patch.object(et, "schreibe_korrekturen") as schreiben:
|
||||
tab._on_korrigieren()
|
||||
schreiben.assert_not_called()
|
||||
|
||||
def test_schreibfehler_warnung_und_anzeige_unveraendert(self, warnung, hinweis, erfolg):
|
||||
"""Prüft, dass bei einem Schreibfehler gewarnt, die Anzeige nicht geändert und der Pfad beim nächsten Mal neu bestimmt wird."""
|
||||
anzeige = MagicMock()
|
||||
tab = _anz_tab(angezeigt=[_ANNA], anzeige=anzeige)
|
||||
tab._korrektur_pfade["csv1"] = "X:/gesperrt.csv"
|
||||
with patch.object(et, "frage_korrektur", return_value=(0, {"wohnort": "Neustadt"})), \
|
||||
patch.object(et, "schreibe_korrekturen", side_effect=PermissionError(13, "Zugriff verweigert")):
|
||||
tab._on_korrigieren()
|
||||
warnung.assert_called_once()
|
||||
anzeige.zeige.assert_not_called()
|
||||
self.assertIs(tab._angezeigt[0], _ANNA)
|
||||
self.assertNotIn("csv1", tab._korrektur_pfade)
|
||||
|
||||
def test_ohne_anzeige_keine_korrektur(self, warnung, hinweis, erfolg):
|
||||
"""Prüft, dass ohne angezeigte Daten kein Korrekturdialog erscheint."""
|
||||
tab = _anz_tab()
|
||||
with patch.object(et, "frage_korrektur") as dialog:
|
||||
tab._on_korrigieren()
|
||||
dialog.assert_not_called()
|
||||
warnung.assert_called_once()
|
||||
|
||||
|
||||
class TestKorrekturDatei(unittest.TestCase):
|
||||
|
||||
def test_vorschlag_neben_der_eigentuemerdatei(self):
|
||||
"""Prüft, dass die Korrektur-CSV ohne Rückfrage neben der Eigentümerdatei liegt, wenn der Ordner beschreibbar ist, und gemerkt wird."""
|
||||
with tempfile.TemporaryDirectory() as ordner:
|
||||
quelle = "file:///" + os.path.join(ordner, "export.csv").replace(os.sep, "/") + "?type=csv"
|
||||
layer = types.SimpleNamespace(source=lambda: quelle)
|
||||
tab = _anz_tab()
|
||||
with patch.object(et, "hole_layer", return_value=layer), \
|
||||
patch.object(et.QFileDialog, "getSaveFileName") as dialog:
|
||||
pfad = tab._korrektur_datei()
|
||||
dialog.assert_not_called()
|
||||
self.assertEqual(os.path.normcase(os.path.normpath(pfad)),
|
||||
os.path.normcase(os.path.join(ordner, "export_korrekturen.csv")))
|
||||
self.assertEqual(tab._korrektur_pfade["csv1"], pfad)
|
||||
|
||||
def test_ohne_csv_quelle_wird_gefragt(self):
|
||||
"""Prüft, dass ohne verwertbare CSV-Quelle nach dem Speicherort gefragt wird; Abbrechen liefert keinen Pfad."""
|
||||
tab = _anz_tab()
|
||||
layer = types.SimpleNamespace(source=lambda: "memory?geometry=None")
|
||||
with patch.object(et, "hole_layer", return_value=layer), \
|
||||
patch.object(et.QFileDialog, "getSaveFileName", return_value=("", "")) as dialog:
|
||||
self.assertIsNone(tab._korrektur_datei())
|
||||
dialog.assert_called_once()
|
||||
self.assertEqual(tab._korrektur_pfade, {})
|
||||
|
||||
with patch.object(et, "hole_layer", return_value=layer), \
|
||||
patch.object(et.QFileDialog, "getSaveFileName", return_value=("D:/k.csv", "")):
|
||||
self.assertEqual(tab._korrektur_datei(), "D:/k.csv")
|
||||
|
||||
|
||||
class TestDockWidgetTabs(unittest.TestCase):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user