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>
This commit is contained in:
1 parent
1d7497bead
commit
0fb3521c2f
34 files changed
+833
-245
No files matched your search
@@ -48,3 +48,12 @@ def login():
|
||||
client.user = {"id": user_id, "name": username.capitalize()}
|
||||
return client
|
||||
return make
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def befriend():
|
||||
"""befriend(anna, ben): Freundschaftsanfrage + Annahme."""
|
||||
def make(a: TestClient, b: TestClient) -> None:
|
||||
a.post(f"/api/freunde/{b.user['id']}")
|
||||
assert b.post(f"/api/freunde/{a.user['id']}").json()["friends"]
|
||||
return make
|
||||
@@ -1,6 +1,8 @@
|
||||
"""Datenschutz: keine Metadaten in Bildern, private Listen bleiben privat, keine fremden Bilder, keine offene API-Doku."""
|
||||
import io
|
||||
|
||||
from test_haushalt import new_list
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
from PIL import Image, PngImagePlugin
|
||||
|
||||
@@ -68,8 +70,9 @@ def private_liste(anna):
|
||||
return lid
|
||||
|
||||
|
||||
def test_privates_produkt_nicht_in_fremden_vorschlaegen(login):
|
||||
def test_privates_produkt_nicht_in_fremden_vorschlaegen(login, befriend):
|
||||
anna, ben = login("anna"), login("ben")
|
||||
befriend(anna, ben)
|
||||
lid = private_liste(anna)
|
||||
assert "Schwangerschaftstest" in anna.get("/api/haushalt").json()["products"]
|
||||
assert "Schwangerschaftstest" not in ben.get("/api/haushalt").json()["products"]
|
||||
@@ -89,9 +92,10 @@ def test_rezept_zuordnung_verraet_nichts(login):
|
||||
assert ing["known"] is True and ing["product"] == "Kondome"
|
||||
|
||||
|
||||
def test_vorschlag_loeschen_nur_in_eigenen_listen(login):
|
||||
def test_vorschlag_loeschen_nur_in_eigenen_listen(login, befriend):
|
||||
anna, ben = login("anna"), login("ben")
|
||||
home = anna.get("/api/haushalt").json()["lists"][0]["id"]
|
||||
befriend(anna, ben)
|
||||
home = new_list(anna, "Zuhause", members=[ben.user["id"]])
|
||||
private_liste(anna)
|
||||
anna.post("/api/haushalt/items", json={"list": "einkauf", "name": "Schwangerschaftstest", "listId": home})
|
||||
# Ben löscht den Vorschlag: weg aus "Zuhause", in Annas privater Liste bleibt er
|
||||
@@ -125,7 +129,7 @@ def test_api_doku_nicht_oeffentlich():
|
||||
|
||||
def test_umstellung_bestehender_daten(conn, login):
|
||||
anna = login("anna")
|
||||
home = anna.get("/api/haushalt").json()["lists"][0]["id"]
|
||||
home = new_list(anna, "Zuhause")
|
||||
with conn:
|
||||
# Stand vor Schritt 2: Katalog ohne Listen-Zuordnung, Artikel mit fremdem Bild, Foto mit GPS
|
||||
conn.execute("INSERT INTO shopping_lists (id, name, everyone, position, created_at, created_by) VALUES (99, 'P', 0, 2, 'x', ?)", (anna.user["id"],))
|
||||
@@ -133,6 +137,7 @@ def test_umstellung_bestehender_daten(conn, login):
|
||||
conn.execute("INSERT INTO products (key, name, uses, last_used) VALUES (?, ?, 3, 'x')", (key, name))
|
||||
conn.execute("INSERT INTO household_items (key, name, list, list_id, quantity, unit, updated_at) VALUES ('geheim', 'Geheim', 'einkauf', 99, 1, 'stk', 'x')")
|
||||
conn.execute("DELETE FROM list_products")
|
||||
conn.execute("UPDATE shopping_lists SET everyone = 1 WHERE id = ?", (home,)) # damals für alle sichtbar
|
||||
conn.execute("""INSERT INTO projects (id, title, status, content, created_at, updated_at)
|
||||
VALUES ('a1', 'T', 'aktiv', '<p>x</p><img src="https://tracker.example/p.gif">', 'x', 'x')""")
|
||||
conn.execute("PRAGMA user_version = 1")
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
def test_anfrage_annehmen_entfernen(login):
|
||||
anna, ben = login("anna"), login("ben")
|
||||
assert anna.post(f"/api/freunde/{ben.user['id']}").json()["outgoing"][0]["name"] == "Ben"
|
||||
state = ben.get("/api/freunde").json()
|
||||
assert [p["name"] for p in state["incoming"]] == ["Anna"] and state["friends"] == []
|
||||
assert [p["name"] for p in ben.post(f"/api/freunde/{anna.user['id']}").json()["friends"]] == ["Anna"]
|
||||
assert [p["name"] for p in anna.get("/api/freunde").json()["friends"]] == ["Ben"]
|
||||
assert ben.delete(f"/api/freunde/{anna.user['id']}").json()["friends"] == []
|
||||
assert anna.get("/api/freunde").json()["friends"] == []
|
||||
|
||||
|
||||
def test_eigene_anfrage_nimmt_nicht_selbst_an(login):
|
||||
anna, ben = login("anna"), login("ben")
|
||||
anna.post(f"/api/freunde/{ben.user['id']}")
|
||||
assert anna.post(f"/api/freunde/{ben.user['id']}").json()["friends"] == []
|
||||
assert anna.post(f"/api/freunde/{anna.user['id']}").status_code == 400
|
||||
assert anna.post("/api/freunde/9999").status_code == 404
|
||||
|
||||
|
||||
def test_suche(login):
|
||||
anna, ben = login("anna"), login("ben")
|
||||
login("bernd")
|
||||
anna.post(f"/api/freunde/{ben.user['id']}")
|
||||
res = anna.get("/api/freunde/suche", params={"q": "be"}).json()
|
||||
assert [(r["name"], r["status"]) for r in res] == [("Ben", "angefragt"), ("Bernd", None)]
|
||||
assert anna.get("/api/freunde/suche", params={"q": "a"}).json() == [] # zu kurz
|
||||
assert [r["name"] for r in anna.get("/api/freunde/suche", params={"q": "%"}).json()] == []
|
||||
@@ -10,51 +10,72 @@ 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, everyone=True, members=()):
|
||||
res = client.post("/api/haushalt/listen", json={"name": name, "everyone": everyone, "members": list(members)})
|
||||
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 test_neue_db_hat_liste_zuhause(login):
|
||||
data = state(login("anna"))
|
||||
assert [l["name"] for l in data["lists"]] == ["Zuhause"]
|
||||
assert data["lists"][0]["everyone"] is True
|
||||
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 = state(anna)["lists"][0]["id"]
|
||||
add(anna, home, "milch", quantity=2)
|
||||
data = add(anna, home, "Milch", quantity=1).json()
|
||||
assert [(i["name"], i["quantity"]) for i in items(data, home, "einkauf")] == [("Milch", 3)]
|
||||
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, "Milch", quantity=1.5, unit="l").json()
|
||||
assert len(items(data, home, "einkauf")) == 2
|
||||
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 = state(anna)["lists"][0]["id"]
|
||||
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, "vorrat") == []
|
||||
assert items(data, home_id, "vorrat") == []
|
||||
|
||||
|
||||
def test_private_liste_nur_fuer_mitglieder(login):
|
||||
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", everyone=False)
|
||||
geheim = new_list(anna, "Geheim")
|
||||
item = add(anna, geheim, "Kuchen").json()
|
||||
item_id = items(item, geheim)[0]["id"]
|
||||
|
||||
for other in (ben, chef): # auch Admins sehen fremde private Listen nicht
|
||||
assert all(l["id"] != geheim for l in state(other)["lists"])
|
||||
assert add(other, geheim, "x").status_code == 404
|
||||
assert other.patch(f"/api/haushalt/items/{item_id}", json={"quantity": 5}).status_code == 404
|
||||
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
|
||||
|
||||
anna.patch(f"/api/haushalt/listen/{geheim}", json={"name": "Geheim", "everyone": False, "members": [ben.user["id"]]})
|
||||
# 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
|
||||
@@ -71,53 +92,53 @@ def test_liste_loeschen_entfernt_eintraege(login, conn):
|
||||
|
||||
|
||||
def test_ungueltiges_mitglied(login):
|
||||
res = login("anna").post("/api/haushalt/listen", json={"name": "x", "everyone": False, "members": [9999]})
|
||||
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 = state(anna)["lists"][0]["id"]
|
||||
add(anna, home, "Milch", quantity=2)
|
||||
add(anna, home, "Brot")
|
||||
add(anna, home, "Milch", area="vorrat", quantity=1)
|
||||
milch = next(i for i in items(state(anna), home, "einkauf") if i["name"] == "Milch")
|
||||
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, "einkauf")} == {"Milch": True, "Brot": False}
|
||||
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}/einkauf-beenden")
|
||||
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, "einkauf")] == ["Brot"]
|
||||
assert [(i["name"], i["quantity"]) for i in items(data, home, "vorrat")] == [("Milch", 3)]
|
||||
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}/einkauf-beenden").status_code == 400
|
||||
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 = state(anna)["lists"][0]["id"]
|
||||
vorrat = items(add(anna, home, "Reis", area="vorrat").json(), home, "vorrat")[0]
|
||||
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 = state(anna)["lists"][0]["id"]
|
||||
milch = items(add(anna, home, "Milch").json(), home)[0]
|
||||
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, "Milch").json()
|
||||
assert items(data, home)[0]["checked"] is False
|
||||
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 = state(anna)["lists"][0]["id"]
|
||||
reis = items(add(anna, home, "Reis", area="vorrat", quantity=4).json(), home)[0]
|
||||
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)] == [("einkauf", 1)]
|
||||
assert [(i["list"], i["quantity"]) for i in items(data, home_id)] == [("einkauf", 1)]
|
||||
|
||||
|
||||
def test_rezept_uebernehmen_in_gewaehlte_liste(login):
|
||||
@@ -132,9 +153,9 @@ def test_rezept_uebernehmen_in_gewaehlte_liste(login):
|
||||
|
||||
def test_einheitenwechsel_fuehrt_zusammen(login):
|
||||
anna = login("anna")
|
||||
home = state(anna)["lists"][0]["id"]
|
||||
add(anna, home, "Mehl", quantity=1, unit="kg")
|
||||
stk = items(add(anna, home, "Mehl", quantity=2).json(), home)
|
||||
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)] == [(3, "kg")]
|
||||
assert [(i["quantity"], i["unit"]) for i in items(data, home_id)] == [(3, "kg")]
|
||||
@@ -56,9 +56,10 @@ def test_zurueckholen_startet_fristen_neu(login, conn):
|
||||
assert [x["title"] for x in data["tasks"]] == ["alt"]
|
||||
|
||||
|
||||
def test_loeschen_nur_ersteller_oder_admin(login):
|
||||
def test_loeschen_nur_ersteller_oder_admin(login, befriend):
|
||||
anna, ben, chef = login("anna"), login("ben"), login("chef", admin=True)
|
||||
pid = make_board(anna)
|
||||
befriend(anna, ben)
|
||||
pid = anna.post("/api/kanban/projekte", json={"name": "Test", "members": [ben.user["id"]]}).json()["id"]
|
||||
t1, t2 = task(anna, pid, "eins"), task(anna, pid, "zwei")
|
||||
assert ben.delete(f"/api/kanban/aufgaben/{t1['id']}").status_code == 403
|
||||
assert anna.delete(f"/api/kanban/aufgaben/{t1['id']}").status_code == 200
|
||||
|
||||
@@ -2,9 +2,11 @@ BERLIN = {"geonameId": 2950159, "name": "Berlin", "country": "DE", "lat": 52.52,
|
||||
LEIDEN = {"geonameId": 2751773, "name": "Leiden", "country": "NL", "lat": 52.16, "lon": 4.49}
|
||||
|
||||
|
||||
def test_staedte_anderer_nur_lesend(login):
|
||||
def test_staedte_anderer_nur_lesend(login, befriend):
|
||||
anna, ben = login("anna"), login("ben")
|
||||
anna.post("/api/reisen/staedte", json=BERLIN)
|
||||
assert ben.get("/api/reisen").json()["others"] == [] # erst nach Freundschaft sichtbar
|
||||
befriend(anna, ben)
|
||||
data = ben.get("/api/reisen").json()
|
||||
assert data["cities"] == []
|
||||
assert [(p["name"], [c["name"] for c in p["cities"]]) for p in data["others"]] == [("Anna", ["Berlin"])]
|
||||
@@ -51,8 +53,9 @@ def foto(client) -> str:
|
||||
return client.post("/api/uploads", content=buf.getvalue()).json()["file"]
|
||||
|
||||
|
||||
def test_wunschliste(login):
|
||||
def test_wunschliste(login, befriend):
|
||||
anna, ben = login("anna"), login("ben")
|
||||
befriend(anna, ben)
|
||||
data = anna.post("/api/reisen/staedte", json={**PRAG, "wish": True}).json()
|
||||
prag = data["cities"][0]
|
||||
assert prag["wish"] is True
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
"""Mindestbestand, Laden-Kategorien und Rezeptsammlung."""
|
||||
from app.kategorien import KEYS
|
||||
|
||||
from test_haushalt import add, items, new_list, state
|
||||
|
||||
|
||||
def home(client):
|
||||
return state(client)["lists"][0]["id"]
|
||||
from test_haushalt import add, home, items, new_list, state
|
||||
|
||||
|
||||
def amounts(data, list_id, name):
|
||||
@@ -49,7 +45,7 @@ def test_mindestbestand_aufgebraucht_und_sofort(login):
|
||||
|
||||
def test_mindestbestand_nur_mit_listenzugriff(login):
|
||||
anna, ben = login("anna"), login("ben")
|
||||
privat = new_list(anna, "Privat", everyone=False)
|
||||
privat = new_list(anna, "Privat")
|
||||
res = ben.put(f"/api/haushalt/listen/{privat}/mindestbestand", json={"name": "x", "unit": "stk", "minimum": 1})
|
||||
assert res.status_code == 404
|
||||
|
||||
@@ -70,7 +66,7 @@ def test_kategorie_geraten_und_aenderbar(login):
|
||||
|
||||
def test_kategorie_privater_produkte_nicht_aenderbar(login):
|
||||
anna, ben = login("anna"), login("ben")
|
||||
privat = new_list(anna, "Privat", everyone=False)
|
||||
privat = new_list(anna, "Privat")
|
||||
add(anna, privat, "Geheimzutat")
|
||||
assert ben.put("/api/haushalt/produkte/kategorie", json={"name": "Geheimzutat", "category": "obst"}).status_code == 404
|
||||
|
||||
@@ -78,10 +74,10 @@ def test_kategorie_privater_produkte_nicht_aenderbar(login):
|
||||
def test_laden_reihenfolge(login):
|
||||
anna = login("anna")
|
||||
lid = home(anna)
|
||||
assert state(anna)["lists"][0]["categoryOrder"] == KEYS
|
||||
assert next(l for l in state(anna)["lists"] if l["id"] == lid)["categoryOrder"] == KEYS
|
||||
order = list(reversed(KEYS))
|
||||
data = anna.put(f"/api/haushalt/listen/{lid}/reihenfolge", json={"order": order}).json()
|
||||
assert data["lists"][0]["categoryOrder"] == order
|
||||
assert next(l for l in data["lists"] if l["id"] == lid)["categoryOrder"] == order
|
||||
assert anna.put(f"/api/haushalt/listen/{lid}/reihenfolge", json={"order": KEYS[:3]}).status_code == 400
|
||||
|
||||
|
||||
@@ -126,7 +122,7 @@ def test_rezept_loeschen_nur_ersteller_oder_admin(login):
|
||||
|
||||
def test_rezept_vorschlag_verraet_keine_privaten_produkte(login):
|
||||
anna, ben = login("anna"), login("ben")
|
||||
privat = new_list(anna, "Privat", everyone=False)
|
||||
privat = new_list(anna, "Privat")
|
||||
add(anna, privat, "Kondome", area="vorrat")
|
||||
save(ben, "Test", ["2 Kondom"])
|
||||
res = ben.get("/api/haushalt/rezepte", params={"listId": home(ben)}).json()
|
||||
@@ -189,7 +185,7 @@ def test_im_laden_hinzufuegen(login):
|
||||
|
||||
def test_laden_fremder_liste_nicht_nutzbar(login):
|
||||
anna, ben = login("anna"), login("ben")
|
||||
privat = new_list(anna, "Privat", everyone=False)
|
||||
privat = new_list(anna, "Privat")
|
||||
fremd = new_store(anna, privat, "Geheimladen")
|
||||
lid = home(ben)
|
||||
assert ben.patch(f"/api/haushalt/laeden/{fremd}", json={"name": "x"}).status_code == 404
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
"""Privat-Modell: Kanban-Projekte nur für Mitglieder, Artikel lesen alle / bearbeiten Beteiligte, Admins dürfen alles."""
|
||||
from app import db
|
||||
|
||||
|
||||
def projekt(client, name="P", members=(), links=()):
|
||||
res = client.post("/api/kanban/projekte", json={"name": name, "members": list(members), "links": list(links)})
|
||||
assert res.status_code == 201, res.text
|
||||
return res.json()["id"]
|
||||
|
||||
|
||||
def artikel(client, title="Doku"):
|
||||
res = client.post("/api/projects", json={"title": title})
|
||||
assert res.status_code == 201, res.text
|
||||
return res.json()["id"]
|
||||
|
||||
|
||||
def test_kanban_privat(login, befriend):
|
||||
anna, ben, chef = login("anna"), login("ben"), login("chef", admin=True)
|
||||
pid = projekt(anna)
|
||||
assert ben.get("/api/kanban").json()["projects"] == []
|
||||
assert ben.get(f"/api/kanban/projekte/{pid}").status_code == 404
|
||||
assert ben.post(f"/api/kanban/projekte/{pid}/aufgaben", json={"title": "x"}).status_code == 404
|
||||
assert chef.get(f"/api/kanban/projekte/{pid}").status_code == 200 # Admin sieht alles
|
||||
# Mitglied nur als Freund
|
||||
assert anna.patch(f"/api/kanban/projekte/{pid}", json={"members": [ben.user["id"]]}).status_code == 400
|
||||
befriend(anna, ben)
|
||||
data = anna.patch(f"/api/kanban/projekte/{pid}", json={"members": [ben.user["id"]]}).json()
|
||||
assert data["project"]["members"] == [ben.user["id"]]
|
||||
assert ben.get(f"/api/kanban/projekte/{pid}").status_code == 200
|
||||
# Mitglieder verwalten nur Ersteller/Admin
|
||||
assert ben.patch(f"/api/kanban/projekte/{pid}", json={"members": []}).status_code == 403
|
||||
|
||||
|
||||
def test_bearbeiter_nur_beteiligte(login, befriend):
|
||||
anna, ben, carl = login("anna"), login("ben"), login("carl")
|
||||
befriend(anna, ben)
|
||||
pid = projekt(anna, members=[ben.user["id"]])
|
||||
data = anna.post(f"/api/kanban/projekte/{pid}/aufgaben", json={"title": "A", "assignee": ben.user["id"]}).json()
|
||||
task = data["tasks"][0]
|
||||
assert [p["name"] for p in data["people"]] == ["Anna", "Ben"]
|
||||
assert anna.patch(f"/api/kanban/aufgaben/{task['id']}", json={"assignee": carl.user["id"]}).status_code == 400
|
||||
# Ben fliegt raus → seine Aufgaben sind niemandem mehr zugeteilt
|
||||
data = anna.patch(f"/api/kanban/projekte/{pid}", json={"members": []}).json()
|
||||
assert data["tasks"][0]["assignee"] is None
|
||||
|
||||
|
||||
def test_artikel_lesen_alle_bearbeiten_beteiligte(login, befriend):
|
||||
anna, ben, carl, chef = login("anna"), login("ben"), login("carl"), login("chef", admin=True)
|
||||
aid = artikel(anna)
|
||||
assert ben.get(f"/api/projects/{aid}").json()["canEdit"] is False
|
||||
assert ben.patch(f"/api/projects/{aid}", json={"title": "x"}).status_code == 403
|
||||
# Ben arbeitet an einem verknüpften Kanban-Projekt mit → darf bearbeiten
|
||||
befriend(anna, ben)
|
||||
projekt(anna, members=[ben.user["id"]], links=[aid])
|
||||
assert ben.get(f"/api/projects/{aid}").json()["canEdit"] is True
|
||||
assert ben.patch(f"/api/projects/{aid}", json={"title": "Neu"}).status_code == 200
|
||||
assert carl.get(f"/api/projects/{aid}").status_code == 200 # lesen dürfen alle
|
||||
assert carl.patch(f"/api/projects/{aid}", json={"title": "x"}).status_code == 403
|
||||
assert chef.patch(f"/api/projects/{aid}", json={"title": "Admin"}).status_code == 200
|
||||
# Löschen bleibt Autor/Admin
|
||||
assert ben.delete(f"/api/projects/{aid}").status_code == 403
|
||||
|
||||
|
||||
def test_verknuepfen_nur_bearbeitbare_artikel(login):
|
||||
"""Sonst könnte man sich durch ein eigenes Kanban-Projekt Bearbeitungsrechte verschaffen."""
|
||||
anna, carl = login("anna"), login("carl")
|
||||
aid = artikel(anna)
|
||||
res = carl.post("/api/kanban/projekte", json={"name": "Trick", "links": [aid]})
|
||||
assert res.status_code == 400
|
||||
assert carl.patch(f"/api/projects/{aid}", json={"title": "x"}).status_code == 403
|
||||
|
||||
|
||||
def test_board_auf_artikelseite_nur_sichtbare(login):
|
||||
anna, ben = login("anna"), login("ben")
|
||||
aid = artikel(anna)
|
||||
projekt(anna, name="Intern", links=[aid])
|
||||
assert [b["name"] for b in anna.get(f"/api/projects/{aid}").json()["boards"]] == ["Intern"]
|
||||
assert ben.get(f"/api/projects/{aid}").json()["boards"] == []
|
||||
|
||||
|
||||
def test_umstellung_privat(conn, login):
|
||||
"""Schritt 6: bisher gemeinsame Listen/Projekte bekommen alle Konten als Mitglieder, ohne Besitzer → erster Admin."""
|
||||
anna, ben, chef = login("anna"), login("ben"), login("chef", admin=True)
|
||||
with conn:
|
||||
conn.execute("INSERT INTO shopping_lists (id, name, everyone, position, created_at, created_by) VALUES (50, 'Alt', 1, 1, 'x', ?)", (anna.user["id"],))
|
||||
conn.execute("INSERT INTO shopping_lists (id, name, everyone, position, created_at) VALUES (51, 'Ohne', 1, 2, 'x')")
|
||||
conn.execute("INSERT INTO kanban_projects (id, name, position, created_at, created_by, updated_at) VALUES (60, 'K', 1, 'x', ?, 'x')", (anna.user["id"],))
|
||||
conn.execute("PRAGMA user_version = 5")
|
||||
db.init()
|
||||
members = lambda lid: sorted(r[0] for r in conn.execute("SELECT user_id FROM shopping_list_members WHERE list_id = ?", (lid,)))
|
||||
assert members(50) == sorted([ben.user["id"], chef.user["id"]])
|
||||
assert conn.execute("SELECT created_by FROM shopping_lists WHERE id = 51").fetchone()[0] == chef.user["id"]
|
||||
assert sorted(r[0] for r in conn.execute("SELECT user_id FROM kanban_project_members WHERE project_id = 60")) == sorted([ben.user["id"], chef.user["id"]])
|
||||
assert conn.execute("SELECT COUNT(*) FROM shopping_lists WHERE everyone = 1").fetchone()[0] == 0
|
||||
assert any(l["id"] == 50 for l in ben.get("/api/haushalt").json()["lists"])
|
||||
Reference in new issue
Block a user