"""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()