Eigentümerdatei (CSV-Layer mit Nachname/Vorname) wählen, Name und/oder Vorname eingeben: Eine nicht modale Trefferliste zeigt Name, Anschrift, E-Mail und die Kennzeichen „verstorben“/„Rechtsperson“. Gleiche Personen mit gleicher Anschrift werden zusammengefasst; Groß-/Kleinschreibung und Umlautschreibweise spielen keine Rolle. Von QGIS als Zahl gelesene PLZ bekommen ihre führende Null zurück. Meldungen enthalten keine Namen. Die Auswahltabelle aus „Verfahrensgebiete aus Landkreis laden“ ist als baue_auswahltabelle herausgelöst und wird von der Trefferliste mitgenutzt. Zweiter Schritt von #56; Anzeigefenster und Korrekturen folgen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
187 lines
8.6 KiB
Python
187 lines
8.6 KiB
Python
"""Tests für die Eigentümersuche im Serienbriefexport (#56).
|
||
|
||
Alle Namen und Anschriften sind erfunden.
|
||
"""
|
||
|
||
import contextlib
|
||
import io
|
||
import types
|
||
import unittest
|
||
from unittest.mock import patch
|
||
|
||
from sn_verfahrensgebiet.functions import eigentuemer_suche as es
|
||
from sn_verfahrensgebiet.functions.eigentuemer_suche import Eigentuemer
|
||
|
||
KOPF = [
|
||
"EIG_KONTO", "PER_ID", "PER_VERSTORBEN", "PER_NR", "Anrede", "Titel", "Vorname", "Nachname",
|
||
"OT", "STRASSE", "PLZ", "WOHNORT", "Email", "PER_KONTOINHABER", "PER_RECHTSPERSON",
|
||
"EIG_GEMARKUNG", "EIG_BLATT", "LISTE_ZGBBL", "ANZAHL_PERSONEN", "EIG_ID",
|
||
]
|
||
|
||
|
||
class _Null:
|
||
"""Nachbildung von QGIS-NULL."""
|
||
|
||
def isNull(self):
|
||
return True
|
||
|
||
|
||
class _Feature(dict):
|
||
def __init__(self, fid, werte):
|
||
super().__init__(werte)
|
||
self._fid = fid
|
||
|
||
def id(self):
|
||
return self._fid
|
||
|
||
|
||
class _Layer:
|
||
def __init__(self, zeilen, kopf=KOPF, name="Serienbrief", layer_id="csv1"):
|
||
self._kopf = list(kopf)
|
||
self._name = name
|
||
self._id = layer_id
|
||
self._features = [
|
||
_Feature(i + 1, {spalte: zeile.get(spalte.lstrip(""), "") for spalte in self._kopf})
|
||
for i, zeile in enumerate(zeilen)
|
||
]
|
||
|
||
def name(self):
|
||
return self._name
|
||
|
||
def id(self):
|
||
return self._id
|
||
|
||
def fields(self):
|
||
return types.SimpleNamespace(names=lambda: list(self._kopf))
|
||
|
||
def getFeatures(self):
|
||
return iter(self._features)
|
||
|
||
|
||
def _zeile(nachname, vorname, strasse="Hauptstraße 1", plz="01234", ort="Musterstadt", **weitere):
|
||
zeile = {"Nachname": nachname, "Vorname": vorname, "STRASSE": strasse, "PLZ": plz, "WOHNORT": ort}
|
||
zeile.update(weitere)
|
||
return zeile
|
||
|
||
|
||
class TestLayererkennung(unittest.TestCase):
|
||
|
||
def test_layer_mit_name_und_vorname_ist_eigentuemerlayer(self):
|
||
"""Prüft, dass ein Layer mit den Spalten Nachname und Vorname als Eigentümerdatei erkannt wird, auch mit BOM und anderer Schreibweise."""
|
||
self.assertTrue(es.ist_eigentuemerlayer(_Layer([])))
|
||
self.assertTrue(es.ist_eigentuemerlayer(_Layer([], kopf=["NACHNAME", "vorname"])))
|
||
self.assertFalse(es.ist_eigentuemerlayer(_Layer([], kopf=["flurstueckskennzeichen"])))
|
||
self.assertFalse(es.ist_eigentuemerlayer(object()))
|
||
|
||
def test_eigentuemerlayer_im_projekt(self):
|
||
"""Prüft, dass aus dem Projekt nur passende Layer kommen, nach Namen sortiert."""
|
||
b = _Layer([], name="b-Export", layer_id="b")
|
||
a = _Layer([], name="A-Export", layer_id="a")
|
||
flst = _Layer([], kopf=["flurstueckskennzeichen"], name="Flurstücke", layer_id="f")
|
||
projekt = types.SimpleNamespace(mapLayers=lambda: {"b": b, "f": flst, "a": a})
|
||
with patch.object(es, "QgsProject", types.SimpleNamespace(instance=lambda: projekt)):
|
||
self.assertEqual(es.eigentuemerlayer_im_projekt(), [a, b])
|
||
|
||
|
||
class TestSuche(unittest.TestCase):
|
||
|
||
def test_ohne_eingabe_fehler(self):
|
||
"""Prüft, dass ohne Name und Vorname nicht die ganze Datei gelistet, sondern um eine Eingabe gebeten wird."""
|
||
with self.assertRaisesRegex(es.EigentuemerSucheFehler, "Namen"):
|
||
es.suche_eigentuemer(_Layer([_zeile("Beispiel", "Anna")]), " ", "")
|
||
|
||
def test_layer_ohne_pflichtspalten(self):
|
||
"""Prüft, dass ein Layer ohne Nachname/Vorname mit Hinweis auf den Serienbriefexport abgelehnt wird."""
|
||
with self.assertRaisesRegex(es.EigentuemerSucheFehler, "Serienbriefexport"):
|
||
es.suche_eigentuemer(_Layer([], kopf=["flurstueckskennzeichen"]), "Beispiel", "")
|
||
|
||
def test_teilstring_ohne_gross_kleinschreibung(self):
|
||
"""Prüft, dass Name und Vorname als Teilstring ohne Rücksicht auf Groß-/Kleinschreibung gesucht werden."""
|
||
layer = _Layer([
|
||
_zeile("Beispiel", "Anna"),
|
||
_zeile("Beispiel-Muster", "Bernd"),
|
||
_zeile("Anders", "Anna"),
|
||
])
|
||
self.assertEqual([e.nachname for e in es.suche_eigentuemer(layer, "beispiel", "")],
|
||
["Beispiel", "Beispiel-Muster"])
|
||
self.assertEqual([e.nachname for e in es.suche_eigentuemer(layer, "", "ANNA")], ["Anders", "Beispiel"])
|
||
self.assertEqual([e.vorname for e in es.suche_eigentuemer(layer, "beisp", "an")], ["Anna"])
|
||
|
||
def test_umlaute_und_eszett(self):
|
||
"""Prüft, dass „Müller“ auch „Mueller“ findet und „Strauss“ auch „Strauß“."""
|
||
layer = _Layer([_zeile("Mueller", "Jörg"), _zeile("Strauß", "Erika", ort="Anderswo")])
|
||
self.assertEqual(len(es.suche_eigentuemer(layer, "Müller", "Joerg")), 1)
|
||
self.assertEqual(len(es.suche_eigentuemer(layer, "Strauss", "")), 1)
|
||
|
||
def test_gleiche_person_gleiche_anschrift_zusammengefasst(self):
|
||
"""Prüft, dass Zeilen mit gleichem Namen und gleicher Anschrift ein Treffer werden und Konten, Personen-IDs und E-Mails gesammelt werden."""
|
||
layer = _Layer([
|
||
_zeile("Beispiel", "Anna", EIG_KONTO="100", PER_ID="7", Email="anna@example.org"),
|
||
_zeile("beispiel ", " Anna", strasse="Hauptstraße 1", EIG_KONTO="200", PER_ID="7",
|
||
Titel="Dr.", Email="anna@example.org"),
|
||
_zeile("Beispiel", "Anna", EIG_KONTO="300", PER_ID="8", Email="a.b@example.org"),
|
||
])
|
||
treffer = es.suche_eigentuemer(layer, "Beispiel", "Anna")
|
||
self.assertEqual(len(treffer), 1)
|
||
e = treffer[0]
|
||
self.assertEqual(e.konten, ("100", "200", "300"))
|
||
self.assertEqual(e.per_ids, ("7", "8"))
|
||
self.assertEqual(e.emails, ("anna@example.org", "a.b@example.org"))
|
||
self.assertEqual(e.feature_ids, (1, 2, 3))
|
||
self.assertEqual(e.titel, "Dr.")
|
||
|
||
def test_andere_anschrift_eigener_treffer(self):
|
||
"""Prüft, dass dieselbe Person mit anderer Anschrift ein eigener Treffer bleibt, sortiert nach Wohnort."""
|
||
layer = _Layer([
|
||
_zeile("Beispiel", "Anna", ort="Zettlitz"),
|
||
_zeile("Beispiel", "Anna", ort="Altdorf", plz="09999"),
|
||
])
|
||
treffer = es.suche_eigentuemer(layer, "Beispiel", "Anna")
|
||
self.assertEqual([e.wohnort for e in treffer], ["Altdorf", "Zettlitz"])
|
||
|
||
def test_kennzeichen_verstorben_und_rechtsperson(self):
|
||
"""Prüft, dass „verstorben“ und „Rechtsperson“ aus üblichen Ja-Werten erkannt werden und Nein-Werte, Leeres und NULL nicht zählen."""
|
||
ja = ["J", "ja", "1", "true", "X"]
|
||
nein = ["", "0", "N", "nein", "false", None, _Null()]
|
||
zeilen = [_zeile(f"Ja{i}", "Test", PER_VERSTORBEN=w, PER_RECHTSPERSON=w) for i, w in enumerate(ja)]
|
||
zeilen += [_zeile(f"Nein{i}", "Test", PER_VERSTORBEN=w, PER_RECHTSPERSON=w) for i, w in enumerate(nein)]
|
||
for e in es.suche_eigentuemer(_Layer(zeilen), "", "Test"):
|
||
erwartet = e.nachname.startswith("Ja")
|
||
with self.subTest(name=e.nachname):
|
||
self.assertEqual(e.verstorben, erwartet)
|
||
self.assertEqual(e.rechtsperson, erwartet)
|
||
self.assertEqual(e.hinweise, "verstorben, Rechtsperson" if erwartet else "")
|
||
|
||
def test_plz_mit_fuehrender_null(self):
|
||
"""Prüft, dass eine von QGIS als Zahl gelesene PLZ wieder fünfstellig mit führender Null erscheint und Zeilen dadurch zusammenfinden."""
|
||
layer = _Layer([
|
||
_zeile("Beispiel", "Anna", plz=1067),
|
||
_zeile("Beispiel", "Anna", plz="01067"),
|
||
_zeile("Muster", "Bernd", plz="1067.0"),
|
||
])
|
||
treffer = es.suche_eigentuemer(layer, "e", "")
|
||
self.assertEqual([(e.nachname, e.plz) for e in treffer], [("Beispiel", "01067"), ("Muster", "01067")])
|
||
|
||
def test_suche_gibt_nichts_aus(self):
|
||
"""Prüft, dass die Suche keine Personendaten auf die Konsole schreibt (Datenschutz)."""
|
||
ausgabe = io.StringIO()
|
||
with contextlib.redirect_stdout(ausgabe), contextlib.redirect_stderr(ausgabe):
|
||
es.suche_eigentuemer(_Layer([_zeile("Beispiel", "Anna")]), "Beispiel", "")
|
||
self.assertEqual(ausgabe.getvalue(), "")
|
||
|
||
|
||
class TestAnzeigeformen(unittest.TestCase):
|
||
|
||
def test_name_und_anschrift(self):
|
||
"""Prüft, dass Name mit Titel und einzeilige Anschrift mit Ortsteil lesbar zusammengesetzt werden."""
|
||
e = Eigentuemer(titel="Dr.", vorname="Anna", nachname="Beispiel", strasse="Hauptstraße 1",
|
||
plz="01234", wohnort="Musterstadt", ortsteil="Unterdorf")
|
||
self.assertEqual(e.name, "Beispiel, Dr. Anna")
|
||
self.assertEqual(e.anschrift, "Hauptstraße 1, 01234 Musterstadt, OT Unterdorf")
|
||
self.assertEqual(Eigentuemer(nachname="Beispiel", ortsteil="OT Unterdorf").anschrift, "OT Unterdorf")
|
||
self.assertEqual(Eigentuemer(nachname="Gemeinde Musterstadt").name, "Gemeinde Musterstadt")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|