"""Zwei Dinge, die es brauchte, damit man einer Buchung ansieht, was sie ist. Die Notiz: Die Kategorie sagt, in welche Schublade es gehoert – nicht, WAS es war. „Amazon" kann Einrichtung sein, Werkzeug oder ein Geschenk. Der Rueckweg aus „Ignorieren": Bisher war ein Klick endgueltig. Das Paar blieb bei jedem weiteren Suchlauf stumm, und nichts verriet, dass es je einen Vorschlag gab. """ from datetime import date def _konto(admin, name, **kw): body = {"name": name, "type": "girokonto", "currency": "EUR"} body.update(kw) r = admin.post("/api/accounts", json=body) assert r.status_code == 201, r.text return r.json() def _buchung(admin, konto, art, betrag, wer, tag="2026-05-04", **kw): body = {"type": art, "date": tag, "account_id": konto["id"], "amount_cents": betrag, "payee": wer} body.update(kw) r = admin.post("/api/transactions", json=body) assert r.status_code in (200, 201), r.text return r.json() # --- Notiz ------------------------------------------------------------------ def test_notiz_laesst_sich_setzen_aendern_und_loeschen(admin): konto = _konto(admin, "Notiz-Konto") txn = _buchung(admin, konto, "expense", 4990, "Amazon", notes="Schraubenset, kein Moebelstueck") assert txn["notes"] == "Schraubenset, kein Moebelstueck" r = admin.patch(f"/api/transactions/{txn['id']}", json={"notes": "Geschenk fuer Tim"}) assert r.json()["notes"] == "Geschenk fuer Tim" r = admin.patch(f"/api/transactions/{txn['id']}", json={"notes": None}) assert r.json()["notes"] is None def test_notiz_ueberlebt_eine_aenderung_am_betrag(admin): """Sie haengt an der Buchung, nicht an einer Nebensache.""" konto = _konto(admin, "Notiz-Konto 2") txn = _buchung(admin, konto, "expense", 1000, "Amazon", notes="beruflich") r = admin.patch(f"/api/transactions/{txn['id']}", json={"amount_cents": 1200}) assert r.json()["notes"] == "beruflich" def test_notiz_steht_in_der_liste(admin): """Nach dem eigenen Konto gefragt – sonst haengt der Test an der Anzahl Buchungen, die andere Tests vorher angelegt haben.""" konto = _konto(admin, "Notiz-Konto 3") _buchung(admin, konto, "expense", 2000, "Amazon", notes="Werkzeug") zeilen = admin.get(f"/api/transactions?account_id={konto['id']}").json() assert [t for t in zeilen if t.get("notes") == "Werkzeug"], "ohne das Feld in der Liste nuetzt die Notiz beim Suchen nichts" # --- Abgelehnte Vorschlaege ------------------------------------------------- def _paar(admin, name): a = _konto(admin, f"{name} A") b = _konto(admin, f"{name} B") # Mit Kategorie, damit nicht automatisch verknuepft wird: Dann bleibt es # ein Vorschlag, und genau den kann man ablehnen. kat = None for g in admin.get("/api/categories").json(): for c in g["categories"]: if c["type"] == "expense": kat = c["id"] break if kat: break raus = _buchung(admin, a, "expense", 777, "Umbuchung X", category_id=kat) rein = _buchung(admin, b, "income", 777, "Umbuchung X") return raus, rein def test_abgelehntes_paar_laesst_sich_zurueckholen(admin): raus, rein = _paar(admin, "Ignoriert") vor = admin.get("/api/uebertraege/vorschlaege").json()["vorschlaege"] assert any({p["a"]["id"], p["b"]["id"]} == {raus["id"], rein["id"]} for p in vor) admin.post("/api/uebertraege/ignorieren", json={"a_id": raus["id"], "b_id": rein["id"]}) nach = admin.get("/api/uebertraege/vorschlaege").json()["vorschlaege"] assert not any({p["a"]["id"], p["b"]["id"]} == {raus["id"], rein["id"]} for p in nach) # Und es ist auffindbar, statt spurlos zu verschwinden. liste = admin.get("/api/uebertraege/ignoriert").json() assert any({p["a"]["id"], p["b"]["id"]} == {raus["id"], rein["id"]} for p in liste) r = admin.request("DELETE", "/api/uebertraege/ignoriert", json={"a_id": raus["id"], "b_id": rein["id"]}) assert r.status_code == 204, r.text wieder = admin.get("/api/uebertraege/vorschlaege").json()["vorschlaege"] assert any({p["a"]["id"], p["b"]["id"]} == {raus["id"], rein["id"]} for p in wieder) def test_ignoriert_zeigt_genug_zum_wiedererkennen(admin): raus, rein = _paar(admin, "Lesbar") admin.post("/api/uebertraege/ignorieren", json={"a_id": raus["id"], "b_id": rein["id"]}) eintrag = [p for p in admin.get("/api/uebertraege/ignoriert").json() if {p["a"]["id"], p["b"]["id"]} == {raus["id"], rein["id"]}][0] for seite in (eintrag["a"], eintrag["b"]): assert seite["date"] == "2026-05-04" assert seite["amount_cents"] == 777 assert seite["payee"] == "Umbuchung X" assert seite["account_name"].startswith("Lesbar") def test_geloeschte_buchung_verschwindet_aus_der_liste(admin): """Ein Eintrag, zu dem es nichts mehr zurueckzunehmen gibt, ist Rauschen.""" raus, rein = _paar(admin, "Geloescht") admin.post("/api/uebertraege/ignorieren", json={"a_id": raus["id"], "b_id": rein["id"]}) admin.delete(f"/api/transactions/{rein['id']}") liste = admin.get("/api/uebertraege/ignoriert").json() assert not any(p["a"]["id"] == raus["id"] or p["b"]["id"] == raus["id"] for p in liste) def test_fremdes_paar_laesst_sich_nicht_zurueckholen(admin): assert date.today() r = admin.request("DELETE", "/api/uebertraege/ignoriert", json={"a_id": 999999, "b_id": 999998}) assert r.status_code == 404, r.text