- 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>
171 lines
8.5 KiB
Python
171 lines
8.5 KiB
Python
BERLIN = {"geonameId": 2950159, "name": "Berlin", "country": "DE", "lat": 52.52, "lon": 13.41, "population": 3426354, "capital": True}
|
|
LEIDEN = {"geonameId": 2751773, "name": "Leiden", "country": "NL", "lat": 52.16, "lon": 4.49}
|
|
|
|
|
|
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"])]
|
|
city_id = data["others"][0]["cities"][0]["id"]
|
|
assert ben.delete(f"/api/reisen/staedte/{city_id}").status_code == 404
|
|
assert anna.get("/api/reisen").json()["others"] == []
|
|
|
|
|
|
def test_hauptstadt_kennzeichen(login):
|
|
anna = login("anna")
|
|
anna.post("/api/reisen/staedte", json=BERLIN)
|
|
data = anna.post("/api/reisen/staedte", json=LEIDEN).json()
|
|
# Mit Angabe aus der Suche gespeichert, ohne Angabe "unbekannt" (wird im Hintergrund nachgetragen)
|
|
assert {c["name"]: c["capital"] for c in data["cities"]} == {"Berlin": True, "Leiden": None}
|
|
|
|
|
|
def test_doppelte_stadt_wird_ignoriert(login):
|
|
anna = login("anna")
|
|
anna.post("/api/reisen/staedte", json=BERLIN)
|
|
assert len(anna.post("/api/reisen/staedte", json=BERLIN).json()["cities"]) == 1
|
|
|
|
|
|
def test_land_markieren(login):
|
|
anna = login("anna")
|
|
assert anna.put("/api/reisen/laender/IS").json()["countries"] == ["IS"]
|
|
assert anna.delete("/api/reisen/laender/IS").json()["countries"] == []
|
|
assert anna.put("/api/reisen/laender/xx").status_code == 400
|
|
|
|
|
|
# ---------- Wunschliste und Besuche ----------
|
|
|
|
import io # noqa: E402
|
|
|
|
from PIL import Image # noqa: E402
|
|
|
|
from app.config import DATA_DIR # noqa: E402
|
|
|
|
PRAG = {"geonameId": 3067696, "name": "Prag", "country": "CZ", "lat": 50.08, "lon": 14.42, "capital": True}
|
|
|
|
|
|
def foto(client) -> str:
|
|
buf = io.BytesIO()
|
|
Image.new("RGB", (8, 8), "blue").save(buf, "JPEG")
|
|
return client.post("/api/uploads", content=buf.getvalue()).json()["file"]
|
|
|
|
|
|
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
|
|
assert ben.get("/api/reisen").json()["others"] == [] # Wünsche sehen andere nicht
|
|
data = anna.patch(f"/api/reisen/staedte/{prag['id']}", json={"wish": False}).json()
|
|
assert data["cities"][0]["wish"] is False
|
|
assert [p["name"] for p in ben.get("/api/reisen").json()["others"]] == ["Anna"]
|
|
|
|
|
|
def test_besuch_mit_fotos(login):
|
|
anna, ben = login("anna"), login("ben")
|
|
prag = anna.post("/api/reisen/staedte", json={**PRAG, "wish": True}).json()["cities"][0]
|
|
f1, f2 = foto(anna), foto(anna)
|
|
data = anna.post(f"/api/reisen/staedte/{prag['id']}/besuche", json={
|
|
"dateFrom": "2024-03-01", "dateTo": "2024-03-04", "companions": "Ben", "notes": "Karlsbrücke", "photos": [f1, f2],
|
|
}).json()
|
|
assert data["cities"][0]["wish"] is False # Besuch macht aus dem Wunsch einen Besuch
|
|
visit = data["visits"][0]
|
|
assert (visit["dateFrom"], visit["dateTo"], visit["companions"], visit["photos"]) == ("2024-03-01", "2024-03-04", "Ben", [f1, f2])
|
|
assert ben.get("/api/reisen").json().get("visits") == [] # Besuche sind privat
|
|
# Foto entfernt → Datei gelöscht; Besuch gelöscht → alle Fotos weg
|
|
anna.put(f"/api/reisen/besuche/{visit['id']}", json={**visit, "photos": [f1]})
|
|
assert not (DATA_DIR / "uploads" / f2).exists()
|
|
anna.delete(f"/api/reisen/besuche/{visit['id']}")
|
|
assert not (DATA_DIR / "uploads" / f1).exists()
|
|
|
|
|
|
def test_stadt_loeschen_entfernt_fotos(login):
|
|
anna = login("anna")
|
|
prag = anna.post("/api/reisen/staedte", json=PRAG).json()["cities"][0]
|
|
f = foto(anna)
|
|
anna.post(f"/api/reisen/staedte/{prag['id']}/besuche", json={"dateFrom": "2023-05-01", "photos": [f]})
|
|
anna.delete(f"/api/reisen/staedte/{prag['id']}")
|
|
assert not (DATA_DIR / "uploads" / f).exists()
|
|
|
|
|
|
def test_besuch_pruefungen(login):
|
|
anna, ben = login("anna"), login("ben")
|
|
prag = anna.post("/api/reisen/staedte", json=PRAG).json()["cities"][0]
|
|
url = f"/api/reisen/staedte/{prag['id']}/besuche"
|
|
assert anna.post(url, json={"dateFrom": "2024-13-01"}).status_code == 400
|
|
assert anna.post(url, json={"dateFrom": "2024-05-02", "dateTo": "2024-05-01"}).status_code == 400
|
|
assert anna.post(url, json={"dateFrom": "2024-05-02", "photos": ["../../etc/passwd"]}).status_code == 400
|
|
assert anna.post(url, json={"dateFrom": "2024-05-02", "photos": ["9e" * 12 + ".jpg"]}).status_code == 400 # gibt es nicht
|
|
assert ben.post(url, json={"dateFrom": "2024-05-02"}).status_code == 404 # fremde Stadt
|
|
|
|
|
|
def test_reisefotos_ueberleben_aufraeumen(login):
|
|
"""Das Aufräumen verwaister Bilder darf Reisefotos nicht löschen."""
|
|
from app import projects
|
|
anna = login("anna")
|
|
prag = anna.post("/api/reisen/staedte", json=PRAG).json()["cities"][0]
|
|
f = foto(anna)
|
|
anna.post(f"/api/reisen/staedte/{prag['id']}/besuche", json={"dateFrom": "2023-05-01", "photos": [f]})
|
|
import os, time
|
|
old = time.time() - 3 * 24 * 3600
|
|
os.utime(DATA_DIR / "uploads" / f, (old, old))
|
|
projects._cleanup_orphans()
|
|
assert (DATA_DIR / "uploads" / f).exists()
|
|
|
|
|
|
def test_wohnorte_mit_datum(login):
|
|
anna, ben = login("anna"), login("ben")
|
|
berlin = {k: BERLIN[k] for k in ("geonameId", "name", "country", "lat", "lon")}
|
|
prag = {k: PRAG[k] for k in ("geonameId", "name", "country", "lat", "lon")}
|
|
anna.post("/api/reisen/wohnorte", json=berlin)
|
|
data = anna.post("/api/reisen/wohnorte", json={**prag, "since": "2024-01-01"}).json()
|
|
assert [(h["name"], h["since"]) for h in data["homes"]] == [("Berlin", None), ("Prag", "2024-01-01")]
|
|
# gleiches Datum ersetzt
|
|
data = anna.post("/api/reisen/wohnorte", json={**berlin, "since": "2024-01-01"}).json()
|
|
assert [(h["name"], h["since"]) for h in data["homes"]] == [("Berlin", None), ("Berlin", "2024-01-01")]
|
|
assert ben.get("/api/reisen").json()["homes"] == []
|
|
assert anna.post("/api/reisen/wohnorte", json={**berlin, "since": "2024-02-30"}).status_code == 400
|
|
assert ben.delete(f"/api/reisen/wohnorte/{data['homes'][0]['id']}").status_code == 404
|
|
assert len(anna.delete(f"/api/reisen/wohnorte/{data['homes'][0]['id']}").json()["homes"]) == 1
|
|
|
|
|
|
def test_hier_gewohnt(login):
|
|
anna = login("anna")
|
|
berlin = {k: BERLIN[k] for k in ("geonameId", "name", "country", "lat", "lon")}
|
|
anna.post("/api/reisen/wohnorte", json=berlin)
|
|
prag = anna.post("/api/reisen/staedte", json=PRAG).json()["cities"][0]
|
|
visit = anna.post(f"/api/reisen/staedte/{prag['id']}/besuche", json={"dateFrom": "2023-07-27", "dateTo": "2024-06-16"}).json()["visits"][0]
|
|
data = anna.post(f"/api/reisen/besuche/{visit['id']}/gewohnt").json()
|
|
assert [(h["name"], h["since"]) for h in data["homes"]] == [("Berlin", None), ("Prag", "2023-07-27"), ("Berlin", "2024-06-17")]
|
|
# Ohne "Bis" kein Aufenthalt
|
|
kurz = anna.post(f"/api/reisen/staedte/{prag['id']}/besuche", json={"dateFrom": "2025-01-01"}).json()["visits"][0]
|
|
assert anna.post(f"/api/reisen/besuche/{kurz['id']}/gewohnt").status_code == 400
|
|
|
|
|
|
def test_anreise_je_besuch(login):
|
|
anna = login("anna")
|
|
prag = anna.post("/api/reisen/staedte", json=PRAG).json()["cities"][0]
|
|
url = f"/api/reisen/staedte/{prag['id']}/besuche"
|
|
data = anna.post(url, json={"dateFrom": "2024-05-01", "continued": True}).json()
|
|
assert data["visits"][0]["continued"] is True
|
|
v = data["visits"][0]
|
|
data = anna.put(f"/api/reisen/besuche/{v['id']}", json={**v, "continued": None}).json()
|
|
assert data["visits"][0]["continued"] is None
|
|
assert anna.post(url, json={"dateFrom": "2024-05-01", "continued": "ja"}).status_code == 400
|
|
|
|
|
|
def test_besuch_bearbeitbar_wenn_fotodatei_fehlt(login):
|
|
anna = login("anna")
|
|
prag = anna.post("/api/reisen/staedte", json=PRAG).json()["cities"][0]
|
|
f = foto(anna)
|
|
v = anna.post(f"/api/reisen/staedte/{prag['id']}/besuche", json={"dateFrom": "2023-05-01", "photos": [f]}).json()["visits"][0]
|
|
(DATA_DIR / "uploads" / f).unlink()
|
|
res = anna.put(f"/api/reisen/besuche/{v['id']}", json={**v, "notes": "geändert"})
|
|
assert res.status_code == 200 and res.json()["visits"][0]["notes"] == "geändert"
|
|
# Neue, nicht vorhandene Fotos werden weiter abgelehnt
|
|
assert anna.put(f"/api/reisen/besuche/{v['id']}", json={**v, "photos": [f, "9e" * 12 + ".jpg"]}).status_code == 400
|