215 lines
7.5 KiB
Python
215 lines
7.5 KiB
Python
from datetime import datetime, timedelta, timezone
|
|
import re
|
|
|
|
from fastapi.testclient import TestClient
|
|
from sqlalchemy import select
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.config import Settings
|
|
from app.database import get_engine
|
|
from app.main import create_app
|
|
from app.models import SupportTicket
|
|
|
|
|
|
def _csrf_from_html(html: str) -> str:
|
|
match = re.search(r'name="csrf_token" value="([^"]+)"', html)
|
|
assert match is not None
|
|
return match.group(1)
|
|
|
|
|
|
def _started_at_from_html(html: str) -> str:
|
|
match = re.search(r'name="started_at" value="([^"]+)"', html)
|
|
assert match is not None
|
|
return match.group(1)
|
|
|
|
|
|
def _build_admin_app(db_url: str) -> object:
|
|
return create_app(
|
|
Settings(
|
|
APP_ENV="test",
|
|
DB_URL=db_url,
|
|
SESSION_SECRET="test-secret",
|
|
COOKIE_SECURE=False,
|
|
COOKIE_SAMESITE="lax",
|
|
LOGIN_RATE_LIMIT_ATTEMPTS=5,
|
|
LOGIN_RATE_LIMIT_WINDOW_MINUTES=15,
|
|
BOOTSTRAP_ADMIN_EMAIL="admin@example.com",
|
|
)
|
|
)
|
|
|
|
|
|
def test_public_footer_and_legal_pages_render(app):
|
|
with TestClient(app) as client:
|
|
response = client.get("/login")
|
|
assert response.status_code == 200
|
|
assert 'href="/kontakt"' in response.text
|
|
assert 'href="/impressum"' in response.text
|
|
assert 'href="/datenschutz"' in response.text
|
|
|
|
impressum = client.get("/impressum")
|
|
assert impressum.status_code == 200
|
|
assert "Impressum" in impressum.text
|
|
|
|
privacy = client.get("/datenschutz")
|
|
assert privacy.status_code == 200
|
|
assert "Datenschutz" in privacy.text
|
|
|
|
|
|
def test_contact_form_creates_ticket(monkeypatch, app):
|
|
import app.main as main_module
|
|
|
|
base_time = datetime(2026, 3, 22, 12, 0, tzinfo=timezone.utc)
|
|
monkeypatch.setattr(main_module, "utc_now", lambda: base_time)
|
|
|
|
with TestClient(app) as client:
|
|
form = client.get("/kontakt")
|
|
assert form.status_code == 200
|
|
csrf = _csrf_from_html(form.text)
|
|
started_at = _started_at_from_html(form.text)
|
|
|
|
monkeypatch.setattr(main_module, "utc_now", lambda: base_time + timedelta(seconds=5))
|
|
submit = client.post(
|
|
"/kontakt",
|
|
data={
|
|
"csrf_token": csrf,
|
|
"started_at": started_at,
|
|
"website": "",
|
|
"category": "feature",
|
|
"name": "Max Beispiel",
|
|
"email": "max@example.com",
|
|
"subject": "Bitte Monatsfilter erweitern",
|
|
"message": "Ich wünsche mir eine bessere Filterung in der Monatsansicht.",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
assert submit.status_code == 303
|
|
assert submit.headers["location"] == "/kontakt?msg=sent"
|
|
|
|
with Session(get_engine()) as db:
|
|
tickets = db.execute(select(SupportTicket)).scalars().all()
|
|
assert len(tickets) == 1
|
|
assert tickets[0].category == "feature"
|
|
assert tickets[0].status == "open"
|
|
assert tickets[0].subject == "Bitte Monatsfilter erweitern"
|
|
|
|
|
|
def test_contact_form_honeypot_blocks_submission(monkeypatch, app):
|
|
import app.main as main_module
|
|
|
|
base_time = datetime(2026, 3, 22, 12, 0, tzinfo=timezone.utc)
|
|
monkeypatch.setattr(main_module, "utc_now", lambda: base_time)
|
|
|
|
with TestClient(app) as client:
|
|
form = client.get("/kontakt")
|
|
csrf = _csrf_from_html(form.text)
|
|
started_at = _started_at_from_html(form.text)
|
|
|
|
monkeypatch.setattr(main_module, "utc_now", lambda: base_time + timedelta(seconds=5))
|
|
submit = client.post(
|
|
"/kontakt",
|
|
data={
|
|
"csrf_token": csrf,
|
|
"started_at": started_at,
|
|
"website": "spam",
|
|
"category": "problem",
|
|
"name": "",
|
|
"email": "spam@example.com",
|
|
"subject": "Spamversuch",
|
|
"message": "Das sollte blockiert werden.",
|
|
},
|
|
)
|
|
assert submit.status_code == 429
|
|
assert "nicht versendet" in submit.text
|
|
|
|
with Session(get_engine()) as db:
|
|
tickets = db.execute(select(SupportTicket)).scalars().all()
|
|
assert tickets == []
|
|
|
|
|
|
def test_admin_can_manage_legal_content_and_tickets(tmp_path, monkeypatch):
|
|
import app.main as main_module
|
|
|
|
db_path = tmp_path / "legal-support.db"
|
|
app = _build_admin_app(f"sqlite:///{db_path}")
|
|
base_time = datetime(2026, 3, 22, 12, 0, tzinfo=timezone.utc)
|
|
monkeypatch.setattr(main_module, "utc_now", lambda: base_time)
|
|
|
|
with TestClient(app) as admin_client:
|
|
register = admin_client.post(
|
|
"/auth/register",
|
|
json={"email": "admin@example.com", "password": "verystrongPass123"},
|
|
)
|
|
assert register.status_code == 200
|
|
csrf = register.json()["csrf_token"]
|
|
|
|
update_legal = admin_client.post(
|
|
"/settings/admin/site-content",
|
|
data={
|
|
"csrf_token": csrf,
|
|
"impressum_markdown": "# Impressum\n\n**Stage Test**",
|
|
"privacy_markdown": "# Datenschutz\n\nBitte Datenschutz beachten.",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
assert update_legal.status_code == 303
|
|
assert update_legal.headers["location"] == "/settings?tab=admin&msg=site_content_updated"
|
|
|
|
impressum = admin_client.get("/impressum")
|
|
assert impressum.status_code == 200
|
|
assert "Stage Test" in impressum.text
|
|
|
|
form = admin_client.get("/kontakt")
|
|
started_at = _started_at_from_html(form.text)
|
|
monkeypatch.setattr(main_module, "utc_now", lambda: base_time + timedelta(seconds=5))
|
|
submit = admin_client.post(
|
|
"/kontakt",
|
|
data={
|
|
"csrf_token": csrf,
|
|
"started_at": started_at,
|
|
"website": "",
|
|
"category": "problem",
|
|
"name": "Admin Test",
|
|
"email": "admin@example.com",
|
|
"subject": "Ticket bitte schließen",
|
|
"message": "Dieses Ticket wird direkt im Adminbereich geschlossen.",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
assert submit.status_code == 303
|
|
|
|
with Session(get_engine()) as db:
|
|
ticket = db.execute(select(SupportTicket).where(SupportTicket.subject == "Ticket bitte schließen")).scalar_one()
|
|
ticket_id = ticket.id
|
|
|
|
with TestClient(app) as admin_client:
|
|
login = admin_client.post(
|
|
"/login",
|
|
data={
|
|
"email": "admin@example.com",
|
|
"password": "verystrongPass123",
|
|
"csrf_token": _csrf_from_html(admin_client.get("/login").text),
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
assert login.status_code == 303
|
|
settings_page = admin_client.get("/settings?tab=admin")
|
|
settings_csrf = _csrf_from_html(settings_page.text)
|
|
update_ticket = admin_client.post(
|
|
f"/settings/admin/tickets/{ticket_id}",
|
|
data={
|
|
"csrf_token": settings_csrf,
|
|
"status": "closed",
|
|
"admin_notes": "Geschlossen im Test",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
assert update_ticket.status_code == 303
|
|
assert update_ticket.headers["location"] == "/settings?tab=admin&msg=ticket_updated"
|
|
|
|
with Session(get_engine()) as db:
|
|
ticket = db.get(SupportTicket, ticket_id)
|
|
assert ticket is not None
|
|
assert ticket.status == "closed"
|
|
assert ticket.admin_notes == "Geschlossen im Test"
|
|
assert ticket.closed_at is not None
|