import re from datetime import date from decimal import Decimal from pathlib import Path from types import SimpleNamespace from app.engine.loans import add_months from app.models.tables import (Category, Loan, RecurringItem, Scenario, ScenarioModifier) D = Decimal MONAT = "2030-03" def _login(client): client.post("/login", data={"username": "admin", "password": "geheim"}) def _daten(db, gehalt="3000.00"): wohnen, eink, vers = Category(name="Wohnen"), Category(name="Einkommen"), Category(name="Versicherung") db.add_all([wohnen, eink, vers]) db.flush() db.add_all([ RecurringItem(name="Gehalt", amount=D(gehalt), rhythm="monthly", due_day=1, category_id=eink.id), RecurringItem(name="Miete", amount=D("-1000.00"), rhythm="monthly", due_day=1, category_id=wohnen.id), RecurringItem(name="Police", amount=D("-1200.00"), rhythm="yearly", due_day=1, category_id=vers.id), ]) db.commit() return wohnen def _get(client, **params): return client.get("/aufteilung", params=params) def test_menuepunkt(client): _login(client) assert 'Aufteilung' in client.get("/").text def test_standardauswahl(client): _login(client) r = _get(client) assert r.status_code == 200 assert f'value="{date.today().strftime("%Y-%m")}"' in r.text assert re.search(r'', r.text) assert len(re.findall(r'type="checkbox"[^>]*checked', r.text)) == 3 def test_tabelle_kennzahlen_torte(client, db): _login(client) _daten(db) t = _get(client, monat=MONAT).text assert "03.2030" in t assert "Fixposten (jährlich)" in t assert "-100,00 €" in t # Police 1.200 / 12 assert "33,3 %" in t # Miete 1.000 von 3.000 assert "Gesamte Einkünfte: 3.000,00 €" in t tfoot = re.search(r"(.*?)", t, re.S).group(1) assert "Frei verfügbar" in tfoot and "1.900,00 €" in tfoot and "63,3 %" in tfoot assert '\s*]*>Gehalt.*?", t, re.S).group(0) assert "%" not in zeile def test_alle_filter_aus_zeigt_keine_posten(client, db): _login(client) _daten(db) t = _get(client, monat=MONAT, gesendet="1").text assert "Für diese Auswahl gibt es keine Posten." in t assert '
]*checked', t) def test_unterdeckung(client, db): _login(client) _daten(db, gehalt="500.00") t = _get(client, monat=MONAT).text assert "Kosten übersteigen die Einkünfte um 600,00 €" in t assert "Anteile an den Gesamtkosten" in t assert 'class="seg frei"' not in t tfoot = re.search(r"(.*?)", t, re.S).group(1) assert "Unterdeckung" in tfoot and "-600,00 €" in tfoot def test_szenario_mit_modifikator(client, db): _login(client) wohnen = _daten(db) sc = Scenario(name="Sparen", description="") db.add(sc) db.flush() db.add(ScenarioModifier(scenario_id=sc.id, target_type="category", target_id=wohnen.id, kind="percent", value=D("50"))) db.commit() t = _get(client, monat=MONAT, szenario=str(sc.id)).text assert "-500,00 €" in t and "Sparen" in t assert f'' in t def test_szenario_vergangenheit_fehler_inline(client, db): _login(client) _daten(db) sc = Scenario(name="S", description="") db.add(sc) db.commit() vormonat = add_months(date.today().replace(day=1), -1).strftime("%Y-%m") r = _get(client, monat=vormonat, szenario=str(sc.id)) assert r.status_code == 200 assert "Szenarien sind erst ab dem laufenden Monat wählbar" in r.text assert '
\s* t.index("") def _drill(client, pfad="/aufteilung/kategorie", **params): return client.get(pfad, params={"monat": MONAT, **params}) def test_hinweistext_und_segment_links(client, db): _login(client) _daten(db) t = _get(client, monat=MONAT).text assert "Für Kategorie-Aufteilung auf Segment klicken" in t assert 'hx-get="/aufteilung/kategorie?' in t assert "kategorie=Wohnen" in t assert 'hx-target="#aufteilung-kategorie"' in t assert '
' in t def test_frei_verfuegbar_ohne_link(client, db): _login(client) _daten(db) t = _get(client, monat=MONAT).text assert "kategorie=Frei" not in t frei = re.search(r']*class="seg frei[^"]*"[^>]*>', t).group(0) assert "hx-get" not in frei def test_fragment_zweite_torte_und_oob(client, db): _login(client) _daten(db) r = _drill(client, kategorie="Wohnen") assert r.status_code == 200 assert "Wohnen: 1.000,00 € / Monat" in r.text assert "Miete" in r.text and "33,3 % der Einkünfte" in r.text assert '
' in r.text assert "gewaehlt" in r.text assert "(.*?)', t, re.S).group(1) assert 'class="legende-zusatz"' in legende assert "Kleinposten 3" in legende CSS = (Path(__file__).resolve().parent.parent / "app" / "static" / "style.css").read_text() def test_nav_bricht_um(): nav = re.search(r"(?m)^nav\s*\{([^}]*)\}", CSS).group(1) assert re.search(r"flex-wrap:\s*wrap", nav) def test_legende_zusatz_nicht_fett_und_figcaption_bleibt_fett(): assert re.search(r"\.legende-zusatz\s*\{[^}]*font-weight:\s*normal", CSS) # Nutzerwunsch A14: Bildunterschrift der Torte bleibt fett. assert re.search(r"\.torte figcaption\s*\{[^}]*font-weight:\s*bold", CSS) def test_gewaehltes_segment_zuletzt_gezeichnet(): from app.routers.gui import templates def seg(label, gewaehlt): s = SimpleNamespace(path=f"M0 0 L{label}", color="s1", label=label, merged=False, members=[], value=Decimal("1.00"), share=Decimal("50.0")) return {"s": s, "q": None, "gewaehlt": gewaehlt, "zusatz": None} torte = templates.env.get_template("_torte_macros.html").module.torte html = str(torte([seg("Alpha", True), seg("Beta", False)], "Titel")) svg = html.split("", 1)[0] assert svg.index("M0 0 LBeta") < svg.index("M0 0 LAlpha") legende = html.split('
    ', 1)[1] assert legende.index("Alpha") < legende.index("Beta") # Legende behält Reihenfolge def test_tabellen_scrollen_auf_schmalen_bildschirmen_in_sich(): m = re.search(r"@media \(max-width: 600px\)[^@]*table\s*\{([^}]*)\}", CSS) assert m and re.search(r"overflow-x:\s*auto", m.group(1)) and re.search(r"display:\s*block", m.group(1)) assert re.search(r"@media \(max-width: 600px\)[^@]*fieldset\s*\{[^}]*min-width:\s*0", CSS)