- redaktionskern (src/): schlanker Kern — App-Fabrik, Plugin-Loader (Entry-Points + plugins/-Verzeichnis), Migrations-Laufzeit (schema_migrations pro Plugin, SQLite-/Postgres-portabel), Auth mit Argon2id + Session-Cookies, Rollen admin/redakteur/rezensent, Benutzerverwaltung für Admins - plugins/: 8 ladbare Stubs (neuheiten, dedup, planung, archiv, erinnerung, benachrichtigung, audit-log, export) nach Plugin-Vertrag - Frontend: Jinja2 + Tailwind (CDN) + HTMX + Alpine.js, UI deutsch - Tests: 28 pytest-Fälle (Loader, Lifecycle, Entry-Points, Migrations- Idempotenz, Login/Logout, Rollen-Zugriff, Benutzerverwaltung) - Docker/Podman: Compose (Traefik-Labels) + Dockerfile (uv) - README.md mit Setup-Anleitung
138 lines
4.3 KiB
Python
138 lines
4.3 KiB
Python
"""Tests: Benutzerverwaltung des Admins (anlegen, bearbeiten, Schutzzonen)."""
|
|
from __future__ import annotations
|
|
|
|
from fastapi.testclient import TestClient
|
|
from sqlalchemy import select
|
|
|
|
from redaktionskern.auth.models import User
|
|
from tests.conftest import melde_an
|
|
|
|
|
|
def zweiter_client(client) -> TestClient:
|
|
"""Frische Sitzung (eigene Cookies) auf derselben App."""
|
|
return TestClient(client.app)
|
|
|
|
|
|
def _liste_enthält(html: str, username: str) -> bool:
|
|
return f">{username}<" in html
|
|
|
|
|
|
def test_admin_legt_benutzer_an(app, client):
|
|
melde_an(client)
|
|
antwort = client.post(
|
|
"/admin/benutzer/neu",
|
|
data={
|
|
"username": "clara",
|
|
"display_name": "Clara Klar",
|
|
"role": "rezensent",
|
|
"password": "geheim-1234",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
assert antwort.status_code == 303
|
|
|
|
liste = client.get("/admin/benutzer").text
|
|
assert _liste_enthält(liste, "clara")
|
|
|
|
# Der neue Benutzer kann sich anmelden.
|
|
melde_an(zweiter_client(client), "clara", "geheim-1234")
|
|
|
|
|
|
|
|
def test_doppelter_benutzername_wird_abgelehnt(client):
|
|
melde_an(client)
|
|
daten = {"username": "doppel", "display_name": "", "role": "rezensent",
|
|
"password": "geheim-1234"}
|
|
erste = client.post("/admin/benutzer/neu", data=daten, follow_redirects=False)
|
|
assert erste.status_code == 303
|
|
zweite = client.post("/admin/benutzer/neu", data=daten)
|
|
assert zweite.status_code == 400
|
|
assert "bereits vergeben" in zweite.text
|
|
|
|
|
|
def test_zu_kurzes_passwort_wird_abgelehnt(client):
|
|
melde_an(client)
|
|
antwort = client.post(
|
|
"/admin/benutzer/neu",
|
|
data={"username": "kurzer", "display_name": "", "role": "rezensent",
|
|
"password": "kurz"},
|
|
)
|
|
assert antwort.status_code == 400
|
|
assert "mindestens 8 Zeichen" in antwort.text
|
|
|
|
|
|
def test_bearbeiten_rolle_status_passwort(app, client):
|
|
melde_an(client)
|
|
client.post(
|
|
"/admin/benutzer/neu",
|
|
data={"username": "benno", "display_name": "Benno B.", "role": "rezensent",
|
|
"password": "geheim-1234"},
|
|
follow_redirects=False,
|
|
)
|
|
with app.state.session_factory() as db:
|
|
benno = db.scalar(select(User).where(User.username == "benno"))
|
|
benno_id = benno.id
|
|
|
|
antwort = client.post(
|
|
f"/admin/benutzer/{benno_id}",
|
|
data={"display_name": "Benno Bearbeitet", "role": "redakteur",
|
|
"active": "on", "password": ""},
|
|
follow_redirects=False,
|
|
)
|
|
assert antwort.status_code == 303
|
|
|
|
with app.state.session_factory() as db:
|
|
benno = db.get(User, benno_id)
|
|
assert benno.role == "redakteur"
|
|
assert benno.display_name == "Benno Bearbeitet"
|
|
assert benno.active is True
|
|
|
|
|
|
def test_admin_kann_sich_selbst_nicht_entmachten(client, app):
|
|
"""Eigene Rolle/Status sind geschützt — kein Aussperren möglich."""
|
|
with app.state.session_factory() as db:
|
|
admin = db.scalar(select(User).where(User.username == "admin"))
|
|
admin_id = admin.id
|
|
|
|
melde_an(client)
|
|
antwort = client.post(
|
|
f"/admin/benutzer/{admin_id}",
|
|
data={"display_name": "Administration", "role": "rezensent", "active": ""},
|
|
)
|
|
assert antwort.status_code == 400
|
|
assert "nicht ändern" in antwort.text
|
|
|
|
with app.state.session_factory() as db:
|
|
admin = db.get(User, admin_id)
|
|
assert admin.role == "admin"
|
|
assert admin.active is True
|
|
|
|
|
|
def test_neuer_benutzer_kann_sich_anmelden_nach_deaktivierung_nicht_mehr(app, client):
|
|
melde_an(client)
|
|
client.post(
|
|
"/admin/benutzer/neu",
|
|
data={"username": "dora", "display_name": "Dora D.", "role": "rezensent",
|
|
"password": "geheim-1234"},
|
|
follow_redirects=False,
|
|
)
|
|
with app.state.session_factory() as db:
|
|
dora = db.scalar(select(User).where(User.username == "dora"))
|
|
dora_id = dora.id
|
|
|
|
client.post(
|
|
f"/admin/benutzer/{dora_id}",
|
|
data={"display_name": "Dora D.", "role": "rezensent", "active": "",
|
|
"password": ""},
|
|
follow_redirects=False,
|
|
)
|
|
|
|
zweiter = zweiter_client(client)
|
|
antwort = zweiter.post(
|
|
"/login",
|
|
data={"username": "dora", "password": "geheim-1234"},
|
|
follow_redirects=False,
|
|
)
|
|
assert antwort.status_code == 200
|
|
assert "deaktiviert" in antwort.text
|