Drei P3-Funde aus der Durchsicht vom 14.09.: Reservieren: Statusprüfung und Schreiben waren getrennt - zwei gleichzeitige Anfragen konnten beide passieren, die zweite überschrieb Name und Token der ersten, ohne dass die es erfuhr. Jetzt entscheidet ein UPDATE mit Status-Bedingung; der Verlierer bekommt None und die Route meldet "schon weg" (Seite) bzw. 409 (API). /?limit=abc lieferte jedem anonymen Besucher einen internen Serverfehler, limit=-1 hiess in SQLite "alles". _limit_lesen() fällt bei Unsinn auf die Seitengrösse zurück und deckelt bei 500. Erfassen/Bearbeiten: int(category_id) und die Pydantic-Prüfung warfen im Handler - 500 statt Fehlermeldung. Jetzt Meldung; ausserdem werden die Angaben VOR den Bildern geprüft, damit bei abgelehnten Angaben keine verwaisten Bilddateien liegen bleiben. Vier neue Tests, jeder einmal gegen den alten Code gelaufen und dabei rot geworden. 84 lokal grün (HEIC-Test braucht pillow-heif, Docker). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
108 lines
3.9 KiB
Python
108 lines
3.9 KiB
Python
"""Tests rund ums Reservieren.
|
|
|
|
Schwerpunkt ist die Berechtigung: die Galerie ist frei zugänglich, also
|
|
darf ein offener release-Endpunkt nicht fremde Reservierungen löschen
|
|
können.
|
|
"""
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture
|
|
def item_id(client, kategorie_id):
|
|
return client.post(
|
|
"/api/v1/items", json={"size": "98/104", "category_id": kategorie_id}
|
|
).json()["id"]
|
|
|
|
|
|
def test_reservieren(client, item_id):
|
|
antwort = client.post(
|
|
f"/api/v1/items/{item_id}/reserve", json={"reserved_by": "Familie Meier"}
|
|
)
|
|
assert antwort.status_code == 200
|
|
daten = antwort.json()
|
|
assert daten["item"]["status"] == "reserved"
|
|
assert daten["item"]["reserved_by"] == "Familie Meier"
|
|
assert len(daten["reservation_token"]) > 20
|
|
|
|
|
|
def test_zweimal_reservieren_geht_nicht(client, item_id):
|
|
client.post(f"/api/v1/items/{item_id}/reserve", json={"reserved_by": "A"})
|
|
zweiter = client.post(f"/api/v1/items/{item_id}/reserve", json={"reserved_by": "B"})
|
|
assert zweiter.status_code == 409
|
|
|
|
|
|
def test_freigeben_mit_token(client, item_id):
|
|
token = client.post(
|
|
f"/api/v1/items/{item_id}/reserve", json={"reserved_by": "Familie Meier"}
|
|
).json()["reservation_token"]
|
|
|
|
antwort = client.post(f"/api/v1/items/{item_id}/release?token={token}")
|
|
assert antwort.status_code == 200
|
|
assert antwort.json()["status"] == "available"
|
|
assert antwort.json()["reserved_by"] is None
|
|
|
|
|
|
def test_freigeben_ohne_token_wird_abgewiesen(client, gast, item_id):
|
|
"""Der Kern der Entscheidung: sonst löscht jeder Besucher fremde
|
|
Reservierungen.
|
|
|
|
Bewusst als Gast geprüft - der angemeldete Betreiber darf das sehr wohl
|
|
ohne Token, damit er eine Missbrauchswelle aufräumen kann.
|
|
"""
|
|
client.post(f"/api/v1/items/{item_id}/reserve", json={"reserved_by": "Familie Meier"})
|
|
client.post("/api/v1/auth/logout")
|
|
|
|
assert gast.post(f"/api/v1/items/{item_id}/release").status_code == 403
|
|
assert gast.post(f"/api/v1/items/{item_id}/release?token=falsch").status_code == 403
|
|
|
|
# Reservierung steht unverändert
|
|
item = gast.get(f"/api/v1/items/{item_id}").json()
|
|
assert item["status"] == "reserved"
|
|
assert item["reserved_by"] == "Familie Meier"
|
|
|
|
|
|
def test_token_gilt_nach_dem_freigeben_nicht_mehr(client, gast, item_id):
|
|
"""Sonst könnte ein alter Link später die Reservierung einer anderen
|
|
Person aufheben."""
|
|
token = client.post(
|
|
f"/api/v1/items/{item_id}/reserve", json={"reserved_by": "Erste"}
|
|
).json()["reservation_token"]
|
|
client.post(f"/api/v1/items/{item_id}/release?token={token}")
|
|
|
|
client.post(f"/api/v1/items/{item_id}/reserve", json={"reserved_by": "Zweite"})
|
|
client.post("/api/v1/auth/logout")
|
|
assert gast.post(f"/api/v1/items/{item_id}/release?token={token}").status_code == 403
|
|
|
|
|
|
def test_abgegeben_loescht_den_namen(client, item_id):
|
|
"""Datensparsam - die Galerie ist öffentlich."""
|
|
client.post(f"/api/v1/items/{item_id}/reserve", json={"reserved_by": "Familie Meier"})
|
|
antwort = client.post(f"/api/v1/items/{item_id}/mark-given").json()
|
|
assert antwort["status"] == "given_away"
|
|
assert antwort["reserved_by"] is None
|
|
|
|
|
|
def test_name_ohne_werbung(client, item_id):
|
|
antwort = client.post(
|
|
f"/api/v1/items/{item_id}/reserve",
|
|
json={"reserved_by": "Jetzt billig kaufen http://spam.example"},
|
|
)
|
|
assert antwort.status_code == 422
|
|
|
|
|
|
def test_wettlauf_beim_reservieren_gewinnt_nur_einer(client, item_id, db_sitzung):
|
|
"""Zwei Anfragen können beide die Statusprüfung in der Route passieren,
|
|
bevor eine schreibt. Das UPDATE mit Status-Bedingung lässt trotzdem nur
|
|
eine gewinnen - die zweite bekommt None und überschreibt nichts."""
|
|
from app import crud
|
|
|
|
item = crud.item_holen(db_sitzung, item_id)
|
|
erster = crud.reservieren(db_sitzung, item, "A")
|
|
zweiter = crud.reservieren(db_sitzung, item, "B")
|
|
|
|
assert erster is not None
|
|
assert zweiter is None
|
|
assert item.reserved_by == "A"
|
|
assert item.reservation_token is not None
|