feat(P2): Notruf verschickt E-Mail an Kontaktperson (Testphase)

Beim Notruf-Knopf wird – sofern SMTP konfiguriert ist – eine E-Mail an die
Kontaktperson(en) des Nutzers geschickt (Default: dieter.schlueter@linix.de).
Der Nutzer-Hinweis spiegelt ehrlich wider, ob wirklich etwas rausging:
"Kontaktperson per E-Mail benachrichtigt" vs. "noch keine Benachrichtigung".

- config.py: EMERGENCY_CONTACT_EMAIL + SMTP_* (ohne SMTP_HOST kein Versand);
  veralteten LLM-Klassifikator-Kommentar entfernt.
- emergency.py: SMTP-Versand (best effort, im Thread), lokalisierte Hinweise
  für "gesendet"/"nicht gesendet" (10 Sprachen). Pro-Nutzer-Kontakte via
  prefs.emergency_contacts vorbereitet, Fallback auf Config-Default.
- Test mit gemocktem smtplib. env.example + DEPLOYMENT.md 2.5 dokumentiert.

SMS/Anruf-Eskalation folgt später (eigene Planung). 210 passed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-25 03:37:18 +02:00
commit c35fe6e187
6 changed files with 141 additions and 20 deletions

View file

@ -64,3 +64,33 @@ def test_manual_emergency_does_not_count_against_quota(monkeypatch):
monkeypatch.setattr(settings, "daily_request_limit", 1)
for _ in range(3):
assert client.post("/api/emergency", json={"language": "de"}).status_code == 200
def test_manual_emergency_sends_email_when_smtp_configured(monkeypatch):
import app.safety.emergency as em
captured = {}
class FakeSMTP:
def __init__(self, host, port, timeout=10):
captured["host"] = host
def __enter__(self):
return self
def __exit__(self, *a):
return False
def starttls(self):
captured["tls"] = True
def login(self, u, p):
captured["login"] = u
def send_message(self, msg):
captured["to"] = msg["To"]
captured["subject"] = msg["Subject"]
monkeypatch.setattr(em.smtplib, "SMTP", FakeSMTP)
monkeypatch.setattr(settings, "smtp_host", "mail.example.com")
monkeypatch.setattr(settings, "emergency_contact_email", "kontakt@example.com")
data = client.post("/api/emergency", json={"language": "de"}).json()
assert data["email_sent"] is True
assert "benachrichtigt" in data["notice"].lower()
assert captured["to"] == "kontakt@example.com"
assert "Notruf" in captured["subject"]