@@ -8,7 +8,7 @@ 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
|
||||
from app.models import SupportTicket, User
|
||||
|
||||
|
||||
def _csrf_from_html(html: str) -> str:
|
||||
@@ -38,6 +38,18 @@ def _build_admin_app(db_url: str) -> object:
|
||||
)
|
||||
|
||||
|
||||
def _register_and_verify_user(client: TestClient, email: str, password: str = "verystrongPass123") -> None:
|
||||
register = client.post(
|
||||
"/auth/register",
|
||||
json={"email": email, "password": password},
|
||||
)
|
||||
assert register.status_code == 200
|
||||
with Session(get_engine()) as db:
|
||||
user = db.execute(select(User).where(User.email == email)).scalar_one()
|
||||
user.email_verified = True
|
||||
db.commit()
|
||||
|
||||
|
||||
def test_public_footer_and_legal_pages_render(app):
|
||||
with TestClient(app) as client:
|
||||
response = client.get("/login")
|
||||
@@ -54,6 +66,44 @@ def test_public_footer_and_legal_pages_render(app):
|
||||
assert privacy.status_code == 200
|
||||
assert "Datenschutz" in privacy.text
|
||||
|
||||
contact = client.get("/kontakt", follow_redirects=False)
|
||||
assert contact.status_code == 303
|
||||
assert contact.headers["location"] == "/login?msg=contact_requires_login"
|
||||
|
||||
|
||||
def test_legal_pages_obfuscate_email_addresses(tmp_path):
|
||||
db_path = tmp_path / "legal-obfuscation.db"
|
||||
app = _build_admin_app(f"sqlite:///{db_path}")
|
||||
|
||||
with TestClient(app) as client:
|
||||
register = client.post(
|
||||
"/auth/register",
|
||||
json={"email": "admin@example.com", "password": "verystrongPass123"},
|
||||
)
|
||||
assert register.status_code == 200
|
||||
csrf = register.json()["csrf_token"]
|
||||
|
||||
update_legal = client.post(
|
||||
"/settings/admin/site-content",
|
||||
data={
|
||||
"csrf_token": csrf,
|
||||
"impressum_markdown": "# Impressum\n\nE-Mail: [kontakt@example.com](mailto:kontakt@example.com)",
|
||||
"privacy_markdown": "# Datenschutz\n\nE-Mail: kontakt@example.com",
|
||||
},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert update_legal.status_code == 303
|
||||
|
||||
impressum = client.get("/impressum")
|
||||
assert impressum.status_code == 200
|
||||
assert "kontakt [at] example [dot] com" in impressum.text
|
||||
assert "mailto:" not in impressum.text
|
||||
|
||||
privacy = client.get("/datenschutz")
|
||||
assert privacy.status_code == 200
|
||||
assert "kontakt [at] example [dot] com" in privacy.text
|
||||
assert "mailto:" not in privacy.text
|
||||
|
||||
|
||||
def test_contact_form_creates_ticket(monkeypatch, app):
|
||||
import app.main as main_module
|
||||
@@ -62,6 +112,7 @@ def test_contact_form_creates_ticket(monkeypatch, app):
|
||||
monkeypatch.setattr(main_module, "utc_now", lambda: base_time)
|
||||
|
||||
with TestClient(app) as client:
|
||||
_register_and_verify_user(client, "max@example.com")
|
||||
form = client.get("/kontakt")
|
||||
assert form.status_code == 200
|
||||
csrf = _csrf_from_html(form.text)
|
||||
@@ -76,7 +127,7 @@ def test_contact_form_creates_ticket(monkeypatch, app):
|
||||
"website": "",
|
||||
"category": "feature",
|
||||
"name": "Max Beispiel",
|
||||
"email": "max@example.com",
|
||||
"email": "ignored@example.com",
|
||||
"subject": "Bitte Monatsfilter erweitern",
|
||||
"message": "Ich wünsche mir eine bessere Filterung in der Monatsansicht.",
|
||||
},
|
||||
@@ -88,6 +139,7 @@ def test_contact_form_creates_ticket(monkeypatch, app):
|
||||
with Session(get_engine()) as db:
|
||||
tickets = db.execute(select(SupportTicket)).scalars().all()
|
||||
assert len(tickets) == 1
|
||||
assert tickets[0].email == "max@example.com"
|
||||
assert tickets[0].category == "feature"
|
||||
assert tickets[0].status == "open"
|
||||
assert tickets[0].subject == "Bitte Monatsfilter erweitern"
|
||||
@@ -100,6 +152,7 @@ def test_contact_form_honeypot_blocks_submission(monkeypatch, app):
|
||||
monkeypatch.setattr(main_module, "utc_now", lambda: base_time)
|
||||
|
||||
with TestClient(app) as client:
|
||||
_register_and_verify_user(client, "spam@example.com")
|
||||
form = client.get("/kontakt")
|
||||
csrf = _csrf_from_html(form.text)
|
||||
started_at = _started_at_from_html(form.text)
|
||||
@@ -113,7 +166,7 @@ def test_contact_form_honeypot_blocks_submission(monkeypatch, app):
|
||||
"website": "spam",
|
||||
"category": "problem",
|
||||
"name": "",
|
||||
"email": "spam@example.com",
|
||||
"email": "ignored@example.com",
|
||||
"subject": "Spamversuch",
|
||||
"message": "Das sollte blockiert werden.",
|
||||
},
|
||||
@@ -212,3 +265,22 @@ def test_admin_can_manage_legal_content_and_tickets(tmp_path, monkeypatch):
|
||||
assert ticket.status == "closed"
|
||||
assert ticket.admin_notes == "Geschlossen im Test"
|
||||
assert ticket.closed_at is not None
|
||||
|
||||
|
||||
def test_contact_requires_verified_user(tmp_path):
|
||||
app = _build_admin_app(f"sqlite:///{tmp_path / 'contact-verified.db'}")
|
||||
|
||||
with TestClient(app) as client:
|
||||
register = client.post(
|
||||
"/auth/register",
|
||||
json={"email": "user@example.com", "password": "verystrongPass123"},
|
||||
)
|
||||
assert register.status_code == 200
|
||||
with Session(get_engine()) as db:
|
||||
user = db.execute(select(User).where(User.email == "user@example.com")).scalar_one()
|
||||
user.email_verified = False
|
||||
db.commit()
|
||||
|
||||
contact = client.get("/kontakt", follow_redirects=False)
|
||||
assert contact.status_code == 303
|
||||
assert contact.headers["location"] == "/verify-email/resend"
|
||||
|
||||
@@ -1013,6 +1013,7 @@ def test_register_onboarding_applies_optional_settings(app):
|
||||
assert payload["vacation_days_total"] == 22
|
||||
assert payload["vacation_show_in_header"] is True
|
||||
assert payload["preferred_home_view"] == "month"
|
||||
assert payload["theme_preference"] == "auto"
|
||||
assert payload["entry_mode"] == "auto_until_today"
|
||||
assert payload["overtime_start_date"] == "2026-02-02"
|
||||
assert payload["overtime_expiry_days"] == 90
|
||||
@@ -1070,6 +1071,7 @@ def test_settings_export_all_supports_backup_and_existing_formats(app):
|
||||
assert "application/json" in export_backup.headers["content-type"]
|
||||
payload = export_backup.json()
|
||||
assert payload["backup_version"] == 2
|
||||
assert payload["settings"]["theme_preference"] == "auto"
|
||||
assert "user" not in payload
|
||||
assert payload["settings"]["weekly_target_minutes"] == 1500
|
||||
assert len(payload["time_entries"]) == 1
|
||||
@@ -1169,6 +1171,7 @@ def test_register_can_import_backup_during_signup(app):
|
||||
"/settings/preferences",
|
||||
data={
|
||||
"preferred_home_view": "month",
|
||||
"theme_preference": "light",
|
||||
"preferred_month_view_mode": "weeks",
|
||||
"entry_mode": "auto_until_today",
|
||||
"csrf_token": source_csrf,
|
||||
@@ -1237,6 +1240,7 @@ def test_register_can_import_backup_during_signup(app):
|
||||
assert me.status_code == 200
|
||||
payload = me.json()
|
||||
assert payload["preferred_home_view"] == "month"
|
||||
assert payload["theme_preference"] == "light"
|
||||
assert payload["entry_mode"] == "auto_until_today"
|
||||
assert payload["working_days"] == [0, 1, 2, 3]
|
||||
assert payload["count_vacation_as_worktime"] is True
|
||||
@@ -1333,6 +1337,7 @@ def test_settings_default_view_redirect(app):
|
||||
"/settings/preferences",
|
||||
data={
|
||||
"preferred_home_view": "month",
|
||||
"theme_preference": "light",
|
||||
"preferred_month_view_mode": "weeks",
|
||||
"csrf_token": csrf,
|
||||
},
|
||||
@@ -1348,6 +1353,10 @@ def test_settings_default_view_redirect(app):
|
||||
assert dashboard_redirect.status_code == 303
|
||||
assert dashboard_redirect.headers["location"].startswith("/month?view=weeks")
|
||||
|
||||
month_page = client.get("/month")
|
||||
assert month_page.status_code == 200
|
||||
assert 'data-theme="light"' in month_page.text
|
||||
|
||||
|
||||
def test_main_navigation_uses_explicit_period_links(app):
|
||||
with TestClient(app) as client:
|
||||
|
||||
Reference in New Issue
Block a user