diff --git a/CHANGELOG.md b/CHANGELOG.md index 829750446..bf3ba77a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to ### Added +- ✨(domains) add notification when domain status changes - ✨(domains) add periodic tasks to fetch domain status - 🧑‍💻(docker) add celery beat to manage periodic tasks - ✨(dimail) management command to fetch domain status diff --git a/src/backend/mailbox_manager/models.py b/src/backend/mailbox_manager/models.py index 77ba8e5db..4f9dd78f5 100644 --- a/src/backend/mailbox_manager/models.py +++ b/src/backend/mailbox_manager/models.py @@ -73,6 +73,17 @@ def get_abilities(self, user): "manage_accesses": is_owner_or_admin, } + def send_notification(self, subject, message): + """ + Notify owners and admins of the domain + """ + for access in self.accesses.filter( + role__in=[MailDomainRoleChoices.OWNER, MailDomainRoleChoices.ADMIN] + ).all(): + access.user.email_user( + subject=subject, message=message, from_email=settings.DEFAULT_FROM_EMAIL + ) + class MailDomainAccess(BaseModel): """Allow to manage users' accesses to mail domains.""" diff --git a/src/backend/mailbox_manager/tasks.py b/src/backend/mailbox_manager/tasks.py index b5ec66ee3..8cf11e945 100644 --- a/src/backend/mailbox_manager/tasks.py +++ b/src/backend/mailbox_manager/tasks.py @@ -40,6 +40,18 @@ def fetch_domains_status(): else: if old_status != domain.status: update_count += 1 + # Send notification to owners and admins of the domain + # when its status changes to failed or enabled + if domain.status == MailDomainStatusChoices.FAILED: + domain.send_notification( + subject="Domain status changed", + message=f"Domain {domain.name} is down", + ) + elif domain.status == MailDomainStatusChoices.ENABLED: + domain.send_notification( + subject="Domain status changed", + message=f"Domain {domain.name} is up", + ) else: check_count += 1 return f"Domains processed: {update_count} updated, {check_count} checked" diff --git a/src/backend/mailbox_manager/tests/test_tasks.py b/src/backend/mailbox_manager/tests/test_tasks.py index 54db68099..77969948a 100644 --- a/src/backend/mailbox_manager/tests/test_tasks.py +++ b/src/backend/mailbox_manager/tests/test_tasks.py @@ -4,6 +4,9 @@ import json import re +from unittest import mock + +from django.conf import settings import pytest import responses @@ -21,13 +24,24 @@ def test_fetch_domain_status_task_success(): # pylint: disable=too-many-locals domain_enabled1 = factories.MailDomainEnabledFactory() domain_enabled2 = factories.MailDomainEnabledFactory() + owner_domain_enabled2 = factories.MailDomainAccessFactory( + domain=domain_enabled2, role=enums.MailDomainRoleChoices.OWNER + ).user + admin_domain_enabled2 = factories.MailDomainAccessFactory( + domain=domain_enabled2, role=enums.MailDomainRoleChoices.ADMIN + ).user domain_disabled = factories.MailDomainFactory( status=enums.MailDomainStatusChoices.DISABLED ) domain_failed = factories.MailDomainFactory( status=enums.MailDomainStatusChoices.FAILED ) - + owner_domain_failed = factories.MailDomainAccessFactory( + domain=domain_failed, role=enums.MailDomainRoleChoices.OWNER + ).user + admin_domain_failed = factories.MailDomainAccessFactory( + domain=domain_failed, role=enums.MailDomainRoleChoices.ADMIN + ).user body_content_ok1 = CHECK_DOMAIN_OK.copy() body_content_ok1["name"] = domain_enabled1.name @@ -52,7 +66,8 @@ def test_fetch_domain_status_task_success(): # pylint: disable=too-many-locals status=200, content_type="application/json", ) - tasks.fetch_domains_status() + with mock.patch("django.core.mail.send_mail") as mock_send: + tasks.fetch_domains_status() domain_enabled1.refresh_from_db() domain_enabled2.refresh_from_db() domain_disabled.refresh_from_db() @@ -63,6 +78,35 @@ def test_fetch_domain_status_task_success(): # pylint: disable=too-many-locals assert domain_enabled2.status == enums.MailDomainStatusChoices.FAILED # Status of the failed domain has changed to enabled assert domain_failed.status == enums.MailDomainStatusChoices.ENABLED + # Check notification was sent to owners and admins + assert mock_send.call_count == 4 + calls = [ + mock.call( + "Domain status changed", + f"Domain {domain_enabled2.name} is down", + settings.DEFAULT_FROM_EMAIL, + [owner_domain_enabled2.email], + ), + mock.call( + "Domain status changed", + f"Domain {domain_enabled2.name} is down", + settings.DEFAULT_FROM_EMAIL, + [admin_domain_enabled2.email], + ), + mock.call( + "Domain status changed", + f"Domain {domain_failed.name} is up", + settings.DEFAULT_FROM_EMAIL, + [owner_domain_failed.email], + ), + mock.call( + "Domain status changed", + f"Domain {domain_failed.name} is up", + settings.DEFAULT_FROM_EMAIL, + [admin_domain_failed.email], + ), + ] + mock_send.assert_has_calls(calls, any_order=True) # Disabled domain was excluded assert domain_disabled.status == enums.MailDomainStatusChoices.DISABLED