Files
bin/finance/tests/test_final_review.py
wlfb cf9282c716 fix: Final-Review-Findings (GUI-Login, FK-Kaskaden, Validierung, Duplikat-Recheck, Views)
F1: GUI-Login-Hash gegen $-Korruption gehaertet. .env-Werte einfach-gequotet
    geschrieben + Sanity-Check nach Sourcing. Zusaetzlich systemd-Ebene:
    podman escapt $->$$ in Environment=, systemd 252 kollabiert das nicht ->
    sed-Post-Processing der Unit-Dateien. Live-.env: GUI-Passwort rotiert.
F2: Explizite Dependent-Deletes gegen FK-500 (delete_scenario/loan), 409 bei
    bestaetigtem Import-Delete. Test-Engine erzwingt PRAGMA foreign_keys=ON.
F3: Enum/Range-Validierung (Literal + Field ge/le) fuer Recurring, Loan,
    Modifier inkl. PATCH-Pfade.
F4: Confirm nur fuer draft-Statements (sonst 409) + Dedup-Re-Check gegen
    bestaetigte Buchungen vor dem Promoten.
F5: v_balance_history/v_balance_total addieren pro Konto den opening_balance
    des fruehesten bestaetigten Statements (approved deviation vom Plan-SQL).
F6: patch_scenario Namenskollision -> 409; create_transaction unbekanntes
    Konto -> 404 (statt 500).
F7: Generierte systemd-Unit-Dateien chmod 600 (enthalten Env-Secrets).
F8: Globaler htmx:responseError-Handler in base.html.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 20:11:31 +02:00

94 lines
3.8 KiB
Python

"""Tests fuer die Final-Review-Findings F2, F3, F6."""
from datetime import date
from decimal import Decimal
from app.models.tables import (ProjectionPoint, ProjectionResult, Scenario,
Statement, Transaction)
H = {"Authorization": "Bearer test-key"}
def _account(client, iban="DE01"):
return client.post("/api/accounts", headers=H, json={
"bank": "DKB", "iban": iban, "name": "G", "type": "giro"}).json()
# ----------------------------------------------------------------- F2 cascades
def test_delete_projected_scenario_removes_dependent_rows(client, db):
sc = client.post("/api/scenarios", headers=H, json={"name": "Basis"}).json()
sid = sc["id"]
r = client.post(f"/api/scenarios/{sid}/project", headers=H,
params={"horizon_days": 10, "start_date": "2026-07-15"})
assert r.status_code == 200
assert db.query(ProjectionPoint).filter_by(scenario_id=sid).count() > 0
assert db.get(ProjectionResult, sid) is not None
assert client.delete(f"/api/scenarios/{sid}", headers=H).status_code == 204
db.expire_all()
assert db.get(Scenario, sid) is None
assert db.query(ProjectionPoint).filter_by(scenario_id=sid).count() == 0
assert db.get(ProjectionResult, sid) is None
def test_delete_loan_assigned_to_scenario(client):
loan = client.post("/api/loans", headers=H, json={
"name": "K1", "principal": "5000.00", "annual_rate_pct": "6.0",
"term_months": 48, "payout_date": "2026-07-20"}).json()
sc = client.post("/api/scenarios", headers=H, json={"name": "Kredit"}).json()
assert client.post(f"/api/scenarios/{sc['id']}/loans/{loan['id']}",
headers=H).status_code == 204
assert client.delete(f"/api/loans/{loan['id']}", headers=H).status_code == 204
def test_delete_confirmed_import_returns_409(client, db):
acc = _account(client)
db.add(Statement(filename="s.pdf", bank="dkb", account_id=acc["id"],
status="confirmed"))
db.commit()
sid = db.query(Statement).one().id
assert client.delete(f"/api/imports/{sid}", headers=H).status_code == 409
# --------------------------------------------------------------- F3 validation
def test_recurring_invalid_rhythm_422(client):
r = client.post("/api/recurring", headers=H, json={
"name": "X", "amount": "-1.00", "rhythm": "weekly", "due_day": 1})
assert r.status_code == 422
def test_recurring_due_day_zero_422(client):
r = client.post("/api/recurring", headers=H, json={
"name": "X", "amount": "-1.00", "rhythm": "monthly", "due_day": 0})
assert r.status_code == 422
def test_loan_invalid_repayment_type_422(client):
r = client.post("/api/loans", headers=H, json={
"name": "K", "principal": "1000.00", "annual_rate_pct": "3.0",
"term_months": 12, "payout_date": "2026-08-01",
"repayment_type": "balloon"})
assert r.status_code == 422
def test_modifier_invalid_kind_422(client):
sc = client.post("/api/scenarios", headers=H, json={"name": "S"}).json()
r = client.post(f"/api/scenarios/{sc['id']}/modifiers", headers=H, json={
"target_type": "category", "target_id": 1, "kind": "double",
"value": "10"})
assert r.status_code == 422
# ------------------------------------------------------- F6 409/404 statt 500
def test_patch_scenario_rename_clash_409(client):
client.post("/api/scenarios", headers=H, json={"name": "A"})
b = client.post("/api/scenarios", headers=H, json={"name": "B"}).json()
r = client.patch(f"/api/scenarios/{b['id']}", headers=H, json={"name": "A"})
assert r.status_code == 409
def test_create_transaction_unknown_account_404(client):
r = client.post("/api/transactions", headers=H, json={
"account_id": 9999, "booking_date": "2026-07-01",
"amount": "-10.00", "purpose": "X", "counterparty": ""})
assert r.status_code == 404