„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>
135 lines
6.4 KiB
Python
135 lines
6.4 KiB
Python
"""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()
|