diff --git a/src/hct_mis_api/api/admin.py b/src/hct_mis_api/api/admin.py index fc888c1109..c81aa47d3a 100644 --- a/src/hct_mis_api/api/admin.py +++ b/src/hct_mis_api/api/admin.py @@ -5,7 +5,6 @@ from django.contrib.admin.models import LogEntry from django.contrib.admin.templatetags.admin_urls import admin_urlname from django.core.exceptions import ValidationError -from django.core.mail import send_mail from django.db.models import QuerySet from django.db.transaction import atomic from django.forms import Form @@ -134,11 +133,10 @@ def _get_email_context(self, request: HttpRequest, obj: Any) -> Dict[str, Any]: def _send_token_email(self, request: HttpRequest, obj: Any, template: str) -> None: try: - send_mail( - f"HOPE API Token {obj} infos", - template.format(**self._get_email_context(request, obj)), - None, - recipient_list=[obj.user.email], + user = obj.user + user.email_user( + subject=f"HOPE API Token {obj} infos", + text_body=template.format(**self._get_email_context(request, obj)), ) self.message_user(request, f"Email sent to {obj.user.email}", messages.SUCCESS) except OSError: diff --git a/src/hct_mis_api/apps/payment/notifications.py b/src/hct_mis_api/apps/payment/notifications.py index c98d3e2fe4..f4aaec5742 100644 --- a/src/hct_mis_api/apps/payment/notifications.py +++ b/src/hct_mis_api/apps/payment/notifications.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Dict, List +from typing import Any, Dict from django.conf import settings from django.db.models import Q, QuerySet @@ -37,18 +37,22 @@ class PaymentNotification: ACTION_SEND_FOR_APPROVAL: { "action_name": "sent for approval", "subject": "Payment pending for Approval", + "recipient_title": "Approver", }, ACTION_APPROVE: { "action_name": "approved", "subject": "Payment pending for Authorization", + "recipient_title": "Authorizer", }, ACTION_AUTHORIZE: { "action_name": "authorized", "subject": "Payment pending for Release", + "recipient_title": "Reviewer", }, ACTION_REVIEW: { "action_name": "released", "subject": "Payment is Released", + "recipient_title": "Reviewer", }, } @@ -61,8 +65,9 @@ def __init__(self, payment_plan: PaymentPlan, action: str, action_user: User, ac self.payment_plan_creation_date = self.payment_plan.created_at self.email_subject = self.ACTION_PREPARE_EMAIL_BODIES_MAP[self.action]["subject"] self.action_name = self.ACTION_PREPARE_EMAIL_BODIES_MAP[self.action]["action_name"] + self.recipient_title = self.ACTION_PREPARE_EMAIL_BODIES_MAP[self.action]["recipient_title"] self.user_recipients = self._prepare_user_recipients() - self.emails = self._prepare_emails() + self.email = self._prepare_email() self.enable_email_notification = self.payment_plan.business_area.enable_email_notification def _prepare_user_recipients(self) -> QuerySet[User]: @@ -93,22 +98,23 @@ def _prepare_user_recipients(self) -> QuerySet[User]: if permission in DEFAULT_PERMISSIONS_LIST_FOR_IS_UNICEF_PARTNER else Q() ) - users = User.objects.filter( - (Q(user_roles__in=user_roles) & program_access_q) | Q(partner_role_q & program_access_q) | unicef_q - ).distinct() + users = ( + User.objects.filter( + (Q(user_roles__in=user_roles) & program_access_q) | Q(partner_role_q & program_access_q) | unicef_q + ) + .exclude(id=self.action_user.id) + .distinct() + ) if settings.ENV == "prod": users = users.exclude(is_superuser=True) return users - def _prepare_emails(self) -> List[MailjetClient]: - return [self._prepare_email(user) for user in self.user_recipients.exclude(id=self.action_user.id)] - - def _prepare_email(self, user_recipient: User) -> MailjetClient: - body_variables = self._prepare_body_variables(user_recipient) + def _prepare_email(self) -> MailjetClient: + body_variables = self._prepare_body_variables() email = MailjetClient( mailjet_template_id=config.MAILJET_TEMPLATE_PAYMENT_PLAN_NOTIFICATION, subject=self.email_subject, - recipients=[user_recipient.email], + recipients=[user_recipient.email for user_recipient in self.user_recipients], ccs=[self.action_user.email], variables=body_variables, ) @@ -117,16 +123,15 @@ def _prepare_email(self, user_recipient: User) -> MailjetClient: def send_email_notification(self) -> None: if config.SEND_PAYMENT_PLANS_NOTIFICATION and self.enable_email_notification: try: - for email in self.emails: - email.send_email() + self.email.send_email() except Exception as e: # pragma: no cover logger.exception(e) - def _prepare_body_variables(self, user_recipient: User) -> Dict[str, Any]: + def _prepare_body_variables(self) -> Dict[str, Any]: protocol = "https" if settings.SOCIAL_AUTH_REDIRECT_IS_HTTPS else "http" variables = { - "first_name": user_recipient.first_name, - "last_name": user_recipient.last_name, + "first_name": "Payment Plan", + "last_name": self.recipient_title, "action_name": self.action_name, "payment_plan_url": ( f"{protocol}://{settings.FRONTEND_HOST}/{self.payment_plan.business_area.slug}/programs/" diff --git a/src/hct_mis_api/apps/utils/mailjet.py b/src/hct_mis_api/apps/utils/mailjet.py index 78ce602381..5801253479 100644 --- a/src/hct_mis_api/apps/utils/mailjet.py +++ b/src/hct_mis_api/apps/utils/mailjet.py @@ -52,7 +52,6 @@ def send_email(self) -> bool: email_body = self._get_email_body() attachments = {"Attachments": self.attachments} if self.attachments else {} - data = { "Messages": [ { diff --git a/tests/unit/api/test_api_token.py b/tests/unit/api/test_api_token.py new file mode 100644 index 0000000000..910f7a3989 --- /dev/null +++ b/tests/unit/api/test_api_token.py @@ -0,0 +1,73 @@ +import json +from datetime import datetime +from typing import Any +from unittest.mock import patch + +from django.conf import settings +from django.http import HttpRequest +from django.test import TestCase, override_settings + +from constance.test import override_config + +from hct_mis_api.api.admin import TOKEN_INFO_EMAIL, APITokenAdmin +from hct_mis_api.api.models import Grant +from hct_mis_api.apps.account.fixtures import UserFactory +from hct_mis_api.apps.core.fixtures import create_afghanistan +from tests.unit.api.factories import APITokenFactory + + +class TestApiToken(TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.afg = create_afghanistan() + cls.user = UserFactory( + email="testapitoken@email.com", + ) + cls.token = APITokenFactory( + user=cls.user, + grants=[ + Grant.API_READ_ONLY.name, + Grant.API_RDI_UPLOAD.name, + Grant.API_PROGRAM_CREATE.name, + ], + valid_to=datetime(2050, 1, 1), + ) + cls.token.valid_for.set([cls.afg]) + + @patch("hct_mis_api.apps.utils.mailjet.requests.post") + @patch.object(APITokenAdmin, "message_user", return_value=None) + @patch.object(APITokenAdmin, "__init__", return_value=None) + @override_settings(EMAIL_SUBJECT_PREFIX="test") + @override_config(ENABLE_MAILJET=True) + def test_send_api_token( + self, mocked_requests_init: Any, mocked_requests_user: Any, mocked_requests_post: Any + ) -> None: + request = HttpRequest() + + APITokenAdmin()._send_token_email(request, self.token, TOKEN_INFO_EMAIL) + + mocked_requests_post.assert_called_once() + + expected_data = json.dumps( + { + "Messages": [ + { + "From": {"Email": settings.DEFAULT_EMAIL, "Name": settings.DEFAULT_EMAIL_DISPLAY}, + "Subject": f"[test] HOPE API Token {self.token} infos", + "To": [ + { + "Email": "testapitoken@email.com", + }, + ], + "Cc": [], + "TextPart": f"\nDear {self.user.first_name},\n\nplease find below API token infos\n\nName: {self.token}\nKey: {self.token.key}\nGrants: {self.token.grants}\nExpires: {self.token.valid_to}\nBusiness Areas: {', '.join(self.token.valid_for.values_list('name', flat=True))}\n\nRegards\n\nThe HOPE Team\n", + } + ] + } + ) + mocked_requests_post.assert_called_with( + "https://api.mailjet.com/v3.1/send", + auth=(settings.MAILJET_API_KEY, settings.MAILJET_SECRET_KEY), + data=expected_data, + ) diff --git a/tests/unit/apps/payment/test_payment_notification.py b/tests/unit/apps/payment/test_payment_notification.py index 3b78154964..5708be2c32 100644 --- a/tests/unit/apps/payment/test_payment_notification.py +++ b/tests/unit/apps/payment/test_payment_notification.py @@ -263,7 +263,7 @@ def test_send_email_notification(self, mock_send: Any) -> None: payment_notification.send_email_notification() self.assertEqual( mock_send.call_count, - 3, + 1, ) @mock.patch("hct_mis_api.apps.payment.notifications.MailjetClient.send_email") @@ -276,8 +276,7 @@ def test_send_email_notification_subject_test_env(self, mock_send: Any) -> None: self.user_action_user, f"{timezone.now():%-d %B %Y}", ) - for mailjet_client in payment_notification.emails: - self.assertEqual(mailjet_client.subject, "[test] Payment pending for Approval") + self.assertEqual(payment_notification.email.subject, "[test] Payment pending for Approval") @mock.patch("hct_mis_api.apps.payment.notifications.MailjetClient.send_email") @override_config(SEND_PAYMENT_PLANS_NOTIFICATION=True) @@ -289,8 +288,7 @@ def test_send_email_notification_subject_prod_env(self, mock_send: Any) -> None: self.user_action_user, f"{timezone.now():%-d %B %Y}", ) - for mailjet_client in payment_notification.emails: - self.assertEqual(mailjet_client.subject, "Payment pending for Approval") + self.assertEqual(payment_notification.email.subject, "Payment pending for Approval") @mock.patch("hct_mis_api.apps.utils.mailjet.requests.post") @override_config( @@ -305,14 +303,18 @@ def test_send_email_notification_catch_all_email(self, mock_post: Any) -> None: f"{timezone.now():%-d %B %Y}", ) payment_notification.send_email_notification() - for mailjet_client in payment_notification.emails: - self.assertEqual( - mailjet_client.recipients, - ["catchallemail@email.com", "catchallemail2@email.com"], - ) + self.assertEqual(len(payment_notification.email.recipients), 2) + self.assertIn( + "catchallemail@email.com", + payment_notification.email.recipients, + ) + self.assertIn( + "catchallemail2@email.com", + payment_notification.email.recipients, + ) self.assertEqual( mock_post.call_count, - 3, + 1, ) @mock.patch("hct_mis_api.apps.utils.mailjet.requests.post") @@ -327,18 +329,22 @@ def test_send_email_notification_without_catch_all_email(self, mock_post: Any) - f"{timezone.now():%-d %B %Y}", ) payment_notification.send_email_notification() - for mailjet_client in payment_notification.emails: - self.assertIn( - mailjet_client.recipients[0], - [ - self.user_with_approval_permission_partner_unicef.email, - self.user_with_approval_permission_partner_with_program_access.email, - self.user_with_partner_action_permissions_and_program_access.email, - ], - ) + self.assertEqual(len(payment_notification.email.recipients), 3) + self.assertIn( + self.user_with_approval_permission_partner_unicef.email, + payment_notification.email.recipients, + ) + self.assertIn( + self.user_with_approval_permission_partner_with_program_access.email, + payment_notification.email.recipients, + ) + self.assertIn( + self.user_with_partner_action_permissions_and_program_access.email, + payment_notification.email.recipients, + ) self.assertEqual( mock_post.call_count, - 3, + 1, ) @mock.patch("hct_mis_api.apps.utils.mailjet.requests.post") @@ -356,16 +362,16 @@ def test_send_email_notification_exclude_superuser(self, mock_post: Any) -> None f"{timezone.now():%-d %B %Y}", ) payment_notification.send_email_notification() - for mailjet_client in payment_notification.emails: - self.assertIn( - mailjet_client.recipients[0], - [ - self.user_with_approval_permission_partner_with_program_access.email, - self.user_with_partner_action_permissions_and_program_access.email, - ], - ) - self.assertNotIn(self.user_with_approval_permission_partner_unicef.email, payment_notification.emails) + self.assertEqual(len(payment_notification.email.recipients), 2) + self.assertIn( + self.user_with_approval_permission_partner_with_program_access.email, + payment_notification.email.recipients, + ) + self.assertIn( + self.user_with_partner_action_permissions_and_program_access.email, + payment_notification.email.recipients, + ) self.assertEqual( mock_post.call_count, - 2, + 1, )