diff --git a/CHANGELOG.md b/CHANGELOG.md index 000d325..9aa6329 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to ### Added +- ✨(domains) store last health check details on MailDomain - ✨(domains) add support email field on domain ## [1.11.0] - 2025-02-07 diff --git a/src/backend/locale/fr_FR/LC_MESSAGES/django.mo b/src/backend/locale/fr_FR/LC_MESSAGES/django.mo index 6cbddcc..034144f 100644 Binary files a/src/backend/locale/fr_FR/LC_MESSAGES/django.mo and b/src/backend/locale/fr_FR/LC_MESSAGES/django.mo differ diff --git a/src/backend/locale/fr_FR/LC_MESSAGES/django.po b/src/backend/locale/fr_FR/LC_MESSAGES/django.po index e7ac86b..eb2cf78 100644 --- a/src/backend/locale/fr_FR/LC_MESSAGES/django.po +++ b/src/backend/locale/fr_FR/LC_MESSAGES/django.po @@ -591,39 +591,47 @@ msgstr "Désactivé" msgid "Action required" msgstr "Action requise" -#: mailbox_manager/models.py:35 +#: mailbox_manager/models.py:33 +msgid "last check details" +msgstr "détails de la dernière vérification" + +#: mailbox_manager/models.py:34 +msgid "A JSON object containing the last health check details" +msgstr "Un objet JSON contenant les détails de la dernière vérification" + +#: mailbox_manager/models.py:39 msgid "Mail domain" msgstr "Domaine de messagerie" -#: mailbox_manager/models.py:36 +#: mailbox_manager/models.py:40 msgid "Mail domains" msgstr "Domaines de messagerie" -#: mailbox_manager/models.py:102 +#: mailbox_manager/models.py:106 msgid "User/mail domain relation" msgstr "Relation entre un utilisateur et un domaine de mail" -#: mailbox_manager/models.py:103 +#: mailbox_manager/models.py:107 msgid "User/mail domain relations" msgstr "Relations entre un utilisateur et un domaine de mail" -#: mailbox_manager/models.py:175 +#: mailbox_manager/models.py:179 msgid "local_part" msgstr "local_part" -#: mailbox_manager/models.py:189 +#: mailbox_manager/models.py:193 msgid "secondary email address" msgstr "adresse email secondaire" -#: mailbox_manager/models.py:199 +#: mailbox_manager/models.py:203 msgid "Mailbox" msgstr "Boîte mail" -#: mailbox_manager/models.py:200 +#: mailbox_manager/models.py:204 msgid "Mailboxes" msgstr "Boîtes mails" -#: mailbox_manager/models.py:224 +#: mailbox_manager/models.py:228 msgid "You can't create or update a mailbox for a disabled domain." msgstr "" "Vous ne pouvez pas créer ou modifier une boîte mail pour un domain désactivé." diff --git a/src/backend/mailbox_manager/migrations/0019_maildomain_last_check_details.py b/src/backend/mailbox_manager/migrations/0019_maildomain_last_check_details.py new file mode 100644 index 0000000..a724753 --- /dev/null +++ b/src/backend/mailbox_manager/migrations/0019_maildomain_last_check_details.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.5 on 2025-02-07 20:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mailbox_manager', '0018_maildomain_support_email'), + ] + + operations = [ + migrations.AddField( + model_name='maildomain', + name='last_check_details', + field=models.JSONField(blank=True, help_text='A JSON object containing the last health check details', null=True, verbose_name='last check details'), + ), + ] diff --git a/src/backend/mailbox_manager/models.py b/src/backend/mailbox_manager/models.py index ca41690..d2905fc 100644 --- a/src/backend/mailbox_manager/models.py +++ b/src/backend/mailbox_manager/models.py @@ -30,6 +30,12 @@ class MailDomain(BaseModel): choices=MailDomainStatusChoices.choices, ) support_email = models.EmailField(_("support email"), null=False, blank=False) + last_check_details = models.JSONField( + null=True, + blank=True, + verbose_name=_("last check details"), + help_text=_("A JSON object containing the last health check details"), + ) class Meta: db_table = "people_mail_domain" diff --git a/src/backend/mailbox_manager/tests/test_utils_dimail_client.py b/src/backend/mailbox_manager/tests/test_utils_dimail_client.py index 14e4a64..445db3f 100644 --- a/src/backend/mailbox_manager/tests/test_utils_dimail_client.py +++ b/src/backend/mailbox_manager/tests/test_utils_dimail_client.py @@ -194,11 +194,13 @@ def test_dimail__fetch_domain_status__switch_to_enabled(domain_status): dimail_client.fetch_domain_status(domain) domain.refresh_from_db() assert domain.status == enums.MailDomainStatusChoices.ENABLED + assert domain.last_check_details == body_content # call again, should be ok dimail_client.fetch_domain_status(domain) domain.refresh_from_db() assert domain.status == enums.MailDomainStatusChoices.ENABLED + assert domain.last_check_details == body_content @pytest.mark.parametrize( @@ -217,12 +219,12 @@ def test_dimail__fetch_domain_status__switch_to_action_required( """Domains should be in status action required when dimail check returns broken status for external checks.""" domain = factories.MailDomainFactory(status=domain_status) - body_content = CHECK_DOMAIN_BROKEN_EXTERNAL.copy() - body_content["name"] = domain.name + body_domain_broken = CHECK_DOMAIN_BROKEN_EXTERNAL.copy() + body_domain_broken["name"] = domain.name responses.add( responses.GET, re.compile(rf".*/domains/{domain.name}/check/"), - body=json.dumps(body_content), + body=json.dumps(body_domain_broken), status=status.HTTP_200_OK, content_type="application/json", ) @@ -230,21 +232,23 @@ def test_dimail__fetch_domain_status__switch_to_action_required( dimail_client.fetch_domain_status(domain) domain.refresh_from_db() assert domain.status == enums.MailDomainStatusChoices.ACTION_REQUIRED + assert domain.last_check_details == body_domain_broken # Support team fixes their part of the problem # Now domain is OK again - body_content = CHECK_DOMAIN_OK.copy() - body_content["name"] = domain.name + body_domain_ok = CHECK_DOMAIN_OK.copy() + body_domain_ok["name"] = domain.name responses.add( responses.GET, re.compile(rf".*/domains/{domain.name}/check/"), - body=json.dumps(body_content), + body=json.dumps(body_domain_ok), status=status.HTTP_200_OK, content_type="application/json", ) dimail_client.fetch_domain_status(domain) domain.refresh_from_db() assert domain.status == enums.MailDomainStatusChoices.ENABLED + assert domain.last_check_details == body_domain_ok @pytest.mark.parametrize( @@ -261,12 +265,12 @@ def test_dimail__fetch_domain_status__switch_to_failed(domain_status): for only internal checks dispite a fix call.""" domain = factories.MailDomainFactory(status=domain_status) # nothing can be done by support team, domain should be in failed - body_content = CHECK_DOMAIN_BROKEN_INTERNAL.copy() - body_content["name"] = domain.name + body_domain_broken = CHECK_DOMAIN_BROKEN_INTERNAL.copy() + body_domain_broken["name"] = domain.name responses.add( responses.GET, re.compile(rf".*/domains/{domain.name}/check/"), - body=json.dumps(body_content), + body=json.dumps(body_domain_broken), status=status.HTTP_200_OK, content_type="application/json", ) @@ -274,7 +278,7 @@ def test_dimail__fetch_domain_status__switch_to_failed(domain_status): responses.add( responses.GET, re.compile(rf".*/domains/{domain.name}/fix/"), - body=json.dumps(body_content), + body=json.dumps(body_domain_broken), status=status.HTTP_200_OK, content_type="application/json", ) @@ -282,6 +286,7 @@ def test_dimail__fetch_domain_status__switch_to_failed(domain_status): dimail_client.fetch_domain_status(domain) domain.refresh_from_db() assert domain.status == enums.MailDomainStatusChoices.FAILED + assert domain.last_check_details == body_domain_broken @pytest.mark.parametrize( @@ -298,12 +303,12 @@ def test_dimail__fetch_domain_status__full_fix_scenario(domain_status): after a fix call.""" domain = factories.MailDomainFactory(status=domain_status) # with all checks KO, domain should be in action required - body_content = CHECK_DOMAIN_BROKEN.copy() - body_content["name"] = domain.name + body_domain_broken = CHECK_DOMAIN_BROKEN.copy() + body_domain_broken["name"] = domain.name responses.add( responses.GET, re.compile(rf".*/domains/{domain.name}/check/"), - body=json.dumps(body_content), + body=json.dumps(body_domain_broken), status=status.HTTP_200_OK, content_type="application/json", ) @@ -311,27 +316,28 @@ def test_dimail__fetch_domain_status__full_fix_scenario(domain_status): dimail_client.fetch_domain_status(domain) domain.refresh_from_db() assert domain.status == enums.MailDomainStatusChoices.ACTION_REQUIRED + assert domain.last_check_details == body_domain_broken # We assume that the support has fixed their part. # So now dimail returns OK for external checks but still KO for internal checks. # A call to dimail fix endpoint is necessary and will be done by # the fetch_domain_status call - body_content = CHECK_DOMAIN_BROKEN_INTERNAL.copy() - body_content["name"] = domain.name + body_domain_broken_internal = CHECK_DOMAIN_BROKEN_INTERNAL.copy() + body_domain_broken_internal["name"] = domain.name responses.add( responses.GET, re.compile(rf".*/domains/{domain.name}/check/"), - body=json.dumps(body_content), + body=json.dumps(body_domain_broken_internal), status=status.HTTP_200_OK, content_type="application/json", ) # the endpoint fix is called and returns OK. Hooray! - body_content = CHECK_DOMAIN_OK.copy() - body_content["name"] = domain.name + body_domain_ok = CHECK_DOMAIN_OK.copy() + body_domain_ok["name"] = domain.name responses.add( responses.GET, re.compile(rf".*/domains/{domain.name}/fix/"), - body=json.dumps(body_content), + body=json.dumps(body_domain_ok), status=status.HTTP_200_OK, content_type="application/json", ) @@ -339,6 +345,7 @@ def test_dimail__fetch_domain_status__full_fix_scenario(domain_status): dimail_client.fetch_domain_status(domain) domain.refresh_from_db() assert domain.status == enums.MailDomainStatusChoices.ENABLED + assert domain.last_check_details == body_domain_ok def test_dimail__enable_pending_mailboxes(caplog): diff --git a/src/backend/mailbox_manager/utils/dimail.py b/src/backend/mailbox_manager/utils/dimail.py index d8c48eb..837eb97 100644 --- a/src/backend/mailbox_manager/utils/dimail.py +++ b/src/backend/mailbox_manager/utils/dimail.py @@ -456,6 +456,7 @@ class DimailAPIClient: ): self.enable_pending_mailboxes(domain) domain.status = enums.MailDomainStatusChoices.ENABLED + domain.last_check_details = dimail_response domain.save() # if dimail returns broken status, we need to extract external and internal checks # and manage the case where the domain has to be fixed by support @@ -465,6 +466,7 @@ class DimailAPIClient: # manage the case where the domain has to be fixed by support if not all(external_checks.values()): domain.status = enums.MailDomainStatusChoices.ACTION_REQUIRED + domain.last_check_details = dimail_response domain.save() # if all external checks are ok but not internal checks, we need to fix internal checks elif all(external_checks.values()) and not all(internal_checks.values()): @@ -479,12 +481,20 @@ class DimailAPIClient: ) if all(external_checks.values()) and all(internal_checks.values()): domain.status = enums.MailDomainStatusChoices.ENABLED + domain.last_check_details = dimail_response domain.save() elif all(external_checks.values()) and not all( internal_checks.values() ): domain.status = enums.MailDomainStatusChoices.FAILED + domain.last_check_details = dimail_response domain.save() + + # if no health check data is stored on the domain, we store it now + if not domain.last_check_details: + domain.last_check_details = dimail_response + domain.save() + return dimail_response def _get_dimail_checks(self, dimail_response, internal: bool):