Zwei Ebenen in einer Tabelle, nach dem Vorbild von DashboardLayout: user_id NULL ist die Vorgabe des Administrators, eine Zeile mit user_id die zusaetzliche Auswahl eines Kontos. Beide Listen gelten zusammen - wer etwas versteckt, versteckt es zusaetzlich; einblenden, was der Administrator ausgeblendet hat, kann niemand. Das erspart einen dritten Zustand je Eintrag. Als Schluessel dient der Routenpfad; der ist ohnehin eindeutig, eine zweite Benennung waere nur eine weitere Stelle zum Auseinanderlaufen. Drei Routen: lesen (Vorgabe, eigene Auswahl und die fertige Vereinigung in einem Zug), eigene Auswahl setzen, Vorgabe setzen (nur Admin). Die Eindeutigkeit laeuft ueber einen Index auf coalesce(user_id, -1). Ein schlichtes UNIQUE(user_id) haette zwei Vorgabezeilen zugelassen, weil NULL in SQL nie gleich NULL ist - das war im Entwurf noch falsch. Versteckt wird nur der Eintrag, nicht die Seite: Lesezeichen, Dashboard-Karten und Lagerort-QR sollen weiter funktionieren. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
138 lines
4.4 KiB
Python
138 lines
4.4 KiB
Python
"""Seitenleiste ausblenden: Vorgabe des Admins, eigene Auswahl, Vereinigung."""
|
||
|
||
import json
|
||
|
||
import pytest
|
||
from fastapi import HTTPException
|
||
from sqlalchemy.exc import IntegrityError
|
||
|
||
from app.models import NavigationSichtbarkeit, Role, User
|
||
from app.routers.navigation import (
|
||
eigene_auswahl_setzen,
|
||
navigation_lesen,
|
||
vorgabe_setzen,
|
||
)
|
||
from app.schemas import NavigationSetzen
|
||
|
||
|
||
@pytest.fixture()
|
||
def admin(db):
|
||
person = User(username="admin", password_hash="x", role=Role.admin)
|
||
db.add(person)
|
||
db.commit()
|
||
db.refresh(person)
|
||
return person
|
||
|
||
|
||
@pytest.fixture()
|
||
def mitbewohner(db):
|
||
person = User(username="mitbewohner", password_hash="x", role=Role.user)
|
||
db.add(person)
|
||
db.commit()
|
||
db.refresh(person)
|
||
return person
|
||
|
||
|
||
def _setze_vorgabe(db, admin, werte):
|
||
return vorgabe_setzen(NavigationSetzen(versteckt=werte), db=db, user=admin)
|
||
|
||
|
||
def _setze_eigen(db, user, werte):
|
||
return eigene_auswahl_setzen(NavigationSetzen(versteckt=werte), db=db, user=user)
|
||
|
||
|
||
# ---- Vorgabe ----
|
||
|
||
def test_ohne_eintrag_ist_nichts_versteckt(db, admin):
|
||
out = navigation_lesen(db=db, user=admin)
|
||
assert out.vorgabe == [] and out.eigen == [] and out.versteckt == []
|
||
|
||
|
||
def test_vorgabe_gilt_fuer_alle(db, admin, mitbewohner):
|
||
_setze_vorgabe(db, admin, ["/rezepte"])
|
||
assert navigation_lesen(db=db, user=mitbewohner).versteckt == ["/rezepte"]
|
||
assert navigation_lesen(db=db, user=admin).versteckt == ["/rezepte"]
|
||
|
||
|
||
def test_normaler_nutzer_darf_die_vorgabe_nicht_setzen(db, mitbewohner):
|
||
"""require_admin greift – geprueft ueber die Rolle, wie in deps.py."""
|
||
from app.deps import require_admin
|
||
|
||
with pytest.raises(HTTPException) as fehler:
|
||
require_admin(mitbewohner)
|
||
assert fehler.value.status_code == 403
|
||
|
||
|
||
# ---- Eigene Auswahl ----
|
||
|
||
def test_eigene_auswahl_betrifft_nur_einen(db, admin, mitbewohner):
|
||
_setze_eigen(db, mitbewohner, ["/history"])
|
||
assert navigation_lesen(db=db, user=mitbewohner).versteckt == ["/history"]
|
||
assert navigation_lesen(db=db, user=admin).versteckt == []
|
||
|
||
|
||
def test_eigene_auswahl_kommt_zur_vorgabe_dazu(db, admin, mitbewohner):
|
||
_setze_vorgabe(db, admin, ["/rezepte"])
|
||
_setze_eigen(db, mitbewohner, ["/history"])
|
||
|
||
out = navigation_lesen(db=db, user=mitbewohner)
|
||
assert out.vorgabe == ["/rezepte"]
|
||
assert out.eigen == ["/history"]
|
||
assert out.versteckt == ["/history", "/rezepte"]
|
||
|
||
|
||
def test_nutzer_kann_die_vorgabe_nicht_aufheben(db, admin, mitbewohner):
|
||
"""Die eigene Auswahl addiert nur – sie nimmt nichts weg."""
|
||
_setze_vorgabe(db, admin, ["/shopping"])
|
||
_setze_eigen(db, mitbewohner, [])
|
||
assert navigation_lesen(db=db, user=mitbewohner).versteckt == ["/shopping"]
|
||
|
||
|
||
# ---- Speicherung ----
|
||
|
||
def test_zweimal_setzen_legt_keine_zweite_zeile_an(db, mitbewohner):
|
||
_setze_eigen(db, mitbewohner, ["/history"])
|
||
_setze_eigen(db, mitbewohner, ["/verliehen"])
|
||
|
||
zeilen = db.query(NavigationSichtbarkeit).all()
|
||
assert len(zeilen) == 1
|
||
assert json.loads(zeilen[0].versteckt) == ["/verliehen"]
|
||
|
||
|
||
def test_nur_eine_vorgabezeile(db, admin):
|
||
"""Der Teilindex muss eine zweite Vorgabe verhindern."""
|
||
_setze_vorgabe(db, admin, ["/rezepte"])
|
||
|
||
db.add(NavigationSichtbarkeit(user_id=None, versteckt='["/shopping"]'))
|
||
with pytest.raises(IntegrityError):
|
||
db.commit()
|
||
db.rollback()
|
||
|
||
|
||
def test_nur_eine_zeile_je_konto(db, mitbewohner):
|
||
_setze_eigen(db, mitbewohner, ["/history"])
|
||
|
||
db.add(NavigationSichtbarkeit(user_id=mitbewohner.id, versteckt="[]"))
|
||
with pytest.raises(IntegrityError):
|
||
db.commit()
|
||
db.rollback()
|
||
|
||
|
||
def test_doppelte_und_leere_eintraege_fallen_weg(db, mitbewohner):
|
||
out = _setze_eigen(db, mitbewohner, ["/history", "/history", "", " ", "/verliehen"])
|
||
assert out.eigen == ["/history", "/verliehen"]
|
||
|
||
|
||
def test_kaputter_inhalt_sperrt_niemanden_aus(db, mitbewohner):
|
||
"""Lieber die volle Leiste als eine Fehlermeldung."""
|
||
db.add(NavigationSichtbarkeit(user_id=mitbewohner.id, versteckt="kein JSON"))
|
||
db.commit()
|
||
assert navigation_lesen(db=db, user=mitbewohner).versteckt == []
|
||
|
||
|
||
def test_unbekannter_schluessel_wird_mitgefuehrt_aber_stoert_nicht(db, mitbewohner):
|
||
"""Ein umbenannter Eintrag darf die Leiste nicht zerreissen – der Server
|
||
kennt die Pfade gar nicht, die Oberflaeche ignoriert Unbekanntes."""
|
||
out = _setze_eigen(db, mitbewohner, ["/gibtsnichtmehr"])
|
||
assert out.versteckt == ["/gibtsnichtmehr"]
|