PER_RECHTSPERSON kennt die Werte 0, 1, 5 und 9 und ist kein Ja/Nein-Feld. Die Trefferliste bekommt dafür eine eigene Spalte mit dem unveränderten Wert; abweichende Werte einer Person werden gesammelt. Die Entschlüsselung folgt später. „verstorben“ (PER_VERSTORBEN 0/1) bleibt als Hinweis. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
198 lines
9.2 KiB
Python
198 lines
9.2 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(self):
|
||
"""Prüft, dass „verstorben“ beim Exportwert 1 (auch als Zahl gelesen) gesetzt ist und bei 0, Leerem und NULL nicht."""
|
||
ja = ["1", 1]
|
||
nein = ["0", 0, "", None, _Null()]
|
||
zeilen = [_zeile(f"Ja{i}", "Test", PER_VERSTORBEN=w) for i, w in enumerate(ja)]
|
||
zeilen += [_zeile(f"Nein{i}", "Test", PER_VERSTORBEN=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.hinweise, "verstorben" if erwartet else "")
|
||
|
||
def test_rechtsperson_als_rohwert(self):
|
||
"""Prüft, dass PER_RECHTSPERSON unverändert angezeigt wird (0, 1, 5, 9; noch nicht entschlüsselt) und abweichende Werte einer Person gesammelt werden."""
|
||
layer = _Layer([
|
||
_zeile("Beispiel", "Anna", PER_RECHTSPERSON="0"),
|
||
_zeile("Gemeinde Musterstadt", "", strasse="Markt 1", PER_RECHTSPERSON=5),
|
||
_zeile("Muster", "Bernd", PER_RECHTSPERSON="1"),
|
||
_zeile("Muster", "Bernd", PER_RECHTSPERSON="9"),
|
||
_zeile("Ohnewert", "Clara", PER_RECHTSPERSON=_Null()),
|
||
])
|
||
werte = {e.nachname: e.rechtsperson_text for e in es.suche_eigentuemer(layer, "e", "")}
|
||
self.assertEqual(werte, {"Beispiel": "0", "Gemeinde Musterstadt": "5", "Muster": "1, 9", "Ohnewert": ""})
|
||
|
||
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()
|