(dimail) manage 'action required' status for MailDomain

Adapt fetch domain status call to manage internal and external
fixes required. Use the new status 'action required' to
manage actions expected from support.
Call a new dimail endpoint to run a fix for internal checks
when all external checks are OK.
This commit is contained in:
Sabrina Demagny
2025-02-04 13:26:15 +01:00
parent e7af1fd591
commit 4df4172151
6 changed files with 463 additions and 116 deletions

View File

@@ -8,9 +8,13 @@ from django.core.management import call_command
import pytest
import responses
from rest_framework import status
from mailbox_manager import enums, factories
from mailbox_manager.tests.fixtures.dimail import CHECK_DOMAIN_BROKEN, CHECK_DOMAIN_OK
from mailbox_manager.tests.fixtures.dimail import (
CHECK_DOMAIN_BROKEN,
CHECK_DOMAIN_OK,
)
pytestmark = pytest.mark.django_db
@@ -48,7 +52,7 @@ def test_fetch_domain_status():
responses.GET,
re.compile(rf".*/domains/{domain.name}/check/"),
body=json.dumps(body_content),
status=200,
status=status.HTTP_200_OK,
content_type="application/json",
)
output = StringIO()
@@ -59,8 +63,8 @@ def test_fetch_domain_status():
domain_failed.refresh_from_db()
# nothing change for the first domain enable
assert domain_enabled1.status == enums.MailDomainStatusChoices.ENABLED
# status of the second activated domain has changed to failure
assert domain_enabled2.status == enums.MailDomainStatusChoices.FAILED
# status of the second activated domain has changed to action required
assert domain_enabled2.status == enums.MailDomainStatusChoices.ACTION_REQUIRED
# status of the failed domain has changed to enabled
assert domain_failed.status == enums.MailDomainStatusChoices.ENABLED
# disabled domain was excluded

View File

@@ -16,7 +16,11 @@ CHECK_DOMAIN_BROKEN = {
"smtp_domain": None,
"context_name": "example.fr",
"transport": None,
"domain_exist": {"ok": True, "internal": False, "errors": []},
"domain_exist": {
"ok": True,
"internal": False,
"errors": [],
},
"mx": {
"ok": False,
"internal": False,
@@ -74,8 +78,16 @@ CHECK_DOMAIN_BROKEN = {
{"code": "no_dkim", "detail": "Il faut un DKIM record, avec la bonne clef"}
],
},
"postfix": {"ok": True, "internal": True, "errors": []},
"ox": {"ok": True, "internal": True, "errors": []},
"postfix": {
"ok": True,
"internal": True,
"errors": [],
},
"ox": {
"ok": True,
"internal": True,
"errors": [],
},
"cert": {
"ok": False,
"internal": True,
@@ -85,6 +97,141 @@ CHECK_DOMAIN_BROKEN = {
},
}
CHECK_DOMAIN_BROKEN_INTERNAL = {
"name": "example.fr",
"state": "broken",
"valid": False,
"delivery": "virtual",
"features": ["webmail", "mailbox"],
"webmail_domain": None,
"imap_domain": None,
"smtp_domain": None,
"context_name": "example.fr",
"transport": None,
"domain_exist": {
"ok": True,
"internal": False,
"errors": [],
},
"mx": {
"ok": True,
"internal": False,
"errors": [],
},
"cname_imap": {
"ok": True,
"internal": False,
"errors": [],
},
"cname_smtp": {
"ok": True,
"internal": False,
"errors": [],
},
"cname_webmail": {
"ok": True,
"internal": False,
"errors": [],
},
"spf": {
"ok": True,
"internal": False,
"errors": [],
},
"dkim": {
"ok": True,
"internal": False,
"errors": [],
},
"postfix": {
"ok": True,
"internal": True,
"errors": [],
},
"ox": {
"ok": True,
"internal": True,
"errors": [],
},
"cert": {
"ok": False,
"internal": True,
"errors": [
{"code": "no_cert", "detail": "Pas de certificat pour ce domaine (ls)"}
],
},
}
CHECK_DOMAIN_BROKEN_EXTERNAL = {
"name": "example.fr",
"state": "broken",
"valid": False,
"delivery": "virtual",
"features": ["webmail", "mailbox"],
"webmail_domain": None,
"imap_domain": None,
"smtp_domain": None,
"context_name": "example.fr",
"transport": None,
"domain_exist": {
"ok": True,
"internal": False,
"errors": [],
},
"mx": {
"ok": False,
"internal": False,
"errors": [
{
"code": "wrong_mx",
"detail": "Je veux que le MX du domaine soit mx.ox.numerique.gouv.fr., or je trouve example-fr.mail.protection.outlook.com.",
}
],
},
"cname_imap": {
"ok": True,
"internal": False,
"errors": [],
},
"cname_smtp": {
"ok": True,
"internal": False,
"errors": [],
},
"cname_webmail": {
"ok": True,
"internal": False,
"errors": [],
},
"spf": {
"ok": True,
"internal": False,
"errors": [],
},
"dkim": {
"ok": True,
"internal": False,
"errors": [],
},
"postfix": {
"ok": True,
"internal": True,
"errors": [],
},
"ox": {
"ok": True,
"internal": True,
"errors": [],
},
"cert": {
"ok": True,
"internal": True,
"errors": [],
},
}
CHECK_DOMAIN_OK = {
"name": "example.fr",
"state": "ok",

View File

@@ -55,6 +55,7 @@ def test_sync_mailboxes__should_not_sync_if_domain_is_not_enabled(
)
@responses.activate
@pytest.mark.django_db
def test_fetch_domain_status__should_switch_to_failed_when_domain_broken(client):
"""Test admin action to check health of some domains"""
@@ -70,35 +71,34 @@ def test_fetch_domain_status__should_switch_to_failed_when_domain_broken(client)
],
}
url = reverse("admin:mailbox_manager_maildomain_changelist")
with responses.RequestsMock() as rsps:
body_content_domain1 = CHECK_DOMAIN_BROKEN.copy()
body_content_domain1["name"] = domain1.name
body_content_domain2 = CHECK_DOMAIN_BROKEN.copy()
body_content_domain2["name"] = domain2.name
rsps.add(
rsps.GET,
re.compile(rf".*/domains/{domain1.name}/check/"),
body=json.dumps(body_content_domain1),
status=status.HTTP_200_OK,
content_type="application/json",
)
rsps.add(
rsps.GET,
re.compile(rf".*/domains/{domain2.name}/check/"),
body=json.dumps(body_content_domain2),
status=status.HTTP_200_OK,
content_type="application/json",
)
response = client.post(url, data, follow=True)
assert response.status_code == status.HTTP_200_OK
domain1.refresh_from_db()
domain2.refresh_from_db()
assert domain1.status == enums.MailDomainStatusChoices.FAILED
assert domain2.status == enums.MailDomainStatusChoices.FAILED
assert "Check domains done with success" in response.content.decode("utf-8")
body_content_domain1 = CHECK_DOMAIN_BROKEN.copy()
body_content_domain1["name"] = domain1.name
body_content_domain2 = CHECK_DOMAIN_BROKEN.copy()
body_content_domain2["name"] = domain2.name
responses.add(
responses.GET,
re.compile(rf".*/domains/{domain1.name}/check/"),
body=json.dumps(body_content_domain1),
status=status.HTTP_200_OK,
content_type="application/json",
)
responses.add(
responses.GET,
re.compile(rf".*/domains/{domain2.name}/check/"),
body=json.dumps(body_content_domain2),
status=status.HTTP_200_OK,
content_type="application/json",
)
response = client.post(url, data, follow=True)
assert response.status_code == status.HTTP_200_OK
domain1.refresh_from_db()
domain2.refresh_from_db()
assert domain1.status == enums.MailDomainStatusChoices.ACTION_REQUIRED
assert domain2.status == enums.MailDomainStatusChoices.ACTION_REQUIRED
assert "Check domains done with success" in response.content.decode("utf-8")
@responses.activate
@pytest.mark.django_db
def test_fetch_domain_status__should_switch_to_enabled_when_domain_ok(client):
"""Test admin action should switch domain state to ENABLED
@@ -115,33 +115,33 @@ def test_fetch_domain_status__should_switch_to_enabled_when_domain_ok(client):
}
url = reverse("admin:mailbox_manager_maildomain_changelist")
with responses.RequestsMock() as rsps:
body_content_domain1 = CHECK_DOMAIN_OK.copy()
body_content_domain1["name"] = domain1.name
body_content_domain1 = CHECK_DOMAIN_OK.copy()
body_content_domain1["name"] = domain1.name
rsps.add(
rsps.GET,
re.compile(rf".*/domains/{domain1.name}/check/"),
body=json.dumps(body_content_domain1),
status=status.HTTP_200_OK,
content_type="application/json",
)
rsps.add(
rsps.GET,
re.compile(r".*/token/"),
body=TOKEN_OK,
status=status.HTTP_200_OK,
content_type="application/json",
)
rsps.add(
rsps.POST,
re.compile(rf".*/domains/{domain1.name}/mailboxes/"),
body=response_mailbox_created(f"truc@{domain1.name}"),
status=status.HTTP_201_CREATED,
content_type="application/json",
)
responses.add(
responses.GET,
re.compile(rf".*/domains/{domain1.name}/check/"),
body=json.dumps(body_content_domain1),
status=status.HTTP_200_OK,
content_type="application/json",
)
# we need to get a token to create mailboxes
responses.add(
responses.GET,
re.compile(r".*/token/"),
body=TOKEN_OK,
status=status.HTTP_200_OK,
content_type="application/json",
)
responses.add(
responses.POST,
re.compile(rf".*/domains/{domain1.name}/mailboxes/"),
body=response_mailbox_created(f"truc@{domain1.name}"),
status=status.HTTP_201_CREATED,
content_type="application/json",
)
response = client.post(url, data, follow=True)
response = client.post(url, data, follow=True)
assert response.status_code == status.HTTP_200_OK
domain1.refresh_from_db()
domain2.refresh_from_db()

View File

@@ -18,6 +18,8 @@ from mailbox_manager.utils.dimail import DimailAPIClient
from .fixtures.dimail import (
CHECK_DOMAIN_BROKEN,
CHECK_DOMAIN_BROKEN_EXTERNAL,
CHECK_DOMAIN_BROKEN_INTERNAL,
CHECK_DOMAIN_OK,
TOKEN_OK,
response_mailbox_created,
@@ -166,40 +168,180 @@ def test_dimail_synchronization__synchronize_mailboxes(mock_warning):
assert imported_mailboxes == [mailbox_valid["email"]]
def test_dimail__fetch_domain_status_from_dimail():
"""Request to dimail health status of a domain"""
domain = factories.MailDomainEnabledFactory()
with responses.RequestsMock() as rsps:
body_content = CHECK_DOMAIN_BROKEN.copy()
body_content["name"] = domain.name
rsps.add(
rsps.GET,
re.compile(rf".*/domains/{domain.name}/check/"),
body=json.dumps(body_content),
status=status.HTTP_200_OK,
content_type="application/json",
)
dimail_client = DimailAPIClient()
response = dimail_client.fetch_domain_status(domain)
assert response.status_code == status.HTTP_200_OK
assert domain.status == enums.MailDomainStatusChoices.FAILED
@pytest.mark.parametrize(
"domain_status",
[
enums.MailDomainStatusChoices.PENDING,
enums.MailDomainStatusChoices.ACTION_REQUIRED,
enums.MailDomainStatusChoices.FAILED,
enums.MailDomainStatusChoices.ENABLED,
],
)
@responses.activate
def test_dimail__fetch_domain_status__switch_to_enabled(domain_status):
"""Domains should be enabled when dimail check returns ok status"""
domain = factories.MailDomainFactory(status=domain_status)
body_content = CHECK_DOMAIN_OK.copy()
body_content["name"] = domain.name
responses.add(
responses.GET,
re.compile(rf".*/domains/{domain.name}/check/"),
body=json.dumps(body_content),
status=status.HTTP_200_OK,
content_type="application/json",
)
dimail_client = DimailAPIClient()
dimail_client.fetch_domain_status(domain)
domain.refresh_from_db()
assert domain.status == enums.MailDomainStatusChoices.ENABLED
# Now domain is ok again
body_content = CHECK_DOMAIN_OK.copy()
body_content["name"] = domain.name
rsps.add(
rsps.GET,
re.compile(rf".*/domains/{domain.name}/check/"),
body=json.dumps(body_content),
status=status.HTTP_200_OK,
content_type="application/json",
)
response = dimail_client.fetch_domain_status(domain)
assert response.status_code == status.HTTP_200_OK
assert domain.status == enums.MailDomainStatusChoices.ENABLED
# call again, should be ok
dimail_client.fetch_domain_status(domain)
domain.refresh_from_db()
assert domain.status == enums.MailDomainStatusChoices.ENABLED
def test_dimail___enable_pending_mailboxes(caplog):
@pytest.mark.parametrize(
"domain_status",
[
enums.MailDomainStatusChoices.PENDING,
enums.MailDomainStatusChoices.ENABLED,
enums.MailDomainStatusChoices.ACTION_REQUIRED,
enums.MailDomainStatusChoices.FAILED,
],
)
@responses.activate
def test_dimail__fetch_domain_status__switch_to_action_required(
domain_status,
):
"""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
responses.add(
responses.GET,
re.compile(rf".*/domains/{domain.name}/check/"),
body=json.dumps(body_content),
status=status.HTTP_200_OK,
content_type="application/json",
)
dimail_client = DimailAPIClient()
dimail_client.fetch_domain_status(domain)
domain.refresh_from_db()
assert domain.status == enums.MailDomainStatusChoices.ACTION_REQUIRED
# Support team fixes their part of the problem
# Now domain is OK again
body_content = CHECK_DOMAIN_OK.copy()
body_content["name"] = domain.name
responses.add(
responses.GET,
re.compile(rf".*/domains/{domain.name}/check/"),
body=json.dumps(body_content),
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
@pytest.mark.parametrize(
"domain_status",
[
enums.MailDomainStatusChoices.PENDING,
enums.MailDomainStatusChoices.ENABLED,
enums.MailDomainStatusChoices.ACTION_REQUIRED,
],
)
@responses.activate
def test_dimail__fetch_domain_status__switch_to_failed(domain_status):
"""Domains should be in status failed when dimail check returns broken 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
responses.add(
responses.GET,
re.compile(rf".*/domains/{domain.name}/check/"),
body=json.dumps(body_content),
status=status.HTTP_200_OK,
content_type="application/json",
)
# the endpoint fix is called and still returns KO for internal checks
responses.add(
responses.GET,
re.compile(rf".*/domains/{domain.name}/fix/"),
body=json.dumps(body_content),
status=status.HTTP_200_OK,
content_type="application/json",
)
dimail_client = DimailAPIClient()
dimail_client.fetch_domain_status(domain)
domain.refresh_from_db()
assert domain.status == enums.MailDomainStatusChoices.FAILED
@pytest.mark.parametrize(
"domain_status",
[
enums.MailDomainStatusChoices.PENDING,
enums.MailDomainStatusChoices.ENABLED,
enums.MailDomainStatusChoices.ACTION_REQUIRED,
],
)
@responses.activate
def test_dimail__fetch_domain_status__full_fix_scenario(domain_status):
"""Domains should be enabled when dimail check returns ok 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
responses.add(
responses.GET,
re.compile(rf".*/domains/{domain.name}/check/"),
body=json.dumps(body_content),
status=status.HTTP_200_OK,
content_type="application/json",
)
dimail_client = DimailAPIClient()
dimail_client.fetch_domain_status(domain)
domain.refresh_from_db()
assert domain.status == enums.MailDomainStatusChoices.ACTION_REQUIRED
# 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
responses.add(
responses.GET,
re.compile(rf".*/domains/{domain.name}/check/"),
body=json.dumps(body_content),
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
responses.add(
responses.GET,
re.compile(rf".*/domains/{domain.name}/fix/"),
body=json.dumps(body_content),
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
def test_dimail__enable_pending_mailboxes(caplog):
"""Status of pending mailboxes should switch to "enabled"
when calling enable_pending_mailboxes."""
caplog.set_level(logging.INFO)