from datetime import date, timedelta from decimal import Decimal import pytest from app.models.tables import Account, Category, Transaction H = {"Authorization": "Bearer test-key"} def _tx(db, acc, day, amount, purpose="Zweck", counterparty="X", category=None, n=[0]): n[0] += 1 db.add(Transaction(account_id=acc.id, booking_date=day, amount=Decimal(amount), purpose=purpose, counterparty=counterparty, status="confirmed", category_id=category.id if category else None, dedup_hash=f"sort-{n[0]}")) @pytest.fixture def small(db): zeta = Account(bank="t", iban="DE-SO-1", name="Zeta", type="giro") alpha = Account(bank="t", iban="DE-SO-2", name="alpha", type="giro") kat = Category(name="Miete") db.add_all([zeta, alpha, kat]) db.flush() _tx(db, zeta, date(2026, 1, 3), "-5.00", "b-Zweck", "Bert", kat) _tx(db, alpha, date(2026, 1, 1), "20.00", "A-Zweck", "anna", None) _tx(db, zeta, date(2026, 1, 2), "-30.00", "c-Zweck", "Carl", kat) db.commit() def _amounts(client, qs): r = client.get(f"/api/transactions?{qs}", headers=H) assert r.status_code == 200 return [Decimal(t["amount"]) for t in r.json()] def test_standard_bleibt_datum_aufsteigend(client, small): assert _amounts(client, "") == [Decimal("20.00"), Decimal("-30.00"), Decimal("-5.00")] @pytest.mark.parametrize("sort,dir,expected", [ ("betrag", "asc", ["-30.00", "-5.00", "20.00"]), ("betrag", "desc", ["20.00", "-5.00", "-30.00"]), ("datum", "desc", ["-5.00", "-30.00", "20.00"]), ("konto", "asc", ["20.00", "-5.00", "-30.00"]), # alpha vor Zeta (case-insensitiv), Gleichstand -> id ("zweck", "asc", ["20.00", "-5.00", "-30.00"]), # A-, b-, c-Zweck ("empfaenger", "desc", ["-30.00", "-5.00", "20.00"]), # Carl, Bert, anna ("kategorie", "asc", ["-5.00", "-30.00", "20.00"]), # Miete (id-Reihenfolge), unkategorisiert zuletzt ("kategorie", "desc", ["-30.00", "-5.00", "20.00"]), # unkategorisiert auch absteigend zuletzt ]) def test_sortierung_je_spalte(client, small, sort, dir, expected): assert _amounts(client, f"sort={sort}&dir={dir}") == [Decimal(e) for e in expected] def test_ungueltige_werte_fallen_auf_standard(client, small): assert _amounts(client, "sort=foo;drop&dir=hoch") == _amounts(client, "") client.post("/login", data={"username": "admin", "password": "geheim"}) assert client.get("/buchungen?sort=foo&dir=bar").status_code == 200 @pytest.fixture def many(db): acc = Account(bank="t", iban="DE-SO-3", name="Viel", type="giro") db.add(acc) db.flush() for i in range(1, 61): # Beträge i,37; Datum verwürfelt _tx(db, acc, date(2026, 1, 1) + timedelta(days=(i * 7) % 60), f"{i}.37", f"Miete {i}") db.commit() def test_sortierung_gilt_ueber_alle_seiten(client, many): client.post("/login", data={"username": "admin", "password": "geheim"}) p1 = client.get("/buchungen?sort=betrag&dir=desc").text p2 = client.get("/buchungen?sort=betrag&dir=desc&page=2").text assert ">60,37" in p1 and ">1,37" not in p1 assert ">1,37" in p2 and ">60,37" not in p2 def test_links_erhalten_filter_und_sortierung(client, many): client.post("/login", data={"username": "admin", "password": "geheim"}) html = client.get("/buchungen?q=Miete&sort=betrag&dir=asc").text # Aktiver Spaltenkopf kehrt die Richtung um, übernimmt den Filter, ohne page. assert 'href="/buchungen?q=Miete&status=confirmed&sort=betrag&dir=desc"' in html # Inaktive Spalte startet aufsteigend. assert 'href="/buchungen?q=Miete&status=confirmed&sort=datum&dir=asc"' in html # Pager behält Filter + Sortierung. assert "q=Miete&status=confirmed&sort=betrag&dir=asc&page=2" in html assert "▲" in html