From 0b2d6b9dc4203dbb746e20c78fe74bd24afb7abd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=AD=C3=B0ir=20Valberg=20Gu=C3=B0mundsson?= Date: Sun, 30 Mar 2025 20:37:36 +0000 Subject: [PATCH] Notify via matrix when a new membership application is submitted. (#87) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-on: https://git.data.coop/data.coop/membersystem/pulls/87 Reviewed-by: benjaoming Co-authored-by: Víðir Valberg Guðmundsson Co-committed-by: Víðir Valberg Guðmundsson --- Justfile | 4 ++ src/membership/models.py | 15 ++++++++ tests/test_waitinglistentry.py | 70 ++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 tests/test_waitinglistentry.py diff --git a/Justfile b/Justfile index bb624e4..b63dd38 100644 --- a/Justfile +++ b/Justfile @@ -30,6 +30,10 @@ typecheck: test: 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 stripe_cli: stripe listen --forward-to 0.0.0.0:8000/order/stripe/webhook/ diff --git a/src/membership/models.py b/src/membership/models.py index 188d068..3c93582 100644 --- a/src/membership/models.py +++ b/src/membership/models.py @@ -1,6 +1,7 @@ """Models for the membership app.""" import uuid +from typing import Any from typing import Self 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.fields import DateRangeField from django.contrib.postgres.fields import RangeOperators +from django.contrib.sites.models import Site from django.db import models +from django.urls import reverse from django.utils import timezone from django.utils.translation import gettext as _ from djmoney.money import Money from services.models import ServiceRequest +from utils.matrix import notify_admins from utils.mixins import CreatedModifiedAbstract @@ -287,3 +291,14 @@ class WaitingListEntry(CreatedModifiedAbstract): class Meta: verbose_name = _("waiting list entry") 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}") diff --git a/tests/test_waitinglistentry.py b/tests/test_waitinglistentry.py new file mode 100644 index 0000000..ea084dd --- /dev/null +++ b/tests/test_waitinglistentry.py @@ -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