Files
p42.f-w/backend/tests/test_haushalt.py
T
guyg1474andClaude Opus 5.5 0fb3521c2f Freunde, privates Teilen, Artikelrechte, Reiserouten
- Freunde (freunde.py, Tabelle friendships): suchen, anfragen, annehmen,
  ablehnen, entfernen; neue Seite "Freunde" im Menü
- Vorratslisten und Kanban-Projekte immer privat: sichtbar für Ersteller und
  Mitglieder; teilen nur mit Freunden (Admins: mit allen); Kanban-Mitglieder
  (Tabelle kanban_project_members), Bearbeiter nur aus dem Projekt,
  ausgetretene Mitglieder verlieren ihre Zuteilung
- Artikel: lesen alle; bearbeiten Autor, Mitarbeitende verknüpfter
  Kanban-Projekte und Admins; verknüpfen nur bearbeitbare Artikel
  (sonst ließen sich Rechte erschleichen); Board-Liste auf der Artikelseite
  zeigt nur sichtbare Boards
- Administratoren sehen und bearbeiten alles
- Reisen: Städte nur von Freunden (Admins: alle); "Reiserouten zeigen"
  zeichnet die Strecken der Kilometer-Rechnung als Bögen
- Migration Schritt 6: bisher gemeinsame Listen und alle Projekte bekommen
  die vorhandenen Konten als Mitglieder, ohne Ersteller → erster Admin;
  erste Liste einer neuen Installation gehört dem ersten Konto
- Tests: test_freunde.py, test_zugriff.py, bestehende auf Privat-Modell
  umgestellt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:49:45 +02:00

162 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
def state(client):
return client.get("/api/haushalt").json()
def add(client, list_id, name, area="einkauf", quantity=1, unit="stk"):
return client.post("/api/haushalt/items", json={"list": area, "name": name, "quantity": quantity, "unit": unit, "listId": list_id})
def items(data, list_id, area=None):
return [i for i in data["items"] if i["listId"] == list_id and (area is None or i["list"] == area)]
def new_list(client, name, members=()):
res = client.post("/api/haushalt/listen", json={"name": name, "members": list(members)})
assert res.status_code == 201, res.text
return res.json()["created"]
def home(client):
"""Erste eigene Liste – Listen sind privat, jedes Konto legt seine eigene an."""
lists = [l for l in state(client)["lists"] if l["createdBy"] and l["createdBy"]["id"] == client.user["id"]]
return lists[0]["id"] if lists else new_list(client, "Zuhause")
def test_neue_liste_ist_privat(login):
anna, ben = login("anna"), login("ben")
home(anna)
assert [l["name"] for l in state(anna)["lists"]] == ["Zuhause"]
assert state(ben)["lists"] == []
def test_erstes_konto_bekommt_startliste():
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
client.post("/api/auth/setup", json={"username": "erste", "password": "passwort123"})
assert [l["name"] for l in client.get("/api/haushalt").json()["lists"]] == ["Zuhause"]
def test_hinzufuegen_fuehrt_zusammen_und_vereinheitlicht_namen(login):
anna = login("anna")
home_id = home(anna)
add(anna, home_id, "milch", quantity=2)
data = add(anna, home_id, "Milch", quantity=1).json()
assert [(i["name"], i["quantity"]) for i in items(data, home_id, "einkauf")] == [("Milch", 3)]
# Andere Einheit = eigene Zeile
data = add(anna, home_id, "Milch", quantity=1.5, unit="l").json()
assert len(items(data, home_id, "einkauf")) == 2
def test_listen_haben_eigenen_vorrat(login):
anna = login("anna")
home_id = home(anna)
urlaub = new_list(anna, "Urlaub")
add(anna, urlaub, "Brot", area="vorrat")
data = state(anna)
assert [i["name"] for i in items(data, urlaub, "vorrat")] == ["Brot"]
assert items(data, home_id, "vorrat") == []
def test_private_liste_nur_fuer_mitglieder(login, befriend):
anna, ben, chef = login("anna"), login("ben"), login("chef", admin=True)
geheim = new_list(anna, "Geheim")
item = add(anna, geheim, "Kuchen").json()
item_id = items(item, geheim)[0]["id"]
assert all(l["id"] != geheim for l in state(ben)["lists"])
assert add(ben, geheim, "x").status_code == 404
assert ben.patch(f"/api/haushalt/items/{item_id}", json={"quantity": 5}).status_code == 404
# Administratoren sehen und bearbeiten alles
assert any(l["id"] == geheim and l["canManage"] for l in state(chef)["lists"])
assert chef.patch(f"/api/haushalt/items/{item_id}", json={"quantity": 5}).status_code == 200
# Teilen geht nur mit Freunden
res = anna.patch(f"/api/haushalt/listen/{geheim}", json={"name": "Geheim", "members": [ben.user["id"]]})
assert res.status_code == 400
befriend(anna, ben)
anna.patch(f"/api/haushalt/listen/{geheim}", json={"name": "Geheim", "members": [ben.user["id"]]})
lst = next(l for l in state(ben)["lists"] if l["id"] == geheim)
assert lst["canManage"] is False
assert ben.patch(f"/api/haushalt/listen/{geheim}", json={"name": "x"}).status_code == 403
assert ben.delete(f"/api/haushalt/listen/{geheim}").status_code == 403
def test_liste_loeschen_entfernt_eintraege(login, conn):
anna = login("anna")
urlaub = new_list(anna, "Urlaub")
add(anna, urlaub, "Brot", area="vorrat")
add(anna, urlaub, "Wasser")
assert anna.delete(f"/api/haushalt/listen/{urlaub}").status_code == 200
assert conn.execute("SELECT COUNT(*) FROM household_items WHERE list_id = ?", (urlaub,)).fetchone()[0] == 0
def test_ungueltiges_mitglied(login):
res = login("anna").post("/api/haushalt/listen", json={"name": "x", "members": [9999]})
assert res.status_code == 400
def test_abhaken_und_einkauf_beenden(login):
anna = login("anna")
home_id = home(anna)
add(anna, home_id, "Milch", quantity=2)
add(anna, home_id, "Brot")
add(anna, home_id, "Milch", area="vorrat", quantity=1)
milch = next(i for i in items(state(anna), home_id, "einkauf") if i["name"] == "Milch")
data = anna.patch(f"/api/haushalt/items/{milch['id']}", json={"checked": True}).json()
# Abgehakt bleibt auf der Einkaufsliste
assert {i["name"]: i["checked"] for i in items(data, home_id, "einkauf")} == {"Milch": True, "Brot": False}
res = anna.post(f"/api/haushalt/listen/{home_id}/einkauf-beenden")
assert res.json()["moved"] == 1
data = res.json()
assert [i["name"] for i in items(data, home_id, "einkauf")] == ["Brot"]
assert [(i["name"], i["quantity"]) for i in items(data, home_id, "vorrat")] == [("Milch", 3)]
# Nichts mehr abgehakt
assert anna.post(f"/api/haushalt/listen/{home_id}/einkauf-beenden").status_code == 400
def test_vorrat_laesst_sich_nicht_abhaken(login):
anna = login("anna")
home_id = home(anna)
vorrat = items(add(anna, home_id, "Reis", area="vorrat").json(), home_id, "vorrat")[0]
assert anna.patch(f"/api/haushalt/items/{vorrat['id']}", json={"checked": True}).status_code == 400
def test_erneut_hinzufuegen_hebt_haken_auf(login):
anna = login("anna")
home_id = home(anna)
milch = items(add(anna, home_id, "Milch").json(), home_id)[0]
anna.patch(f"/api/haushalt/items/{milch['id']}", json={"checked": True})
data = add(anna, home_id, "Milch").json()
assert items(data, home_id)[0]["checked"] is False
def test_aufgebraucht_kommt_mit_menge_1_auf_einkauf(login):
anna = login("anna")
home_id = home(anna)
reis = items(add(anna, home_id, "Reis", area="vorrat", quantity=4).json(), home_id)[0]
data = anna.post(f"/api/haushalt/items/{reis['id']}/move").json()
assert [(i["list"], i["quantity"]) for i in items(data, home_id)] == [("einkauf", 1)]
def test_rezept_uebernehmen_in_gewaehlte_liste(login):
anna = login("anna")
urlaub = new_list(anna, "Urlaub")
res = anna.post("/api/haushalt/rezept/uebernehmen", json={
"items": [{"name": "Mehl", "quantity": 0.5, "unit": "kg"}], "aliases": [], "listId": urlaub,
})
assert res.status_code == 200
assert [(i["name"], i["quantity"], i["unit"]) for i in items(res.json(), urlaub, "einkauf")] == [("Mehl", 0.5, "kg")]
def test_einheitenwechsel_fuehrt_zusammen(login):
anna = login("anna")
home_id = home(anna)
add(anna, home_id, "Mehl", quantity=1, unit="kg")
stk = items(add(anna, home_id, "Mehl", quantity=2).json(), home_id)
stk = next(i for i in stk if i["unit"] == "stk")
data = anna.patch(f"/api/haushalt/items/{stk['id']}", json={"unit": "kg"}).json()
assert [(i["quantity"], i["unit"]) for i in items(data, home_id)] == [(3, "kg")]