feat: Import-Rollback fuer bestaetigte Auszuege (v0.3.0)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-19 16:40:56 +02:00
parent d71c22adce
commit e7855a7bda
6 changed files with 94 additions and 4 deletions

View File

@@ -142,6 +142,67 @@ def test_delete_error_import_returns_204(client, db):
assert client.delete(f"/api/imports/{sid}", headers=H).status_code == 204
def test_rollback_confirmed_import_deletes_transactions_and_statement(client, fake_parse, tmp_path, monkeypatch):
# Ein bestaetigter Import laesst sich als Ganzes zurueckrollen: alle seine
# Buchungen UND der Auszug selbst verschwinden, das Konto bleibt bestehen
# (Name/IBAN fuer einen Re-Import mit verbessertem Parser).
monkeypatch.setenv("FB_INBOX_DIR", str(tmp_path / "inbox"))
monkeypatch.setenv("FB_UPLOADS_DIR", str(tmp_path / "uploads"))
from app.config import get_settings
get_settings.cache_clear()
sid = _upload(client).json()["id"]
assert client.post(f"/api/imports/{sid}/confirm", headers=H).status_code == 200
account_id = client.get("/api/accounts", headers=H).json()[0]["id"]
r = client.post(f"/api/imports/{sid}/rollback", headers=H)
assert r.status_code == 200
body = r.json()
assert body == {"deleted_transactions": 1, "statement_id": sid}
txs = client.get("/api/transactions", headers=H).json()
assert len(txs) == 0
imports = client.get("/api/imports", headers=H).json()
assert all(s["id"] != sid for s in imports)
accounts = client.get("/api/accounts", headers=H).json()
assert any(a["id"] == account_id for a in accounts)
def test_rollback_then_reimport_restores_transactions(client, fake_parse, tmp_path, monkeypatch):
# Der eigentliche Zweck des Rollbacks: denselben Auszug erneut importieren
# koennen (z.B. nach einem verbesserten Parser), ohne dass alte Buchungen
# als Duplikate im Weg stehen.
monkeypatch.setenv("FB_INBOX_DIR", str(tmp_path / "inbox"))
monkeypatch.setenv("FB_UPLOADS_DIR", str(tmp_path / "uploads"))
from app.config import get_settings
get_settings.cache_clear()
sid = _upload(client).json()["id"]
client.post(f"/api/imports/{sid}/confirm", headers=H)
client.post(f"/api/imports/{sid}/rollback", headers=H)
sid2 = _upload(client, "auszug_reimport.pdf").json()["id"]
prev = client.get(f"/api/imports/{sid2}/preview", headers=H).json()
assert prev["balance_ok"] is True
assert prev["duplicates"] == 0
assert client.post(f"/api/imports/{sid2}/confirm", headers=H).status_code == 200
txs = client.get("/api/transactions", headers=H).json()
assert len(txs) == 1 and txs[0]["status"] == "confirmed"
def test_rollback_draft_import_returns_409(client, fake_parse, tmp_path, monkeypatch):
monkeypatch.setenv("FB_INBOX_DIR", str(tmp_path / "inbox"))
monkeypatch.setenv("FB_UPLOADS_DIR", str(tmp_path / "uploads"))
from app.config import get_settings
get_settings.cache_clear()
sid = _upload(client).json()["id"]
r = client.post(f"/api/imports/{sid}/rollback", headers=H)
assert r.status_code == 409
def test_rollback_unknown_import_returns_404(client):
r = client.post("/api/imports/999999/rollback", headers=H)
assert r.status_code == 404
def test_upload_corrupt_pdf_produces_error_statement_not_500(client, tmp_path, monkeypatch):
# Datei besteht die Endungspruefung (.pdf), ist aber strukturell kein
# gueltiges PDF -> pdfplumber wirft eine eigene Exception (keine