diff --git a/jdav_web/mailer/tests/admin.py b/jdav_web/mailer/tests/admin.py index e69de29..79032cf 100644 --- a/jdav_web/mailer/tests/admin.py +++ b/jdav_web/mailer/tests/admin.py @@ -0,0 +1,337 @@ +import json +import unittest +from http import HTTPStatus +from django.test import TestCase, override_settings +from django.contrib.admin.sites import AdminSite +from django.test import RequestFactory, Client +from django.contrib.auth.models import User, Permission +from django.utils import timezone +from django.contrib.sessions.middleware import SessionMiddleware +from django.contrib.messages.middleware import MessageMiddleware +from django.contrib.messages.storage.fallback import FallbackStorage +from django.contrib.messages import get_messages +from django.utils.translation import gettext_lazy as _ +from django.urls import reverse, reverse_lazy +from django.http import HttpResponseRedirect, HttpResponse +from unittest.mock import Mock, patch +from django.test.utils import override_settings +from django.urls import path, include +from django.contrib import admin as django_admin +from django.conf import settings + +from members.tests.utils import create_custom_user +from members.models import Member, MALE, DIVERSE, Group +from ..models import Message, Attachment, EmailAddress +from ..admin import MessageAdmin, submit_message +from ..mailutils import SENT, NOT_SENT, PARTLY_SENT + + +class AdminTestCase(TestCase): + def setUp(self, model, admin): + self.factory = RequestFactory() + self.model = model + if model is not None and admin is not None: + self.admin = admin(model, AdminSite()) + superuser = User.objects.create_superuser( + username='superuser', password='secret' + ) + standard = create_custom_user('standard', ['Standard'], 'Paul', 'Wulter') + trainer = create_custom_user('trainer', ['Standard', 'Trainings'], 'Lise', 'Lotte') + + def _login(self, name): + c = Client() + res = c.login(username=name, password='secret') + # make sure we logged in + assert res + return c + + def _add_middleware(self, request): + """Add required middleware to request.""" + # Session middleware + middleware = SessionMiddleware(lambda x: None) + middleware.process_request(request) + request.session.save() + + # Messages middleware + messages_middleware = MessageMiddleware(lambda x: None) + messages_middleware.process_request(request) + request._messages = FallbackStorage(request) + + +class MessageAdminTestCase(AdminTestCase): + def setUp(self): + super().setUp(Message, MessageAdmin) + + # Create test data + self.group = Group.objects.create(name='Test Group') + self.email_address = EmailAddress.objects.create(name='testmail') + + # Create test member with internal email + self.internal_member = Member.objects.create( + prename='Internal', + lastname='User', + birth_date=timezone.now().date(), + email=f'internal@{settings.ALLOWED_EMAIL_DOMAINS_FOR_INVITE_AS_USER[0]}', + gender=DIVERSE + ) + + # Create test member with external email + self.external_member = Member.objects.create( + prename='External', + lastname='User', + birth_date=timezone.now().date(), + email='external@example.com', + gender=DIVERSE + ) + + # Create users for testing + self.user_with_internal_member = User.objects.create_user(username='testuser', password='secret') + self.user_with_internal_member.member = self.internal_member + self.user_with_internal_member.save() + + self.user_with_external_member = User.objects.create_user(username='external_user', password='secret') + self.user_with_external_member.member = self.external_member + self.user_with_external_member.save() + + self.user_without_member = User.objects.create_user(username='no_member_user', password='secret') + + # Create test message + self.message = Message.objects.create( + subject='Test Message', + content='Test content' + ) + self.message.to_groups.add(self.group) + self.message.to_members.add(self.internal_member) + + def test_save_model_sets_created_by(self): + """Test that save_model sets created_by when creating new message.""" + request = self.factory.post('/admin/mailer/message/add/') + request.user = self.user_with_internal_member + + # Create new message + new_message = Message(subject='New Message', content='New content') + + # Test save_model for new object (change=False) + self.admin.save_model(request, new_message, None, change=False) + + self.assertEqual(new_message.created_by, self.internal_member) + + def test_save_model_does_not_change_created_by_on_update(self): + """Test that save_model doesn't change created_by when updating.""" + request = self.factory.post('/admin/mailer/message/1/change/') + request.user = self.user_with_internal_member + + # Message already has created_by set + self.message.created_by = self.external_member + + # Test save_model for existing object (change=True) + self.admin.save_model(request, self.message, None, change=True) + + self.assertEqual(self.message.created_by, self.external_member) + + @patch('mailer.models.Message.submit') + def test_submit_message_success(self, mock_submit): + """Test submit_message with successful send.""" + mock_submit.return_value = SENT + + request = self.factory.post('/admin/mailer/message/') + request.user = self.user_with_internal_member + self._add_middleware(request) + + # Test submit_message + submit_message(self.message, request) + + # Verify submit was called with correct sender + mock_submit.assert_called_once_with(self.internal_member) + + # Check success message + messages_list = list(get_messages(request)) + self.assertEqual(len(messages_list), 1) + self.assertIn(str(_('Successfully sent message')), str(messages_list[0])) + + @patch('mailer.models.Message.submit') + def test_submit_message_not_sent(self, mock_submit): + """Test submit_message when sending fails.""" + mock_submit.return_value = NOT_SENT + + request = self.factory.post('/admin/mailer/message/') + request.user = self.user_with_internal_member + self._add_middleware(request) + + # Test submit_message + submit_message(self.message, request) + + # Check error message + messages_list = list(get_messages(request)) + self.assertEqual(len(messages_list), 1) + self.assertIn(str(_('Failed to send message')), str(messages_list[0])) + + @patch('mailer.models.Message.submit') + def test_submit_message_partly_sent(self, mock_submit): + """Test submit_message when partially sent.""" + mock_submit.return_value = PARTLY_SENT + + request = self.factory.post('/admin/mailer/message/') + request.user = self.user_with_internal_member + self._add_middleware(request) + + # Test submit_message + submit_message(self.message, request) + + # Check warning message + messages_list = list(get_messages(request)) + self.assertEqual(len(messages_list), 1) + self.assertIn(str(_('Failed to send some messages')), str(messages_list[0])) + + def test_submit_message_user_has_no_member(self): + """Test submit_message when user has no associated member.""" + request = self.factory.post('/admin/mailer/message/') + request.user = self.user_without_member + self._add_middleware(request) + + # Test submit_message + submit_message(self.message, request) + + # Check error message + messages_list = list(get_messages(request)) + self.assertEqual(len(messages_list), 1) + self.assertIn(str(_('Your account is not connected to a member. Please contact your system administrator.')), str(messages_list[0])) + + def test_submit_message_user_has_external_email(self): + """Test submit_message when user has external email.""" + request = self.factory.post('/admin/mailer/message/') + request.user = self.user_with_external_member + self._add_middleware(request) + + # Test submit_message + submit_message(self.message, request) + + # Check error message + messages_list = list(get_messages(request)) + self.assertEqual(len(messages_list), 1) + self.assertIn(str(_('Your email address is not an internal email address. Please use an email address with one of the following domains: %(domains)s.') % {'domains': ", ".join(settings.ALLOWED_EMAIL_DOMAINS_FOR_INVITE_AS_USER)}), str(messages_list[0])) + + @patch('mailer.admin.submit_message') + def test_send_message_action_confirmed(self, mock_submit_message): + """Test send_message action when confirmed.""" + request = self.factory.post('/admin/mailer/message/', {'confirmed': 'true'}) + request.user = self.user_with_internal_member + self._add_middleware(request) + + queryset = Message.objects.filter(pk=self.message.pk) + + # Test send_message action + result = self.admin.send_message(request, queryset) + + # Verify submit_message was called for each message + mock_submit_message.assert_called_once_with(self.message, request) + + # Should return None when confirmed (no template response) + self.assertIsNone(result) + + def test_send_message_action_not_confirmed(self): + """Test send_message action when not confirmed (shows confirmation page).""" + request = self.factory.post('/admin/mailer/message/') + request.user = self.user_with_internal_member + self._add_middleware(request) + + queryset = Message.objects.filter(pk=self.message.pk) + + # Test send_message action + result = self.admin.send_message(request, queryset) + + # Should return HttpResponse with confirmation template + self.assertIsNotNone(result) + self.assertEqual(result.status_code, HTTPStatus.OK) + + @patch('mailer.admin.submit_message') + def test_response_change_with_send(self, mock_submit_message): + """Test response_change when _send is in POST.""" + request = self.factory.post('/admin/mailer/message/1/change/', {'_send': 'Send'}) + request.user = self.user_with_internal_member + self._add_middleware(request) + + # Test response_change + with patch.object(self.admin.__class__.__bases__[2], 'response_change') as mock_super: + mock_super.return_value = HttpResponseRedirect('/admin/') + result = self.admin.response_change(request, self.message) + + # Verify submit_message was called + mock_submit_message.assert_called_once_with(self.message, request) + + # Verify super method was called + mock_super.assert_called_once() + + @patch('mailer.admin.submit_message') + def test_response_change_without_send(self, mock_submit_message): + """Test response_change when _send is not in POST.""" + request = self.factory.post('/admin/mailer/message/1/change/', {'_save': 'Save'}) + request.user = self.user_with_internal_member + self._add_middleware(request) + + # Test response_change + with patch.object(self.admin.__class__.__bases__[2], 'response_change') as mock_super: + mock_super.return_value = HttpResponseRedirect('/admin/') + result = self.admin.response_change(request, self.message) + + # Verify submit_message was NOT called + mock_submit_message.assert_not_called() + + # Verify super method was called + mock_super.assert_called_once() + + @patch('mailer.admin.submit_message') + def test_response_add_with_send(self, mock_submit_message): + """Test response_add when _send is in POST.""" + request = self.factory.post('/admin/mailer/message/add/', {'_send': 'Send'}) + request.user = self.user_with_internal_member + self._add_middleware(request) + + # Test response_add + with patch.object(self.admin.__class__.__bases__[2], 'response_add') as mock_super: + mock_super.return_value = HttpResponseRedirect('/admin/') + result = self.admin.response_add(request, self.message) + + # Verify submit_message was called + mock_submit_message.assert_called_once_with(self.message, request) + + # Verify super method was called + mock_super.assert_called_once() + + def test_get_form_with_members_param(self): + """Test get_form when members parameter is provided.""" + # Create request with members parameter + members_ids = [self.internal_member.pk, self.external_member.pk] + request = self.factory.get(f'/admin/mailer/message/add/?members={json.dumps(members_ids)}') + request.user = self.user_with_internal_member + + # Test get_form + form_class = self.admin.get_form(request) + form = form_class() + + # Verify initial members are set + self.assertEqual(list(form.fields['to_members'].initial), [self.internal_member, self.external_member]) + + def test_get_form_with_invalid_members_param(self): + """Test get_form when members parameter is not a list.""" + # Create request with invalid members parameter + request = self.factory.get('/admin/mailer/message/add/?members="not_a_list"') + request.user = self.user_with_internal_member + + # Test get_form + form_class = self.admin.get_form(request) + + # Should return form without modification + self.assertIsNotNone(form_class) + + def test_get_form_without_members_param(self): + """Test get_form when no members parameter is provided.""" + # Create request without members parameter + request = self.factory.get('/admin/mailer/message/add/') + request.user = self.user_with_internal_member + + # Test get_form + form_class = self.admin.get_form(request) + + # Should return form without modification + self.assertIsNotNone(form_class)