diff --git a/src/backend/mailbox_manager/admin.py b/src/backend/mailbox_manager/admin.py index 19a40fd..75f6b8a 100644 --- a/src/backend/mailbox_manager/admin.py +++ b/src/backend/mailbox_manager/admin.py @@ -15,6 +15,7 @@ class MailDomainAdmin(admin.ModelAdmin): "created_at", "updated_at", "slug", + "status", ) search_fields = ("name",) readonly_fields = ["created_at", "slug"] diff --git a/src/backend/mailbox_manager/enums.py b/src/backend/mailbox_manager/enums.py new file mode 100644 index 0000000..9afc0df --- /dev/null +++ b/src/backend/mailbox_manager/enums.py @@ -0,0 +1,15 @@ +""" +Application enums declaration +""" + +from django.db import models +from django.utils.translation import gettext_lazy as _ + + +class MailDomainStatusChoices(models.TextChoices): + """Defines the possible statuses in which a mail domain can be.""" + + PENDING = "pending", _("Pending") + ENABLED = "enabled", _("Enabled") + FAILED = "failed", _("Failed") + DISABLED = "disabled", _("Disabled") diff --git a/src/backend/mailbox_manager/factories.py b/src/backend/mailbox_manager/factories.py index 2f0ed72..8b876b2 100644 --- a/src/backend/mailbox_manager/factories.py +++ b/src/backend/mailbox_manager/factories.py @@ -10,13 +10,15 @@ from faker import Faker from core import factories as core_factories from core import models as core_models -from mailbox_manager import models +from mailbox_manager import enums, models fake = Faker() class MailDomainFactory(factory.django.DjangoModelFactory): - """A factory to create mail domain.""" + """A factory to create mail domain. Please not this is a factory to create mail domain with + default values. So the status is pending and no mailbox can be created from it, + until the mail domain is enabled.""" class Meta: model = models.MailDomain @@ -40,6 +42,12 @@ class MailDomainFactory(factory.django.DjangoModelFactory): ) +class MailDomainEnabledFactory(MailDomainFactory): + """A factory to create mail domain enabled.""" + + status = enums.MailDomainStatusChoices.ENABLED + + class MailDomainAccessFactory(factory.django.DjangoModelFactory): """A factory to create mail domain accesses.""" @@ -63,5 +71,5 @@ class MailboxFactory(factory.django.DjangoModelFactory): full_name = factory.Faker("name") local_part = factory.LazyAttribute(lambda a: a.full_name.lower().replace(" ", ".")) - domain = factory.SubFactory(MailDomainFactory) + domain = factory.SubFactory(MailDomainEnabledFactory) secondary_email = factory.Faker("email") diff --git a/src/backend/mailbox_manager/migrations/0006_maildomain_status.py b/src/backend/mailbox_manager/migrations/0006_maildomain_status.py new file mode 100644 index 0000000..ca5bd43 --- /dev/null +++ b/src/backend/mailbox_manager/migrations/0006_maildomain_status.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2024-07-06 23:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mailbox_manager', '0005_alter_maildomain_slug'), + ] + + operations = [ + migrations.AddField( + model_name='maildomain', + name='status', + field=models.CharField(choices=[('pending', 'Pending'), ('enabled', 'Enabled'), ('failed', 'Failed'), ('disabled', 'Disabled')], default='pending', max_length=10), + ), + ] diff --git a/src/backend/mailbox_manager/models.py b/src/backend/mailbox_manager/models.py index 26a0ba7..03bb2ab 100644 --- a/src/backend/mailbox_manager/models.py +++ b/src/backend/mailbox_manager/models.py @@ -4,12 +4,15 @@ Declare and configure the models for the People additional application : mailbox from django.conf import settings from django.core import validators +from django.core.exceptions import ValidationError from django.db import models from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ from core.models import BaseModel, RoleChoices +from mailbox_manager.enums import MailDomainStatusChoices + class MailDomain(BaseModel): """Domain names from which we will create email addresses (mailboxes).""" @@ -18,6 +21,11 @@ class MailDomain(BaseModel): _("name"), max_length=150, null=False, blank=False, unique=True ) slug = models.SlugField(null=False, blank=False, unique=True, max_length=80) + status = models.CharField( + max_length=10, + default=MailDomainStatusChoices.PENDING, + choices=MailDomainStatusChoices.choices, + ) class Meta: db_table = "people_mail_domain" @@ -120,3 +128,9 @@ class Mailbox(BaseModel): def __str__(self): return f"{self.local_part!s}@{self.domain.name:s}" + + def save(self, *args, **kwargs): + self.full_clean() + if self.domain.status != MailDomainStatusChoices.ENABLED: + raise ValidationError("You can create mailbox only for a domain enabled") + super().save(*args, **kwargs) diff --git a/src/backend/mailbox_manager/tests/test_api_mailboxes_create.py b/src/backend/mailbox_manager/tests/test_api_mailboxes_create.py index 7f39886..0c28a93 100644 --- a/src/backend/mailbox_manager/tests/test_api_mailboxes_create.py +++ b/src/backend/mailbox_manager/tests/test_api_mailboxes_create.py @@ -15,7 +15,7 @@ pytestmark = pytest.mark.django_db def test_api_mailboxes__create_anonymous_forbidden(): """Anonymous users should not be able to create a new mailbox via the API.""" - mail_domain = factories.MailDomainFactory() + mail_domain = factories.MailDomainEnabledFactory() response = APIClient().post( f"/api/v1.0/mail-domains/{mail_domain.slug}/mailboxes/", @@ -41,7 +41,7 @@ def test_api_mailboxes__create_authenticated_missing_fields(): client = APIClient() client.force_login(user) - mail_domain = factories.MailDomainFactory() + mail_domain = factories.MailDomainEnabledFactory() response = client.post( f"/api/v1.0/mail-domains/{mail_domain.slug}/mailboxes/", { @@ -78,7 +78,7 @@ def test_api_mailboxes__create_authenticated_successful(): client = APIClient() client.force_login(user) - mail_domain = factories.MailDomainFactory(name="saint-jean.collectivite.fr") + mail_domain = factories.MailDomainEnabledFactory(name="saint-jean.collectivite.fr") response = client.post( f"/api/v1.0/mail-domains/{mail_domain.slug}/mailboxes/", { diff --git a/src/backend/mailbox_manager/tests/test_api_mailboxes_list.py b/src/backend/mailbox_manager/tests/test_api_mailboxes_list.py index 43a3e2d..c8bdd5c 100644 --- a/src/backend/mailbox_manager/tests/test_api_mailboxes_list.py +++ b/src/backend/mailbox_manager/tests/test_api_mailboxes_list.py @@ -15,7 +15,7 @@ pytestmark = pytest.mark.django_db def test_api_mailboxes__list_anonymous(): """Anonymous users should not be allowed to list mailboxes.""" - mail_domain = factories.MailDomainFactory() + mail_domain = factories.MailDomainEnabledFactory() factories.MailboxFactory.create_batch(2, domain=mail_domain) response = APIClient().get(f"/api/v1.0/mail-domains/{mail_domain.slug}/mailboxes/") @@ -32,7 +32,7 @@ def test_api_mailboxes__list_authenticated_no_query(): client = APIClient() client.force_login(user) - mail_domain = factories.MailDomainFactory() + mail_domain = factories.MailDomainEnabledFactory() mailbox1 = factories.MailboxFactory(domain=mail_domain) mailbox2 = factories.MailboxFactory(domain=mail_domain) diff --git a/src/backend/mailbox_manager/tests/test_models_mailboxes.py b/src/backend/mailbox_manager/tests/test_models_mailboxes.py index 1fed666..a2656c2 100644 --- a/src/backend/mailbox_manager/tests/test_models_mailboxes.py +++ b/src/backend/mailbox_manager/tests/test_models_mailboxes.py @@ -6,7 +6,7 @@ from django.core.exceptions import ValidationError import pytest -from mailbox_manager import factories +from mailbox_manager import enums, factories pytestmark = pytest.mark.django_db @@ -89,3 +89,40 @@ def test_models_mailboxes__secondary_email_cannot_be_null(): """The "secondary_email" field should not be null.""" with pytest.raises(ValidationError, match="This field cannot be null"): factories.MailboxFactory(secondary_email=None) + + +def test_models_mailboxes__cannot_be_created_for_disabled_maildomain(): + """Mailbox creation is allowed only for a domain enabled. + A disabled status for the mail domain raises an error.""" + with pytest.raises( + ValidationError, match="You can create mailbox only for a domain enabled" + ): + factories.MailboxFactory( + domain=factories.MailDomainFactory( + status=enums.MailDomainStatusChoices.DISABLED + ) + ) + + +def test_models_mailboxes__cannot_be_created_for_failed_maildomain(): + """Mailbox creation is allowed only for a domain enabled. + A failed status for the mail domain raises an error.""" + with pytest.raises( + ValidationError, match="You can create mailbox only for a domain enabled" + ): + factories.MailboxFactory( + domain=factories.MailDomainFactory( + status=enums.MailDomainStatusChoices.FAILED + ) + ) + + +def test_models_mailboxes__cannot_be_created_for_pending_maildomain(): + """Mailbox creation is allowed only for a domain enabled. + A pending status for the mail domain raises an error.""" + with pytest.raises( + ValidationError, match="You can create mailbox only for a domain enabled" + ): + # MailDomainFactory initializes a mail domain with default values, + # so mail domain status is pending! + factories.MailboxFactory(domain=factories.MailDomainFactory())