✨(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:
committed by
Marie
parent
9d9216cf39
commit
cd94dc5091
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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}."
|
||||||
|
)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user