Files
gw/backend/tests/test_member_notifications.py
T

272 lines
11 KiB
Python
Raw Normal View History

2026-04-18 07:23:55 +12:00
from datetime import datetime, timezone
import pytest
from httpx import AsyncClient
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.section import ContentSection
from app.models.member import AdminMessage, Booking, Member
from app.models.settings import SiteSettings
from app.services.notifications import run_automatic_notifications
from app.auth.password import hash_password
pytestmark = pytest.mark.asyncio
async def _create_member(
db: AsyncSession,
email: str,
*,
claimed: bool = False,
member_status: str | None = None,
) -> Member:
member = Member(
email=email,
first_name="Jane",
last_name="Doe",
phone="021 000 0000",
is_claimed=claimed,
is_active=True,
notifications_enabled=True,
member_status=member_status or ("active" if claimed else "invited"),
hashed_password=hash_password("Password1!") if claimed else None,
onboarding_data={"dog_name": "Buddy", "breed": "Labrador"},
)
db.add(member)
await db.commit()
await db.refresh(member)
return member
async def test_activation_sends_member_message(client: AsyncClient, admin_token: str, db_session: AsyncSession):
member = await _create_member(db_session, "notify-active@example.com", claimed=True, member_status="pending_review")
member.onboarding_completed_at = datetime.now(timezone.utc)
member.contract_signed_at = datetime.now(timezone.utc)
member.contract_signer_name = "Jane Doe"
await db_session.commit()
response = await client.post(
f"/api/v1/admin/members/{member.id}/activate",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
result = await db_session.execute(select(AdminMessage).where(AdminMessage.member_id == member.id))
messages = result.scalars().all()
assert len(messages) == 1
assert messages[0].subject == "Your Goodwalk members account is now active"
async def test_activation_respects_member_notification_toggle(client: AsyncClient, admin_token: str, db_session: AsyncSession):
member = await _create_member(db_session, "notify-muted@example.com", claimed=True, member_status="pending_review")
member.onboarding_completed_at = datetime.now(timezone.utc)
member.contract_signed_at = datetime.now(timezone.utc)
member.contract_signer_name = "Jane Doe"
member.notifications_enabled = False
await db_session.commit()
response = await client.post(
f"/api/v1/admin/members/{member.id}/activate",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
result = await db_session.execute(select(AdminMessage).where(AdminMessage.member_id == member.id))
assert result.scalars().first() is None
async def test_global_notification_toggle_suppresses_activation_message(client: AsyncClient, admin_token: str, db_session: AsyncSession):
member = await _create_member(db_session, "notify-global-off@example.com", claimed=True, member_status="pending_review")
member.onboarding_completed_at = datetime.now(timezone.utc)
member.contract_signed_at = datetime.now(timezone.utc)
member.contract_signer_name = "Jane Doe"
db_session.add(
SiteSettings(
site_name="Goodwalk",
automatic_member_notifications_enabled=False,
nz_public_holiday_notifications_enabled=True,
invoice_reminder_notifications_enabled=True,
invoice_day_of_week=1,
)
)
await db_session.commit()
response = await client.post(
f"/api/v1/admin/members/{member.id}/activate",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
result = await db_session.execute(select(AdminMessage).where(AdminMessage.member_id == member.id))
assert result.scalars().first() is None
async def test_booking_confirmation_sends_member_message(client: AsyncClient, admin_token: str, db_session: AsyncSession):
member = await _create_member(db_session, "bookingnotify@example.com", claimed=True, member_status="active")
booking = Booking(
member_id=member.id,
service_type="pack_walk",
status="pending",
requested_date=datetime(2026, 4, 7, 9, 0, tzinfo=timezone.utc),
)
db_session.add(booking)
await db_session.commit()
await db_session.refresh(booking)
response = await client.put(
f"/api/v1/admin/bookings/{booking.id}",
headers={"Authorization": f"Bearer {admin_token}"},
json={"status": "confirmed"},
)
assert response.status_code == 200
result = await db_session.execute(select(AdminMessage).where(AdminMessage.member_id == member.id))
messages = result.scalars().all()
assert len(messages) == 1
assert "confirmed" in messages[0].subject.lower()
async def test_activation_uses_custom_template_content(client: AsyncClient, admin_token: str, db_session: AsyncSession):
member = await _create_member(db_session, "custom-active@example.com", claimed=True, member_status="pending_review")
member.onboarding_completed_at = datetime.now(timezone.utc)
member.contract_signed_at = datetime.now(timezone.utc)
member.contract_signer_name = "Jane Doe"
db_session.add(
ContentSection(
key="notifications.automaticMessages",
data={
"templates": {
"member_activated": {
"subject": "Welcome live, {{member_first_name}}",
"body": "{{member_first_name}}, your members area is unlocked.",
}
}
},
)
)
await db_session.commit()
response = await client.post(
f"/api/v1/admin/members/{member.id}/activate",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
result = await db_session.execute(select(AdminMessage).where(AdminMessage.member_id == member.id))
message = result.scalars().one()
assert message.subject == "Welcome live, Jane"
assert message.body == "Jane, your members area is unlocked."
async def test_automatic_notifications_send_and_dedupe(db_session: AsyncSession):
enabled_member = await _create_member(db_session, "holidaymember@example.com", claimed=True, member_status="active")
muted_member = await _create_member(db_session, "holidaymuted@example.com", claimed=True, member_status="active")
muted_member.notifications_enabled = False
db_session.add(
SiteSettings(
site_name="Goodwalk",
automatic_member_notifications_enabled=True,
nz_public_holiday_notifications_enabled=True,
invoice_reminder_notifications_enabled=False,
invoice_day_of_week=1,
)
)
await db_session.commit()
summary = await run_automatic_notifications(db_session, now=datetime(2026, 7, 10, 0, 30, tzinfo=timezone.utc))
await db_session.commit()
assert summary.public_holiday_messages_sent == 1
result = await db_session.execute(select(AdminMessage).where(AdminMessage.member_id == enabled_member.id))
first_run_messages = result.scalars().all()
assert len(first_run_messages) == 1
assert "public holiday" in first_run_messages[0].subject.lower()
second_summary = await run_automatic_notifications(db_session, now=datetime(2026, 7, 10, 10, 0, tzinfo=timezone.utc))
await db_session.commit()
assert second_summary.public_holiday_messages_sent == 0
async def test_public_holiday_automation_uses_custom_template(db_session: AsyncSession):
member = await _create_member(db_session, "holiday-template@example.com", claimed=True, member_status="active")
db_session.add(
SiteSettings(
site_name="Goodwalk",
automatic_member_notifications_enabled=True,
nz_public_holiday_notifications_enabled=True,
invoice_reminder_notifications_enabled=False,
invoice_day_of_week=1,
)
)
db_session.add(
ContentSection(
key="notifications.publicHolidays",
data={
"subject": "Heads up for {{holiday_name}}",
"body": "{{member_first_name}}, service may change on {{holiday_name}}.",
},
)
)
await db_session.commit()
summary = await run_automatic_notifications(db_session, now=datetime(2026, 7, 10, 0, 30, tzinfo=timezone.utc))
await db_session.commit()
assert summary.public_holiday_messages_sent == 1
result = await db_session.execute(select(AdminMessage).where(AdminMessage.member_id == member.id))
message = result.scalars().one()
assert message.subject == "Heads up for Matariki"
assert message.body == "Jane, service may change on Matariki."
async def test_invoice_automation_uses_custom_template(db_session: AsyncSession):
member = await _create_member(db_session, "invoice-template@example.com", claimed=True, member_status="active")
db_session.add(
SiteSettings(
site_name="Goodwalk",
automatic_member_notifications_enabled=True,
nz_public_holiday_notifications_enabled=False,
invoice_reminder_notifications_enabled=True,
invoice_day_of_week=1,
)
)
db_session.add(
ContentSection(
key="notifications.invoiceReminders",
data={
"subject": "Invoices go out on {{weekday_label}}",
"body": "Preview date: {{invoice_date_label}}.",
},
)
)
await db_session.commit()
summary = await run_automatic_notifications(db_session, now=datetime(2026, 4, 7, 1, 0, tzinfo=timezone.utc))
await db_session.commit()
assert summary.invoice_reminders_sent == 1
result = await db_session.execute(select(AdminMessage).where(AdminMessage.member_id == member.id))
message = result.scalars().one()
assert message.subject == "Invoices go out on Tuesday"
assert message.body == "Preview date: Tuesday 7 April."
async def test_global_automatic_notification_toggle_suppresses_automation(db_session: AsyncSession):
await _create_member(db_session, "invoice@example.com", claimed=True, member_status="active")
db_session.add(
SiteSettings(
site_name="Goodwalk",
automatic_member_notifications_enabled=False,
nz_public_holiday_notifications_enabled=True,
invoice_reminder_notifications_enabled=True,
invoice_day_of_week=1,
)
)
await db_session.commit()
summary = await run_automatic_notifications(db_session, now=datetime(2026, 4, 7, 1, 0, tzinfo=timezone.utc))
await db_session.commit()
assert summary.automatic_member_notifications_enabled is False
assert summary.invoice_reminders_sent == 0