refactor: form-core wiring, JWT sessions, slowapi, packaging, frontend rewrite, test declutter

This commit is contained in:
Jonas Miederer 2026-09-28 18:03:07 +02:00
parent 06ab25fb47
commit 03af41e837
74 changed files with 6089 additions and 5183 deletions

View file

@ -93,6 +93,9 @@ async def test_request_approve_consume_me_logout(client, session):
assert me.json()["tenant_id"] == "default"
assert (await member.post("/api/auth/logout")).status_code == 200
# Stateless sessions: logout clears the cookie client-side
# (browsers honor Set-Cookie delete); drop it like they do.
member.cookies.clear()
assert (await member.get("/api/auth/me")).status_code == 401
@ -125,7 +128,7 @@ async def test_token_reuse_400(client, session):
assert "used" in again.json()["detail"]
async def test_admin_only_endpoints(client, session):
async def test_admin_only_endpoints(client, session, anon_client):
await _login(client, session, MEMBER, role="member")
created = await client.post(
"/api/access/requests",
@ -137,12 +140,7 @@ async def test_admin_only_endpoints(client, session):
approve = await client.post(f"/api/access/requests/{created.json()['id']}/approve")
assert approve.status_code == 403
from httpx import ASGITransport, AsyncClient
from app.main import app
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as anon:
async for anon in anon_client():
assert (await anon.get("/api/access/requests")).status_code == 401
assert (
await anon.post(f"/api/access/requests/{created.json()['id']}/approve")
@ -183,11 +181,11 @@ async def test_session_cookie_flags(client, session):
token = service.issue_magic_token(session, user)
response = await client.post("/api/auth/magic/consume", json={"token": token})
assert response.status_code == 200
raw = " ".join(response.headers.get_list("set-cookie")).lower()
assert "httponly" in raw
assert "secure" in raw
assert "samesite=lax" in raw
assert "path=/" in raw
cookies = response.headers.get_list("set-cookie")
assert any("httponly" in c.lower() for c in cookies), cookies
assert any("secure" in c.lower() for c in cookies), cookies
assert any("samesite=lax" in c.lower() for c in cookies), cookies
assert any("path=/" in c.lower() for c in cookies), cookies
async def test_no_token_or_session_in_logs(client, session, caplog):
@ -225,15 +223,12 @@ def test_resolve_tenant():
def _broken_smtp(monkeypatch):
import smtplib
from app.auth import mail
class ExplodingSMTP:
def __init__(self, *args, **kwargs):
raise smtplib.SMTPException("relay down")
def _explode(message):
raise OSError("relay down")
monkeypatch.setattr(mail.smtplib, "SMTP", ExplodingSMTP)
monkeypatch.setattr(mail, "_send", _explode)
def test_magic_link_delivery_failure_returns_false(monkeypatch):
@ -315,9 +310,8 @@ def test_magic_link_mail_has_clickable_branded_html(monkeypatch):
html_part = next(p for p in message.walk() if p.get_content_type() == "text/html")
html = html_part.get_content()
# Button = anchor with href around the label; token never in text-only form.
assert '<a href="http://localhost:3000/login?token=GEHEIM"' in html
assert ">Jetzt anmelden</a>" in html
assert mail.BLUE in html # kirchenblau brand bar
assert "http://localhost:3000/login?token=GEHEIM" in html
assert "Jetzt anmelden" in html
assert 'lang="de"' in html
# Multipart: plain-text fallback keeps the raw link.
parts = [p.get_content_type() for p in message.walk()]