Previously the To/Cc addresses were hardcoded in notifier.py (Andrea, Barbara, Markus). This caused accidental emails to production contacts during testing. Changes: - New ADMIN_EMAILS env var: comma-separated list of addresses. First address → To; remaining addresses → Cc. - AdminNotifier now accepts admin_emails list; warns and skips if empty. - Removed hardcoded _INDOOR_EMAIL / _OUTDOOR_EMAIL / _ADMIN_CC_EMAIL constants and the _recipients_for() routing method. - Config.from_env() parses ADMIN_EMAILS into a list. - main.py passes config.admin_emails to AdminNotifier. - .env.example documents the new variable with production example. - Tests: fixture updated; TestRecipientsFor removed (routing gone). For testing: ADMIN_EMAILS=you@example.com For production: ADMIN_EMAILS=andrea.sigrist@gmx.net,baba.laeubli@gmail.com,spielgruppen@familien-verein.ch https://claude.ai/code/session_01HaUFs7SaLD5SoiuGCY27Tw
136 lines
4.6 KiB
Python
136 lines
4.6 KiB
Python
"""Tests for AdminNotifier helper methods."""
|
|
|
|
import pytest
|
|
|
|
from src.notifications.notifier import AdminNotifier
|
|
from src.models.registration import RegistrationData, Booking, BookingDay
|
|
|
|
|
|
@pytest.fixture
|
|
def notifier():
|
|
return AdminNotifier(
|
|
smtp_host="smtp.example.com",
|
|
smtp_port=587,
|
|
username="agent@example.com",
|
|
password="secret",
|
|
use_tls=True,
|
|
from_email="agent@example.com",
|
|
admin_emails=["to@example.com", "cc1@example.com", "cc2@example.com"],
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _format_types
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestFormatTypes:
|
|
def test_indoor_label(self, notifier):
|
|
assert "Innen" in notifier._format_types(["indoor"]) or "indoor" in notifier._format_types(["indoor"]).lower()
|
|
|
|
def test_outdoor_label(self, notifier):
|
|
assert "Wald" in notifier._format_types(["outdoor"]) or "outdoor" in notifier._format_types(["outdoor"]).lower()
|
|
|
|
def test_both_labels(self, notifier):
|
|
result = notifier._format_types(["indoor", "outdoor"])
|
|
assert len(result) > 0
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _calculate_age
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCalculateAge:
|
|
def test_returns_age_string(self, notifier):
|
|
result = notifier._calculate_age("2022-01-01")
|
|
assert isinstance(result, str)
|
|
assert len(result) > 0
|
|
|
|
def test_invalid_dob_returns_original_string(self, notifier):
|
|
result = notifier._calculate_age("not-a-date")
|
|
assert result == "not-a-date"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _calculate_monthly_fee
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCalculateMonthlyFee:
|
|
def test_indoor_one_day(self, notifier, complete_registration):
|
|
complete_registration.booking = Booking(
|
|
playgroup_types=["indoor"],
|
|
selected_days=[BookingDay(day="monday", type="indoor")],
|
|
)
|
|
fee = notifier._calculate_monthly_fee(complete_registration)
|
|
assert "130" in fee
|
|
|
|
def test_indoor_two_days(self, notifier, complete_registration):
|
|
complete_registration.booking = Booking(
|
|
playgroup_types=["indoor"],
|
|
selected_days=[
|
|
BookingDay(day="monday", type="indoor"),
|
|
BookingDay(day="wednesday", type="indoor"),
|
|
],
|
|
)
|
|
fee = notifier._calculate_monthly_fee(complete_registration)
|
|
assert "260" in fee
|
|
|
|
def test_indoor_three_days(self, notifier, complete_registration):
|
|
complete_registration.booking = Booking(
|
|
playgroup_types=["indoor"],
|
|
selected_days=[
|
|
BookingDay(day="monday", type="indoor"),
|
|
BookingDay(day="wednesday", type="indoor"),
|
|
BookingDay(day="thursday", type="indoor"),
|
|
],
|
|
)
|
|
fee = notifier._calculate_monthly_fee(complete_registration)
|
|
assert "390" in fee
|
|
|
|
def test_outdoor_one_day(self, notifier, complete_registration):
|
|
complete_registration.booking = Booking(
|
|
playgroup_types=["outdoor"],
|
|
selected_days=[BookingDay(day="monday", type="outdoor")],
|
|
)
|
|
fee = notifier._calculate_monthly_fee(complete_registration)
|
|
assert "250" in fee
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _send — SMTP interaction
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestSend:
|
|
def test_send_calls_smtp(self, notifier, mocker):
|
|
# _send uses smtplib.SMTP directly (not as context manager)
|
|
mock_smtp_cls = mocker.patch("smtplib.SMTP")
|
|
mock_server = mock_smtp_cls.return_value
|
|
|
|
notifier._send(
|
|
to=["admin@example.com"],
|
|
cc=["cc@example.com"],
|
|
subject="Test",
|
|
body="Hello",
|
|
)
|
|
|
|
mock_server.sendmail.assert_called_once()
|
|
|
|
def test_send_includes_all_recipients(self, notifier, mocker):
|
|
mock_smtp_cls = mocker.patch("smtplib.SMTP")
|
|
mock_server = mock_smtp_cls.return_value
|
|
|
|
notifier._send(
|
|
to=["a@example.com"],
|
|
cc=["b@example.com"],
|
|
subject="Test",
|
|
body="Hello",
|
|
)
|
|
|
|
call_args = mock_server.sendmail.call_args
|
|
recipients = call_args[0][1] # positional arg: to_addrs
|
|
assert "a@example.com" in recipients
|
|
assert "b@example.com" in recipients
|