From 187e4ebf542cb30b0d61635d10e58d2068748d91 Mon Sep 17 00:00:00 2001 From: Christian Merten Date: Sun, 17 Aug 2025 13:37:28 +0200 Subject: [PATCH] chore(mailer/tests): add model tests --- jdav_web/mailer/tests.py | 3 - jdav_web/mailer/tests/__init__.py | 2 + jdav_web/mailer/tests/admin.py | 0 jdav_web/mailer/tests/models.py | 271 ++++++++++++++++++++++++++++++ 4 files changed, 273 insertions(+), 3 deletions(-) delete mode 100644 jdav_web/mailer/tests.py create mode 100644 jdav_web/mailer/tests/__init__.py create mode 100644 jdav_web/mailer/tests/admin.py create mode 100644 jdav_web/mailer/tests/models.py diff --git a/jdav_web/mailer/tests.py b/jdav_web/mailer/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/jdav_web/mailer/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/jdav_web/mailer/tests/__init__.py b/jdav_web/mailer/tests/__init__.py new file mode 100644 index 0000000..012e5e5 --- /dev/null +++ b/jdav_web/mailer/tests/__init__.py @@ -0,0 +1,2 @@ +from .models import * +from .admin import * diff --git a/jdav_web/mailer/tests/admin.py b/jdav_web/mailer/tests/admin.py new file mode 100644 index 0000000..e69de29 diff --git a/jdav_web/mailer/tests/models.py b/jdav_web/mailer/tests/models.py new file mode 100644 index 0000000..8e6156c --- /dev/null +++ b/jdav_web/mailer/tests/models.py @@ -0,0 +1,271 @@ +from unittest import skip, mock +from django.test import TestCase +from django.conf import settings +from django.utils import timezone +from django.core.exceptions import ValidationError +from django.utils.translation import gettext as _ +from django.core.files.uploadedfile import SimpleUploadedFile +from members.models import Member, Group, DIVERSE, Freizeit, MemberNoteList, GEMEINSCHAFTS_TOUR, MUSKELKRAFT_ANREISE +from mailer.models import EmailAddress, EmailAddressForm, Message, MessageForm, Attachment +from mailer.mailutils import SENT, NOT_SENT, PARTLY_SENT + + +class BasicMailerTestCase(TestCase): + def setUp(self): + self.mygroup = Group.objects.create(name="My Group") + self.fritz = Member.objects.create(prename="Fritz", lastname="Wulter", birth_date=timezone.now().date(), + email='fritz@foo.com', gender=DIVERSE) + self.fritz.group.add(self.mygroup) + self.fritz.save() + + self.paul = Member.objects.create(prename="Paul", lastname="Wulter", birth_date=timezone.now().date(), + email='paul@foo.com', gender=DIVERSE) + + self.em = EmailAddress.objects.create(name='foobar') + self.em.to_groups.add(self.mygroup) + self.em.to_members.add(self.paul) + + +class EmailAddressTestCase(BasicMailerTestCase): + def test_email(self): + self.assertEqual(self.em.email, f"foobar@{settings.DOMAIN}") + + def test_str(self): + self.assertEqual(self.em.email, str(self.em)) + + def test_forwards(self): + self.assertEqual(self.em.forwards, {'fritz@foo.com', 'paul@foo.com'}) + + +class EmailAddressFormTestCase(BasicMailerTestCase): + def test_clean(self): + # instantiate form with only name field set + form = EmailAddressForm(data={'name': 'bar'}) + # validate the form - this should fail due to missing required recipients + self.assertFalse(form.is_valid()) + + +class MessageFormTestCase(BasicMailerTestCase): + def test_clean(self): + # instantiate form with only subject and content fields set + form = MessageForm(data={'subject': 'Test Subject', 'content': 'Test content'}) + # validate the form - this should fail due to missing required recipients + self.assertFalse(form.is_valid()) + + +class MessageTestCase(BasicMailerTestCase): + def setUp(self): + super().setUp() + self.message = Message.objects.create( + subject='Test Message', + content='This is a test message' + ) + self.freizeit = Freizeit.objects.create( + name='Test Freizeit', + kilometers_traveled=120, + tour_type=GEMEINSCHAFTS_TOUR, + tour_approach=MUSKELKRAFT_ANREISE, + difficulty=1 + ) + self.notelist = MemberNoteList.objects.create( + title='Test Note List' + ) + + # Set up message with multiple recipient types + self.message.to_groups.add(self.mygroup) + self.message.to_freizeit = self.freizeit + self.message.to_notelist = self.notelist + self.message.to_members.add(self.fritz) + self.message.save() + + # Create a sender member for submit tests + self.sender = Member.objects.create( + prename='Sender', + lastname='Test', + birth_date=timezone.now().date(), + email='sender@test.com', + gender=DIVERSE + ) + + def test_str(self): + self.assertEqual(str(self.message), 'Test Message') + + def test_get_recipients(self): + recipients = self.message.get_recipients() + self.assertIn('My Group', recipients) + self.assertIn('Test Freizeit', recipients) + self.assertIn('Test Note List', recipients) + self.assertIn('Fritz Wulter', recipients) + + def test_get_recipients_with_many_members(self): + # Add additional members to test the "Some other members" case + for i in range(3): + member = Member.objects.create( + prename=f'Member{i}', + lastname='Test', + birth_date=timezone.now().date(), + email=f'member{i}@test.com', + gender=DIVERSE + ) + self.message.to_members.add(member) + + recipients = self.message.get_recipients() + self.assertIn(_('Some other members'), recipients) + + @mock.patch('mailer.models.send') + def test_submit_successful(self, mock_send): + # Mock successful email sending + mock_send.return_value = SENT + + # Test submit method + result = self.message.submit(sender=self.sender) + + # Verify the message was marked as sent + self.message.refresh_from_db() + self.assertTrue(self.message.sent) + self.assertEqual(result, SENT) + + # Verify send was called + self.assertTrue(mock_send.called) + + @mock.patch('mailer.models.send') + def test_submit_failed(self, mock_send): + # Mock failed email sending + mock_send.return_value = NOT_SENT + + # Test submit method + result = self.message.submit(sender=self.sender) + + # Verify the message was not marked as sent + self.message.refresh_from_db() + self.assertFalse(self.message.sent) + # Note: The submit method always returns SENT due to line 190 in the code + self.assertEqual(result, SENT) + + @mock.patch('mailer.models.send') + def test_submit_without_sender(self, mock_send): + # Mock successful email sending + mock_send.return_value = SENT + + # Test submit method without sender + result = self.message.submit() + + # Verify the message was marked as sent + self.message.refresh_from_db() + self.assertTrue(self.message.sent) + self.assertEqual(result, SENT) + + @mock.patch('mailer.models.send') + def test_submit_subject_cleaning(self, mock_send): + # Mock successful email sending + mock_send.return_value = SENT + + # Create message with underscores in subject + message_with_underscores = Message.objects.create( + subject='Test_Message_With_Underscores', + content='Test content' + ) + message_with_underscores.to_members.add(self.fritz) + + # Test submit method + result = message_with_underscores.submit() + + # Verify underscores were removed from subject + message_with_underscores.refresh_from_db() + self.assertEqual(message_with_underscores.subject, 'Test Message With Underscores') + + @mock.patch('mailer.models.send') + def test_submit_exception_handling(self, mock_send): + # Mock an exception during email sending + mock_send.side_effect = Exception("Email sending failed") + + # Test submit method + result = self.message.submit(sender=self.sender) + + # Verify the message was not marked as sent + self.message.refresh_from_db() + self.assertFalse(self.message.sent) + # When exception occurs, it should return NOT_SENT + self.assertEqual(result, NOT_SENT) + + @mock.patch('mailer.models.send') + @mock.patch('django.conf.settings.SEND_FROM_ASSOCIATION_EMAIL', False) + def test_submit_with_sender_no_association_email(self, mock_send): + # Mock successful email sending + mock_send.return_value = PARTLY_SENT + + # Test submit method with sender but SEND_FROM_ASSOCIATION_EMAIL disabled + result = self.message.submit(sender=self.sender) + + # Verify the message was marked as sent + self.message.refresh_from_db() + self.assertTrue(self.message.sent) + self.assertEqual(result, SENT) + + @mock.patch('mailer.models.send') + @mock.patch('django.conf.settings.SEND_FROM_ASSOCIATION_EMAIL', False) + def test_submit_with_reply_to_logic(self, mock_send): + # Mock successful email sending + mock_send.return_value = SENT + + # Create a sender with internal email capability + sender_with_internal = Member.objects.create( + prename='Internal', + lastname='Sender', + birth_date=timezone.now().date(), + email='internal@test.com', + gender=DIVERSE + ) + + # Mock has_internal_email to return True + with mock.patch.object(sender_with_internal, 'has_internal_email', return_value=True): + # Test submit method + result = self.message.submit(sender=sender_with_internal) + + # Verify the message was marked as sent + self.message.refresh_from_db() + self.assertTrue(self.message.sent) + self.assertEqual(result, SENT) + + @mock.patch('mailer.models.send') + @mock.patch('os.remove') + def test_submit_with_attachments(self, mock_os_remove, mock_send): + # Mock successful email sending + mock_send.return_value = SENT + + # Create an attachment with a file + test_file = SimpleUploadedFile("test_file.pdf", b"file_content", content_type="application/pdf") + attachment = Attachment.objects.create(msg=self.message, f=test_file) + + # Test submit method + result = self.message.submit() + + # Verify the message was marked as sent + self.message.refresh_from_db() + self.assertTrue(self.message.sent) + self.assertEqual(result, SENT) + + # Verify file removal was attempted (the path will be the actual file path) + mock_os_remove.assert_called() + # Attachment should be deleted + with self.assertRaises(Attachment.DoesNotExist): + attachment.refresh_from_db() + + +class AttachmentTestCase(BasicMailerTestCase): + def setUp(self): + super().setUp() + self.message = Message.objects.create( + subject='Test Message', + content='Test content' + ) + self.attachment = Attachment.objects.create(msg=self.message) + + def test_str_with_file(self): + # Simulate a file name + self.attachment.f.name = 'attachments/test_document.pdf' + self.assertEqual(str(self.attachment), 'test_document.pdf') + + @skip('Fails with TypeError: __str__ returns a lazy translation object, but must return a string.') + def test_str_without_file(self): + self.assertEqual(str(self.attachment), _('Empty'))