From 9d6a53a224c2ff46310dd11b6c62d0adc1dc1ac2 Mon Sep 17 00:00:00 2001 From: Guido Date: Sat, 22 Aug 2026 18:56:43 +0200 Subject: [PATCH] test: Duplikaterkennung ueber Monatsgrenzen abgesichert MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Der uebliche Ablauf ist, Mitte Monat einzulesen und Ende Monat den ganzen Monat noch einmal. Die Tests halten fest, dass bereits gebuchte Zeilen nicht erneut in den Saldo laufen, neue ankommen – und dass zwei bzw. drei echte gleich aussehende Buchungen desselben Tages erhalten bleiben. Co-Authored-By: Claude Opus 5 --- backend/tests/test_import.py | 69 ++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/backend/tests/test_import.py b/backend/tests/test_import.py index 628c914..3d53040 100644 --- a/backend/tests/test_import.py +++ b/backend/tests/test_import.py @@ -622,3 +622,72 @@ def test_verlauf_merkt_sich_die_bank_vorlage(admin): verlauf = admin.get("/api/imports/batches").json() meins = [b for b in verlauf if b["account_id"] == konto["id"]] assert meins[0]["preset"] == "Raiffeisen (CH)" + + +def _csv(*zeilen): + return "Datum;Betrag;Empfaenger;Zweck;GegenIBAN\n" + "".join(z + "\n" for z in zeilen) + + +def _lies(admin, konto_id, text): + return admin.post( + "/api/imports/preview", + files={"file": ("a.csv", io.BytesIO(text.encode("utf-8")), "text/csv")}, + data={"account_id": str(konto_id), "delimiter": ";", "decimal_sep": ",", + "date_format": "%d.%m.%Y", "encoding": "utf-8", "has_header": "true", + "skip_rows": "0", "amount_mode": "signed", "column_map": COLMAP}, + ).json() + + +def test_monatsauszug_ueber_bereits_gebuchte_zeilen_hinweg(admin): + """Der übliche Ablauf: Mitte Monat einlesen, Ende Monat den ganzen Monat. + + Die bereits gebuchten Zeilen dürfen nicht erneut in den Saldo laufen, die + neuen müssen ankommen. Zwei echte, gleich aussehende Buchungen desselben + Tages bleiben dabei beide erhalten – sie sind kein Duplikat. + """ + konto = _account(admin, name="Monatskonto", currency="CHF") + bis_18 = _csv( + "05.08.2026;-20,00;Coop;Einkauf;", + "12.08.2026;-7,50;Kiosk;Snack;", + "12.08.2026;-7,50;Kiosk;Snack;", # echte zweite Buchung, kein Duplikat + "18.08.2026;3.000,00;Arbeitgeber;Lohn;", + ) + v = _lies(admin, konto["id"], bis_18) + assert sum(1 for r in v["rows"] if r["include"]) == 4 + admin.post("/api/imports/commit", json={ + "account_id": konto["id"], "filename": "aug-teil.csv", + "rows": [r for r in v["rows"] if r["include"]], + }) + stand = lambda: admin.get(f"/api/accounts/{konto['id']}").json()["balance_cents"] + assert stand() == 296500 # -2000 -750 -750 +300000 + + ganzer_august = bis_18 + "".join(z + "\n" for z in ( + "22.08.2026;-45,00;Migros;Einkauf;", + "29.08.2026;-12,00;SBB;Billett;", + )) + v2 = _lies(admin, konto["id"], ganzer_august) + dubletten = [r for r in v2["rows"] if r["is_duplicate"]] + neue = [r for r in v2["rows"] if r["include"]] + assert len(dubletten) == 4, [r["payee"] for r in dubletten] + assert [r["payee"] for r in neue] == ["Migros", "SBB"] + + admin.post("/api/imports/commit", json={ + "account_id": konto["id"], "filename": "aug-ganz.csv", + "rows": neue, + }) + assert stand() == 296500 - 4500 - 1200 + + +def test_dritte_gleiche_buchung_gilt_nicht_als_dublette(admin): + """Kommt am selben Tag eine DRITTE gleiche Buchung dazu, ist sie echt.""" + konto = _account(admin, name="Dreimal", currency="CHF") + zwei = _csv("03.08.2026;-5,00;Bäckerei;Brot;", "03.08.2026;-5,00;Bäckerei;Brot;") + v = _lies(admin, konto["id"], zwei) + admin.post("/api/imports/commit", json={ + "account_id": konto["id"], "filename": "zwei.csv", + "rows": [r for r in v["rows"] if r["include"]]}) + + drei = zwei + "03.08.2026;-5,00;Bäckerei;Brot;\n" + v2 = _lies(admin, konto["id"], drei) + assert sum(1 for r in v2["rows"] if r["is_duplicate"]) == 2 + assert sum(1 for r in v2["rows"] if r["include"]) == 1