feat: Backup/Export + Import-UX klarer + Raiffeisen-Preset-Fix
Backup & Export: - routers/backup: /backup/db (Admin, konsistentes SQLite-Online-Backup), /export/transactions.csv|json und /export/all.json (owner-gescoped). - Einstellungen: Bereich „Backup & Export" mit Download-Buttons. docs/BACKUP.md (Wiederherstellung + LXC-Sicherung). pytest für Export + Admin-Schutz. Import-Verbesserungen (Nutzer-Feedback zum Ablauf): - Klarer Hinweis, dass die Zuordnungstabelle nur Beispielzeilen zeigt und „Vorschau erstellen" die vollständige Prüfliste + Buchen-Button erzeugt. - Zusammenfassung (erkannt/gebucht) und Warnung, wenn keine gültige Zeile erkannt wurde (Hinweis auf Format/Bank-Vorlage). - Fix: Raiffeisen-Preset hat has_header=true (der Export hat eine Kopfzeile). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import json
|
||||
import secrets
|
||||
from datetime import date
|
||||
|
||||
|
||||
def _setup(admin):
|
||||
acc = admin.post("/api/accounts", json={"name": "Exp", "currency": "EUR"}).json()
|
||||
admin.post("/api/transactions", json={
|
||||
"type": "expense", "date": str(date.today()), "account_id": acc["id"],
|
||||
"amount_cents": 4200, "payee": "Testladen", "description": "Etwas",
|
||||
})
|
||||
return acc
|
||||
|
||||
|
||||
def test_export_transactions_csv(admin):
|
||||
_setup(admin)
|
||||
r = admin.get("/api/export/transactions.csv")
|
||||
assert r.status_code == 200
|
||||
assert "text/csv" in r.headers["content-type"]
|
||||
body = r.content.decode("utf-8-sig")
|
||||
assert "Datum;Typ;Konto" in body.splitlines()[0]
|
||||
assert "Testladen" in body
|
||||
|
||||
|
||||
def test_export_transactions_json(admin):
|
||||
_setup(admin)
|
||||
r = admin.get("/api/export/transactions.json")
|
||||
assert r.status_code == 200
|
||||
data = json.loads(r.content)
|
||||
assert any(t["payee"] == "Testladen" for t in data)
|
||||
|
||||
|
||||
def test_export_all_json(admin):
|
||||
_setup(admin)
|
||||
r = admin.get("/api/export/all.json")
|
||||
assert r.status_code == 200
|
||||
data = json.loads(r.content)
|
||||
assert data["app"] == "Certain Finance"
|
||||
assert len(data["accounts"]) >= 1
|
||||
assert len(data["transactions"]) >= 1
|
||||
|
||||
|
||||
def test_db_backup_admin_only(admin):
|
||||
from fastapi.testclient import TestClient
|
||||
from app.main import app
|
||||
|
||||
# Admin darf.
|
||||
r = admin.get("/api/backup/db")
|
||||
assert r.status_code == 200
|
||||
assert r.content[:16].startswith(b"SQLite format 3")
|
||||
|
||||
# Normaler Nutzer darf nicht.
|
||||
u, p = "u_" + secrets.token_hex(4), "pw"
|
||||
admin.post("/api/users", json={"username": u, "password": p, "role": "user"})
|
||||
other = TestClient(app)
|
||||
other.post("/api/auth/login", data={"username": u, "password": p})
|
||||
assert other.get("/api/backup/db").status_code == 403
|
||||
# ...aber seinen eigenen Export schon (leer).
|
||||
assert other.get("/api/export/all.json").status_code == 200
|
||||
Reference in New Issue
Block a user