v1
This commit is contained in:
@@ -0,0 +1,271 @@
|
||||
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
|
||||
Reference in New Issue
Block a user