(domains) store last check domain results

Store results of last dimail check on a domain.
This commit is contained in:
Sabrina Demagny
2025-02-10 12:21:58 +01:00
parent d7957547f8
commit 961bceb64e
7 changed files with 78 additions and 28 deletions

View File

@@ -10,6 +10,7 @@ and this project adheres to
### Added ### Added
- ✨(domains) store last health check details on MailDomain
- ✨(domains) add support email field on domain - ✨(domains) add support email field on domain
## [1.11.0] - 2025-02-07 ## [1.11.0] - 2025-02-07

View File

@@ -591,39 +591,47 @@ msgstr "Désactivé"
msgid "Action required" msgid "Action required"
msgstr "Action requise" 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" msgid "Mail domain"
msgstr "Domaine de messagerie" msgstr "Domaine de messagerie"
#: mailbox_manager/models.py:36 #: mailbox_manager/models.py:40
msgid "Mail domains" msgid "Mail domains"
msgstr "Domaines de messagerie" msgstr "Domaines de messagerie"
#: mailbox_manager/models.py:102 #: mailbox_manager/models.py:106
msgid "User/mail domain relation" msgid "User/mail domain relation"
msgstr "Relation entre un utilisateur et un domaine de mail" 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" msgid "User/mail domain relations"
msgstr "Relations entre un utilisateur et un domaine de mail" msgstr "Relations entre un utilisateur et un domaine de mail"
#: mailbox_manager/models.py:175 #: mailbox_manager/models.py:179
msgid "local_part" msgid "local_part"
msgstr "local_part" msgstr "local_part"
#: mailbox_manager/models.py:189 #: mailbox_manager/models.py:193
msgid "secondary email address" msgid "secondary email address"
msgstr "adresse email secondaire" msgstr "adresse email secondaire"
#: mailbox_manager/models.py:199 #: mailbox_manager/models.py:203
msgid "Mailbox" msgid "Mailbox"
msgstr "Boîte mail" msgstr "Boîte mail"
#: mailbox_manager/models.py:200 #: mailbox_manager/models.py:204
msgid "Mailboxes" msgid "Mailboxes"
msgstr "Boîtes mails" 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." msgid "You can't create or update a mailbox for a disabled domain."
msgstr "" msgstr ""
"Vous ne pouvez pas créer ou modifier une boîte mail pour un domain désactivé." "Vous ne pouvez pas créer ou modifier une boîte mail pour un domain désactivé."

View File

@@ -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'),
),
]

View File

@@ -30,6 +30,12 @@ class MailDomain(BaseModel):
choices=MailDomainStatusChoices.choices, choices=MailDomainStatusChoices.choices,
) )
support_email = models.EmailField(_("support email"), null=False, blank=False) 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: class Meta:
db_table = "people_mail_domain" db_table = "people_mail_domain"

View File

@@ -194,11 +194,13 @@ def test_dimail__fetch_domain_status__switch_to_enabled(domain_status):
dimail_client.fetch_domain_status(domain) dimail_client.fetch_domain_status(domain)
domain.refresh_from_db() domain.refresh_from_db()
assert domain.status == enums.MailDomainStatusChoices.ENABLED assert domain.status == enums.MailDomainStatusChoices.ENABLED
assert domain.last_check_details == body_content
# call again, should be ok # call again, should be ok
dimail_client.fetch_domain_status(domain) dimail_client.fetch_domain_status(domain)
domain.refresh_from_db() domain.refresh_from_db()
assert domain.status == enums.MailDomainStatusChoices.ENABLED assert domain.status == enums.MailDomainStatusChoices.ENABLED
assert domain.last_check_details == body_content
@pytest.mark.parametrize( @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 """Domains should be in status action required when dimail check
returns broken status for external checks.""" returns broken status for external checks."""
domain = factories.MailDomainFactory(status=domain_status) domain = factories.MailDomainFactory(status=domain_status)
body_content = CHECK_DOMAIN_BROKEN_EXTERNAL.copy() body_domain_broken = CHECK_DOMAIN_BROKEN_EXTERNAL.copy()
body_content["name"] = domain.name body_domain_broken["name"] = domain.name
responses.add( responses.add(
responses.GET, responses.GET,
re.compile(rf".*/domains/{domain.name}/check/"), re.compile(rf".*/domains/{domain.name}/check/"),
body=json.dumps(body_content), body=json.dumps(body_domain_broken),
status=status.HTTP_200_OK, status=status.HTTP_200_OK,
content_type="application/json", content_type="application/json",
) )
@@ -230,21 +232,23 @@ def test_dimail__fetch_domain_status__switch_to_action_required(
dimail_client.fetch_domain_status(domain) dimail_client.fetch_domain_status(domain)
domain.refresh_from_db() domain.refresh_from_db()
assert domain.status == enums.MailDomainStatusChoices.ACTION_REQUIRED assert domain.status == enums.MailDomainStatusChoices.ACTION_REQUIRED
assert domain.last_check_details == body_domain_broken
# Support team fixes their part of the problem # Support team fixes their part of the problem
# Now domain is OK again # Now domain is OK again
body_content = CHECK_DOMAIN_OK.copy() body_domain_ok = CHECK_DOMAIN_OK.copy()
body_content["name"] = domain.name body_domain_ok["name"] = domain.name
responses.add( responses.add(
responses.GET, responses.GET,
re.compile(rf".*/domains/{domain.name}/check/"), re.compile(rf".*/domains/{domain.name}/check/"),
body=json.dumps(body_content), body=json.dumps(body_domain_ok),
status=status.HTTP_200_OK, status=status.HTTP_200_OK,
content_type="application/json", content_type="application/json",
) )
dimail_client.fetch_domain_status(domain) dimail_client.fetch_domain_status(domain)
domain.refresh_from_db() domain.refresh_from_db()
assert domain.status == enums.MailDomainStatusChoices.ENABLED assert domain.status == enums.MailDomainStatusChoices.ENABLED
assert domain.last_check_details == body_domain_ok
@pytest.mark.parametrize( @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.""" for only internal checks dispite a fix call."""
domain = factories.MailDomainFactory(status=domain_status) domain = factories.MailDomainFactory(status=domain_status)
# nothing can be done by support team, domain should be in failed # nothing can be done by support team, domain should be in failed
body_content = CHECK_DOMAIN_BROKEN_INTERNAL.copy() body_domain_broken = CHECK_DOMAIN_BROKEN_INTERNAL.copy()
body_content["name"] = domain.name body_domain_broken["name"] = domain.name
responses.add( responses.add(
responses.GET, responses.GET,
re.compile(rf".*/domains/{domain.name}/check/"), re.compile(rf".*/domains/{domain.name}/check/"),
body=json.dumps(body_content), body=json.dumps(body_domain_broken),
status=status.HTTP_200_OK, status=status.HTTP_200_OK,
content_type="application/json", content_type="application/json",
) )
@@ -274,7 +278,7 @@ def test_dimail__fetch_domain_status__switch_to_failed(domain_status):
responses.add( responses.add(
responses.GET, responses.GET,
re.compile(rf".*/domains/{domain.name}/fix/"), re.compile(rf".*/domains/{domain.name}/fix/"),
body=json.dumps(body_content), body=json.dumps(body_domain_broken),
status=status.HTTP_200_OK, status=status.HTTP_200_OK,
content_type="application/json", 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) dimail_client.fetch_domain_status(domain)
domain.refresh_from_db() domain.refresh_from_db()
assert domain.status == enums.MailDomainStatusChoices.FAILED assert domain.status == enums.MailDomainStatusChoices.FAILED
assert domain.last_check_details == body_domain_broken
@pytest.mark.parametrize( @pytest.mark.parametrize(
@@ -298,12 +303,12 @@ def test_dimail__fetch_domain_status__full_fix_scenario(domain_status):
after a fix call.""" after a fix call."""
domain = factories.MailDomainFactory(status=domain_status) domain = factories.MailDomainFactory(status=domain_status)
# with all checks KO, domain should be in action required # with all checks KO, domain should be in action required
body_content = CHECK_DOMAIN_BROKEN.copy() body_domain_broken = CHECK_DOMAIN_BROKEN.copy()
body_content["name"] = domain.name body_domain_broken["name"] = domain.name
responses.add( responses.add(
responses.GET, responses.GET,
re.compile(rf".*/domains/{domain.name}/check/"), re.compile(rf".*/domains/{domain.name}/check/"),
body=json.dumps(body_content), body=json.dumps(body_domain_broken),
status=status.HTTP_200_OK, status=status.HTTP_200_OK,
content_type="application/json", 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) dimail_client.fetch_domain_status(domain)
domain.refresh_from_db() domain.refresh_from_db()
assert domain.status == enums.MailDomainStatusChoices.ACTION_REQUIRED assert domain.status == enums.MailDomainStatusChoices.ACTION_REQUIRED
assert domain.last_check_details == body_domain_broken
# We assume that the support has fixed their part. # We assume that the support has fixed their part.
# So now dimail returns OK for external checks but still KO for internal checks. # 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 # A call to dimail fix endpoint is necessary and will be done by
# the fetch_domain_status call # the fetch_domain_status call
body_content = CHECK_DOMAIN_BROKEN_INTERNAL.copy() body_domain_broken_internal = CHECK_DOMAIN_BROKEN_INTERNAL.copy()
body_content["name"] = domain.name body_domain_broken_internal["name"] = domain.name
responses.add( responses.add(
responses.GET, responses.GET,
re.compile(rf".*/domains/{domain.name}/check/"), re.compile(rf".*/domains/{domain.name}/check/"),
body=json.dumps(body_content), body=json.dumps(body_domain_broken_internal),
status=status.HTTP_200_OK, status=status.HTTP_200_OK,
content_type="application/json", content_type="application/json",
) )
# the endpoint fix is called and returns OK. Hooray! # the endpoint fix is called and returns OK. Hooray!
body_content = CHECK_DOMAIN_OK.copy() body_domain_ok = CHECK_DOMAIN_OK.copy()
body_content["name"] = domain.name body_domain_ok["name"] = domain.name
responses.add( responses.add(
responses.GET, responses.GET,
re.compile(rf".*/domains/{domain.name}/fix/"), re.compile(rf".*/domains/{domain.name}/fix/"),
body=json.dumps(body_content), body=json.dumps(body_domain_ok),
status=status.HTTP_200_OK, status=status.HTTP_200_OK,
content_type="application/json", 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) dimail_client.fetch_domain_status(domain)
domain.refresh_from_db() domain.refresh_from_db()
assert domain.status == enums.MailDomainStatusChoices.ENABLED assert domain.status == enums.MailDomainStatusChoices.ENABLED
assert domain.last_check_details == body_domain_ok
def test_dimail__enable_pending_mailboxes(caplog): def test_dimail__enable_pending_mailboxes(caplog):

View File

@@ -456,6 +456,7 @@ class DimailAPIClient:
): ):
self.enable_pending_mailboxes(domain) self.enable_pending_mailboxes(domain)
domain.status = enums.MailDomainStatusChoices.ENABLED domain.status = enums.MailDomainStatusChoices.ENABLED
domain.last_check_details = dimail_response
domain.save() domain.save()
# if dimail returns broken status, we need to extract external and internal checks # 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 # 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 # manage the case where the domain has to be fixed by support
if not all(external_checks.values()): if not all(external_checks.values()):
domain.status = enums.MailDomainStatusChoices.ACTION_REQUIRED domain.status = enums.MailDomainStatusChoices.ACTION_REQUIRED
domain.last_check_details = dimail_response
domain.save() domain.save()
# if all external checks are ok but not internal checks, we need to fix internal checks # 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()): 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()): if all(external_checks.values()) and all(internal_checks.values()):
domain.status = enums.MailDomainStatusChoices.ENABLED domain.status = enums.MailDomainStatusChoices.ENABLED
domain.last_check_details = dimail_response
domain.save() domain.save()
elif all(external_checks.values()) and not all( elif all(external_checks.values()) and not all(
internal_checks.values() internal_checks.values()
): ):
domain.status = enums.MailDomainStatusChoices.FAILED domain.status = enums.MailDomainStatusChoices.FAILED
domain.last_check_details = dimail_response
domain.save() 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 return dimail_response
def _get_dimail_checks(self, dimail_response, internal: bool): def _get_dimail_checks(self, dimail_response, internal: bool):