Notify via matrix when a new membership application is submitted. (#87)

Reviewed-on: https://git.data.coop/data.coop/membersystem/pulls/87
Reviewed-by: benjaoming <benjaoming@data.coop>
Co-authored-by: Víðir Valberg Guðmundsson <valberg@orn.li>
Co-committed-by: Víðir Valberg Guðmundsson <valberg@orn.li>
This commit is contained in:
Víðir Valberg Guðmundsson 2025-03-30 20:37:36 +00:00 committed by valberg
parent 5922c3245c
commit 0b2d6b9dc4
3 changed files with 89 additions and 0 deletions

View file

@ -30,6 +30,10 @@ typecheck:
test: test:
docker compose run --rm app pytest docker compose run --rm app pytest
coverage *ARGS:
@echo "Running tests with coverage"
docker compose run --rm app pytest --cov --cov-report term-missing:skip-covered {{ARGS}}
# You need to install Stripe CLI from here to run this: https://github.com/stripe/stripe-cli/releases # You need to install Stripe CLI from here to run this: https://github.com/stripe/stripe-cli/releases
stripe_cli: stripe_cli:
stripe listen --forward-to 0.0.0.0:8000/order/stripe/webhook/ stripe listen --forward-to 0.0.0.0:8000/order/stripe/webhook/

View file

@ -1,6 +1,7 @@
"""Models for the membership app.""" """Models for the membership app."""
import uuid import uuid
from typing import Any
from typing import Self from typing import Self
from dirtyfields import DirtyFieldsMixin from dirtyfields import DirtyFieldsMixin
@ -9,11 +10,14 @@ from django.contrib.auth.models import UserManager
from django.contrib.postgres.constraints import ExclusionConstraint from django.contrib.postgres.constraints import ExclusionConstraint
from django.contrib.postgres.fields import DateRangeField from django.contrib.postgres.fields import DateRangeField
from django.contrib.postgres.fields import RangeOperators from django.contrib.postgres.fields import RangeOperators
from django.contrib.sites.models import Site
from django.db import models from django.db import models
from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from djmoney.money import Money from djmoney.money import Money
from services.models import ServiceRequest from services.models import ServiceRequest
from utils.matrix import notify_admins
from utils.mixins import CreatedModifiedAbstract from utils.mixins import CreatedModifiedAbstract
@ -287,3 +291,14 @@ class WaitingListEntry(CreatedModifiedAbstract):
class Meta: class Meta:
verbose_name = _("waiting list entry") verbose_name = _("waiting list entry")
verbose_name_plural = _("waiting list entries") verbose_name_plural = _("waiting list entries")
def save(self, *args: Any, **kwargs: Any) -> None: # noqa: ANN401
"""Create notifications when new are added."""
is_new = not self.pk
super().save(*args, **kwargs)
if is_new:
base_domain = Site.objects.get_current()
change_url = reverse("admin:membership_waitinglistentry_change", kwargs={"object_id": self.pk})
notify_admins(f"Membership application: https://{base_domain}{change_url}")

View file

@ -0,0 +1,70 @@
"""Tests for WaitingListEntry functionality."""
from unittest import mock
import pytest
from django.contrib.sites.models import Site
from django.urls import reverse
from membership.models import WaitingListEntry
class TestWaitingListEntryNotifications:
"""Tests for WaitingListEntry notification functionality."""
@pytest.mark.django_db()
def test_matrix_notification_on_create(self, membership_type):
"""Test that a Matrix notification is sent when a new WaitingListEntry is created."""
# Explicitly patch notify_admins to ensure we're testing the right function
with mock.patch("membership.models.notify_admins") as mock_notify:
# Create a new WaitingListEntry
entry = WaitingListEntry.objects.create(
name="Test User",
email="test@example.com",
membership_type=membership_type,
)
# Check that notify_admins was called
mock_notify.assert_called_once()
# Get the expected URL components
base_domain = Site.objects.get_current()
change_url = reverse("admin:membership_waitinglistentry_change", kwargs={"object_id": entry.pk})
expected_message = f"Membership application: https://{base_domain}{change_url}"
# Verify the message content
mock_notify.assert_called_with(expected_message)
@pytest.mark.django_db()
def test_no_matrix_notification_on_update(self, membership_type):
"""Test that no Matrix notification is sent when a WaitingListEntry is updated."""
# First create the entry
entry = WaitingListEntry.objects.create(
name="Test User",
email="test@example.com",
membership_type=membership_type,
)
# Now update it with a patched notify_admins
with mock.patch("membership.models.notify_admins") as mock_notify:
entry.geography = "Some Location"
entry.save()
# Verify that notify_admins was not called
mock_notify.assert_not_called()
@pytest.mark.django_db()
def test_message_format(self, membership_type):
"""Test that the message has the expected format with 'Membership application:' prefix."""
with mock.patch("membership.models.notify_admins") as mock_notify:
# Create a new WaitingListEntry
WaitingListEntry.objects.create(
name="Test User",
email="test@example.com",
membership_type=membership_type,
)
# Check the message format
args, kwargs = mock_notify.call_args
message = args[0]
assert message.startswith("Membership application: ")
assert "https://" in message