(dimail) send pending mailboxes upon domain activation

send creation requests to dimail for all pending mailboxes
when domain goes from "pending" to "enabled".
This commit is contained in:
Marie PUPO JEAMMET
2025-01-10 18:25:07 +01:00
committed by Marie
parent 9d9216cf39
commit cd94dc5091
7 changed files with 172 additions and 29 deletions

View File

@@ -8,10 +8,15 @@ and this project adheres to
## [Unreleased] ## [Unreleased]
### Added
- ✨(dimail) send pending mailboxes upon domain activation
### Fixed ### Fixed
- 🚑️(plugins) fix name from SIRET specific case #674 - 🚑️(plugins) fix name from SIRET specific case #674
## [1.10.1] - 2025-01-27 ## [1.10.1] - 2025-01-27
### Added ### Added

View File

@@ -36,23 +36,19 @@ class MailboxSerializer(serializers.ModelSerializer):
Override create function to fire a request on mailbox creation. Override create function to fire a request on mailbox creation.
""" """
mailbox = super().create(validated_data) mailbox = super().create(validated_data)
if validated_data["domain"].status == enums.MailDomainStatusChoices.ENABLED: if mailbox.domain.status == enums.MailDomainStatusChoices.ENABLED:
client = DimailAPIClient() client = DimailAPIClient()
# send new mailbox request to dimail # send new mailbox request to dimail
response = client.create_mailbox( response = client.create_mailbox(mailbox, self.context["request"].user.sub)
validated_data, self.context["request"].user.sub
)
# fix format to have actual json, and remove uuid # fix format to have actual json
mailbox_data = json.loads( dimail_data = json.loads(response.content.decode("utf-8").replace("'", '"'))
response.content.decode("utf-8").replace("'", '"')
)
mailbox.status = enums.MailDomainStatusChoices.ENABLED mailbox.status = enums.MailDomainStatusChoices.ENABLED
mailbox.save() mailbox.save()
# send confirmation email # send confirmation email
client.notify_mailbox_creation( client.notify_mailbox_creation(
recipient=validated_data["secondary_email"], mailbox_data=mailbox_data recipient=mailbox.secondary_email, mailbox_data=dimail_data
) )
# actually save mailbox on our database # actually save mailbox on our database

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.1.5 on 2025-01-31 17:44
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mailbox_manager', '0015_change_mailboxes_status_to_enabled'),
]
operations = [
migrations.AlterField(
model_name='mailbox',
name='domain',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='mailboxes', to='mailbox_manager.maildomain'),
),
]

View File

@@ -181,7 +181,7 @@ class Mailbox(BaseModel):
domain = models.ForeignKey( domain = models.ForeignKey(
MailDomain, MailDomain,
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name="mail_domain", related_name="mailboxes",
null=False, null=False,
blank=False, blank=False,
) )

View File

@@ -9,16 +9,17 @@ from django.urls import reverse
import pytest import pytest
import responses import responses
from rest_framework import status
from core import factories as core_factories from core import factories as core_factories
from mailbox_manager import enums, factories from mailbox_manager import enums, factories, models
from .fixtures.dimail import CHECK_DOMAIN_BROKEN, CHECK_DOMAIN_OK from .fixtures.dimail import CHECK_DOMAIN_BROKEN, CHECK_DOMAIN_OK
@pytest.mark.django_db @pytest.mark.django_db
def test_admin_action__fetch_domain_status_from_dimail(client): def test_fetch_domain_status__should_switch_to_failed_when_domain_broken(client):
"""Test admin action to check health of some domains""" """Test admin action to check health of some domains"""
admin = core_factories.UserFactory(is_staff=True, is_superuser=True) admin = core_factories.UserFactory(is_staff=True, is_superuser=True)
client.force_login(admin) client.force_login(admin)
@@ -42,38 +43,73 @@ def test_admin_action__fetch_domain_status_from_dimail(client):
rsps.GET, rsps.GET,
re.compile(rf".*/domains/{domain1.name}/check/"), re.compile(rf".*/domains/{domain1.name}/check/"),
body=json.dumps(body_content_domain1), body=json.dumps(body_content_domain1),
status=200, status=status.HTTP_200_OK,
content_type="application/json", content_type="application/json",
) )
rsps.add( rsps.add(
rsps.GET, rsps.GET,
re.compile(rf".*/domains/{domain2.name}/check/"), re.compile(rf".*/domains/{domain2.name}/check/"),
body=json.dumps(body_content_domain2), body=json.dumps(body_content_domain2),
status=200, status=status.HTTP_200_OK,
content_type="application/json", content_type="application/json",
) )
response = client.post(url, data, follow=True) response = client.post(url, data, follow=True)
assert response.status_code == 200 assert response.status_code == status.HTTP_200_OK
domain1.refresh_from_db() domain1.refresh_from_db()
domain2.refresh_from_db() domain2.refresh_from_db()
assert domain1.status == enums.MailDomainStatusChoices.FAILED assert domain1.status == enums.MailDomainStatusChoices.FAILED
assert domain2.status == enums.MailDomainStatusChoices.FAILED assert domain2.status == enums.MailDomainStatusChoices.FAILED
assert "Check domains done with success" in response.content.decode("utf-8") assert "Check domains done with success" in response.content.decode("utf-8")
# check with a valid domain info from dimail
@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
when dimail's response is "ok". It should also activate any pending mailbox."""
admin = core_factories.UserFactory(is_staff=True, is_superuser=True)
client.force_login(admin)
domain1 = factories.MailDomainFactory()
factories.MailboxFactory.create_batch(3, domain=domain1)
domain2 = factories.MailDomainFactory()
data = {
"action": "fetch_domain_status_from_dimail",
"_selected_action": [domain1.id],
}
url = reverse("admin:mailbox_manager_maildomain_changelist")
with responses.RequestsMock() as rsps:
body_content_domain1 = CHECK_DOMAIN_OK.copy() body_content_domain1 = CHECK_DOMAIN_OK.copy()
body_content_domain1["name"] = domain1.name body_content_domain1["name"] = domain1.name
rsps.add( rsps.add(
rsps.GET, rsps.GET,
re.compile(rf".*/domains/{domain1.name}/check/"), re.compile(rf".*/domains/{domain1.name}/check/"),
body=json.dumps(body_content_domain1), body=json.dumps(body_content_domain1),
status=200, status=status.HTTP_200_OK,
content_type="application/json", content_type="application/json",
) )
rsps.add(
rsps.GET,
re.compile(r".*/token/"),
body='{"access_token": "domain_owner_token"}',
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",
)
response = client.post(url, data, follow=True) response = client.post(url, data, follow=True)
assert response.status_code == 200 assert response.status_code == status.HTTP_200_OK
domain1.refresh_from_db() domain1.refresh_from_db()
domain2.refresh_from_db() domain2.refresh_from_db()
assert domain1.status == enums.MailDomainStatusChoices.ENABLED assert domain1.status == enums.MailDomainStatusChoices.ENABLED
assert domain2.status == enums.MailDomainStatusChoices.FAILED assert domain2.status == enums.MailDomainStatusChoices.PENDING
assert "Check domains done with success" in response.content.decode("utf-8") assert "Check domains done with success" in response.content.decode("utf-8")
for mailbox in models.Mailbox.objects.filter(domain=domain1):
assert mailbox.status == enums.MailboxStatusChoices.ENABLED

View File

@@ -3,6 +3,7 @@ Unit tests for dimail client
""" """
import json import json
import logging
import re import re
from email.errors import HeaderParseError, NonASCIILocalPartDefect from email.errors import HeaderParseError, NonASCIILocalPartDefect
from logging import Logger from logging import Logger
@@ -191,3 +192,69 @@ def test_dimail__fetch_domain_status_from_dimail():
response = dimail_client.fetch_domain_status(domain) response = dimail_client.fetch_domain_status(domain)
assert response.status_code == status.HTTP_200_OK assert response.status_code == status.HTTP_200_OK
assert domain.status == enums.MailDomainStatusChoices.ENABLED 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)
domain = factories.MailDomainFactory()
mailbox1 = factories.MailboxFactory(
domain=domain, status=enums.MailboxStatusChoices.PENDING
)
mailbox2 = factories.MailboxFactory(
domain=domain, status=enums.MailboxStatusChoices.PENDING
)
factories.MailboxFactory.create_batch(
2, domain=domain, status=enums.MailboxStatusChoices.ENABLED
)
dimail_client = DimailAPIClient()
with responses.RequestsMock() as rsps:
rsps.add(
rsps.GET,
re.compile(r".*/token/"),
body='{"access_token": "domain_owner_token"}',
status=status.HTTP_200_OK,
content_type="application/json",
)
rsps.add(
rsps.POST,
re.compile(rf".*/domains/{domain.name}/mailboxes/"),
body=str(
{
"email": f"mock@{domain.name}",
"password": "newpass",
"uuid": "uuid",
}
),
status=status.HTTP_201_CREATED,
content_type="application/json",
)
dimail_client.enable_pending_mailboxes(domain=domain)
mailbox1.refresh_from_db()
mailbox2.refresh_from_db()
assert mailbox1.status == enums.MailboxStatusChoices.ENABLED
assert mailbox2.status == enums.MailboxStatusChoices.ENABLED
assert len(caplog.records) == 6
assert (
caplog.records[0].message
== "Token succesfully granted by mail-provisioning API."
)
assert (
caplog.records[1].message
== f"Mailbox successfully created on domain {domain.name} by user None"
)
assert (
caplog.records[2].message
== f"Information for mailbox mock@{domain.name} sent to {mailbox1.secondary_email}."
)
assert (
caplog.records[4].message
== f"Mailbox successfully created on domain {domain.name} by user None"
)
assert (
caplog.records[5].message
== f"Information for mailbox mock@{domain.name} sent to {mailbox2.secondary_email}."
)

View File

@@ -116,15 +116,15 @@ class DimailAPIClient:
"""Send a CREATE mailbox request to mail provisioning API.""" """Send a CREATE mailbox request to mail provisioning API."""
payload = { payload = {
"givenName": mailbox["first_name"], "givenName": mailbox.first_name,
"surName": mailbox["last_name"], "surName": mailbox.last_name,
"displayName": f"{mailbox['first_name']} {mailbox['last_name']}", "displayName": f"{mailbox.first_name} {mailbox.last_name}",
} }
headers = self.get_headers(user_sub) headers = self.get_headers(user_sub)
try: try:
response = session.post( response = session.post(
f"{self.API_URL}/domains/{mailbox['domain']}/mailboxes/{mailbox['local_part']}", f"{self.API_URL}/domains/{mailbox.domain.name}/mailboxes/{mailbox.local_part}",
json=payload, json=payload,
headers=headers, headers=headers,
verify=True, verify=True,
@@ -141,7 +141,7 @@ class DimailAPIClient:
if response.status_code == status.HTTP_201_CREATED: if response.status_code == status.HTTP_201_CREATED:
logger.info( logger.info(
"Mailbox successfully created on domain %s by user %s", "Mailbox successfully created on domain %s by user %s",
str(mailbox["domain"]), str(mailbox.domain),
user_sub, user_sub,
) )
return response return response
@@ -149,7 +149,7 @@ class DimailAPIClient:
if response.status_code == status.HTTP_403_FORBIDDEN: if response.status_code == status.HTTP_403_FORBIDDEN:
logger.error( logger.error(
"[DIMAIL] 403 Forbidden: you cannot access domain %s", "[DIMAIL] 403 Forbidden: you cannot access domain %s",
str(mailbox["domain"]), str(mailbox.domain),
) )
raise exceptions.PermissionDenied( raise exceptions.PermissionDenied(
"Permission denied. Please check your MAIL_PROVISIONING_API_CREDENTIALS." "Permission denied. Please check your MAIL_PROVISIONING_API_CREDENTIALS."
@@ -413,6 +413,7 @@ class DimailAPIClient:
domain.status != enums.MailDomainStatusChoices.ENABLED domain.status != enums.MailDomainStatusChoices.ENABLED
and dimail_status == "ok" and dimail_status == "ok"
): ):
self.enable_pending_mailboxes(domain)
domain.status = enums.MailDomainStatusChoices.ENABLED domain.status = enums.MailDomainStatusChoices.ENABLED
domain.save() domain.save()
elif ( elif (
@@ -423,3 +424,22 @@ class DimailAPIClient:
domain.save() domain.save()
return response return response
return self.raise_exception_for_unexpected_response(response) return self.raise_exception_for_unexpected_response(response)
def enable_pending_mailboxes(self, domain):
"""Send requests for all pending mailboxes of a domain."""
for mailbox in domain.mailboxes.filter(
status=enums.MailboxStatusChoices.PENDING
):
response = self.create_mailbox(mailbox)
# fix format to have actual json
dimail_data = json.loads(response.content.decode("utf-8").replace("'", '"'))
mailbox.status = enums.MailDomainStatusChoices.ENABLED
mailbox.save()
# send confirmation email
self.notify_mailbox_creation(
recipient=mailbox.secondary_email,
mailbox_data=dimail_data,
)