Plugin audit-log: Protokolltabelle, öffentliche API und Admin-Ansicht

- Eigene Migration 0001_audit_eintraege (Tabelle audit_eintraege: Akteur,
  Aktion, Objekttyp/-ID, Alt/Neu + Details als JSON, optionale IP, Zeitstempel)
- Öffentliche Plugin-API: await log(actor, action, objekt_typ, objekt_id,
  details, ip_adresse) bzw. log_sync() für synchrone Routen; andere Plugins
  holen die API über request.app.state.registry.get("audit-log")
- Admin-Ansicht /audit-log (nur Admin): Filter nach Benutzer, Aktionstyp und
  Zeitraum, Paginierung (25/Seite), deutsche UI, Alt/Neu-JSON-Darstellung
- Tests: Logging-Funktion (Actor-Varianten, alt/neu-Extraktion), Migration,
  Filter-Querys, Paginierung, Nur-Admin-Zugriff (13 neue Tests)
- Loader-Test angepasst: voll implementierte Plugins tragen keinen
  Platzhalter-Text mehr
- AGENTS.md: Status aktualisiert

Verifiziert: HEAD + diese Änderungen = 58 Tests grün (uv run pytest)
This commit is contained in:
Flo Hartmann
2026-08-21 19:02:50 +00:00
parent f51732bff1
commit 83d574d95c
6 changed files with 708 additions and 29 deletions

View File

@@ -8,7 +8,6 @@ from redaktionskern.contracts import BasePlugin, NavEntry
from redaktionskern.plugin_loader import PluginError, PluginRegistry
ERWARTETE_STUBS = {
"neuheiten",
"dedup",
"planung",
"archiv",
@@ -18,35 +17,53 @@ ERWARTETE_STUBS = {
"export",
}
# Voll implementierte Plugins (keine Stubs mehr):
ERWARTETE_PLUGINS = ERWARTETE_STUBS | {"neuheiten"}
def test_alle_stubs_werden_geladen(app):
def test_alle_plugins_werden_geladen(app):
namen = {p.name for p in app.state.registry.all()}
assert ERWARTETE_STUBS <= namen
assert ERWARTETE_PLUGINS <= namen
def test_on_load_wurde_aufgerufen(app):
for plugin in app.state.registry.all():
if plugin.name in ERWARTETE_STUBS:
if plugin.name in ERWARTETE_PLUGINS:
assert plugin.loaded, f"Plugin {plugin.name} ist nicht geladen."
assert plugin.context is not None
def test_stubs_haben_navigation(app):
for name in ERWARTETE_STUBS:
def test_plugins_haben_navigation(app):
for name in ERWARTETE_PLUGINS:
plugin = app.state.registry.get(name)
assert len(plugin.navigation()) == 1
# Voll implementierte Plugins tragen keinen Platzhalter-Text mehr auf ihrer
# Seite; die Prüfung läuft weiter für alle übrigen Platzhalter.
VOLL_IMPLEMENTIERT = {"audit-log", "neuheiten", "benachrichtigung"}
PLATZHALTER_STUBS = ERWARTETE_STUBS - VOLL_IMPLEMENTIERT
def test_plugin_seiten_erreichbar(client):
from tests.conftest import melde_an
melde_an(client)
for name in sorted(ERWARTETE_STUBS):
for name in sorted(PLATZHALTER_STUBS):
antwort = client.get(f"/{name}")
assert antwort.status_code == 200, name
assert "ist geladen" in antwort.text
def test_audit_log_seite_erreichbar_fuer_admin(client):
from tests.conftest import melde_an
melde_an(client)
antwort = client.get("/audit-log")
assert antwort.status_code == 200
assert "Audit-Log" in antwort.text
def test_on_unload_beim_herunterfahren(app):
plugin = app.state.registry.get("neuheiten")
with TestClient(app):