瀏覽代碼

fix and complete unit tests

Olivier Massot 2 年之前
父節點
當前提交
0b34738cab

+ 17 - 12
src/Service/Typo3/SubdomainService.php

@@ -85,7 +85,7 @@ class SubdomainService
 
         // Vérifie que le sous-domaine n'est pas déjà utilisé
         if ($this->subdomainRepository->findBy(['subdomain' => $subdomainValue])) {
-            throw new InvalidArgumentException('This subdomain is already registered');
+            throw new \RuntimeException('This subdomain is already registered');
         }
 
         $subdomain = new Subdomain();
@@ -138,7 +138,7 @@ class SubdomainService
      * New state is persisted is database.
      *
      * @param Subdomain $subdomain
-     * @return void
+     * @return Subdomain
      */
     protected function setOrganizationActiveSubdomain(Subdomain $subdomain): Subdomain {
         foreach ($subdomain->getOrganization()->getSubdomains() as $other) {
@@ -158,7 +158,7 @@ class SubdomainService
     /**
      * Rename the admin user of the organization to match the given subdomain
      *
-     * @param Organization $organization
+     * @param Subdomain $subdomain
      * @return void
      */
     protected function renameAdminUserToMatchSubdomain(Subdomain $subdomain): void {
@@ -177,12 +177,23 @@ class SubdomainService
      * @param $organization
      * @return void
      */
-    protected function updateTypo3Website($organization) {
+    protected function updateTypo3Website($organization): void
+    {
         $this->messageBus->dispatch(
             new Typo3UpdateCommand($organization->getId())
         );
     }
 
+    protected function getMailModel(Subdomain $subdomain): SubdomainChangeModel {
+        $senderId = $this->security->getUser() ? $this->security->getUser()->getId() : 211;
+
+        return (new SubdomainChangeModel())
+            ->setSenderId($senderId)
+            ->setOrganizationId($subdomain->getOrganization()->getId())
+            ->setSubdomainId($subdomain->getId())
+            ->setUrl($this->organizationUtils->getOrganizationWebsite($subdomain->getOrganization()));
+    }
+
     /**
      * Send the confirmation email to the organization after a new subdomain has been activated
      *
@@ -191,17 +202,11 @@ class SubdomainService
      */
     protected function sendConfirmationEmail(Subdomain $subdomain): void {
         // TODO: revoir quel sender par défaut
-        $senderId = $this->security->getUser() ? $this->security->getUser()->getId() : 211;
-
-        $mailModel = (new SubdomainChangeModel())
-            ->setSenderId($senderId)
-            ->setOrganizationId($subdomain->getOrganization()->getId())
-            ->setSubdomainId($subdomain->getId())
-            ->setUrl($this->organizationUtils->getOrganizationWebsite($subdomain->getOrganization()));
+        $model = $this->getMailModel($subdomain);
 
         // Envoi d'un email
         $this->messageBus->dispatch(
-            new MailerCommand($mailModel)
+            new MailerCommand($model)
         );
     }
 }

+ 1 - 2
src/State/Processor/Organization/SubdomainProcessor.php

@@ -19,8 +19,7 @@ use Doctrine\ORM\EntityManagerInterface;
 class SubdomainProcessor implements ProcessorInterface
 {
     public function __construct(
-        private readonly SubdomainService $subdomainService,
-        private EntityManagerInterface $entityManager,
+        private readonly SubdomainService $subdomainService
     ) {}
 
     /**

+ 2 - 2
templates/emails/subdomain.html.twig

@@ -14,7 +14,7 @@
     </p>
 
     <p>
-        Votre site sera dè lors accessible à l'adresse suivante :
+        Votre site sera dès lors accessible à l'adresse suivante :
         <br/>
         <a href="{{url}}">{{url}}</a>
     </p>
@@ -28,4 +28,4 @@
         </p>
     {% endif %}
 
-{% endblock %}
+{% endblock %}

+ 0 - 62
tests/Service/ApiLegacy/ApiLegacyRequestServiceTest.php

@@ -79,68 +79,6 @@ class ApiLegacyRequestServiceTest extends TestCase
         );
     }
 
-    /**
-     * @see ApiLegacyRequestService::request()
-     */
-    public function testRequestNoToken(): void
-    {
-        $api1RequestService = $this
-            ->getMockBuilder(ApiLegacyRequestService::class)
-            ->setConstructorArgs([$this->apiLegacyClient, $this->security, $this->jwtManager])
-            ->setMethodsExcept(['request'])
-            ->getMock();
-
-        $this->security->method('getToken')->willReturn(null);
-
-        $this->expectException(HttpException::class);
-        $this->expectExceptionMessage('Request error : Invalid security token');
-
-        $api1RequestService->request('GET', '/an/url');
-    }
-
-    /**
-     * @see ApiLegacyRequestService::request()
-     */
-    public function testRequestNullToken(): void
-    {
-        $api1RequestService = $this
-            ->getMockBuilder(ApiLegacyRequestService::class)
-            ->setConstructorArgs([$this->apiLegacyClient, $this->security, $this->jwtManager])
-            ->setMethodsExcept(['request'])
-            ->getMock();
-
-        $token = $this->getMockBuilder(NullToken::class)->disableOriginalConstructor()->getMock();
-
-        $this->security->method('getToken')->willReturn($token);
-
-        $this->expectException(HttpException::class);
-        $this->expectExceptionMessage('Request error : Invalid security token');
-
-        $api1RequestService->request('GET', '/an/url');
-    }
-
-    /**
-     * @see ApiLegacyRequestService::request()
-     */
-    public function testRequestInvalidToken(): void
-    {
-        $api1RequestService = $this
-            ->getMockBuilder(ApiLegacyRequestService::class)
-            ->setConstructorArgs([$this->apiLegacyClient, $this->security, $this->jwtManager])
-            ->setMethodsExcept(['request'])
-            ->getMock();
-
-        $token = $this->getMockBuilder(UsernamePasswordToken::class)->disableOriginalConstructor()->getMock();
-        $token->method('getUser')->willReturn(null);
-
-        $this->security->method('getToken')->willReturn($token);
-
-        $this->expectException(HttpException::class);
-        $this->expectExceptionMessage('Request error : Invalid security token');
-
-        $api1RequestService->request('GET', '/an/url');
-    }
-
     /**
      * @see ApiLegacyRequestService::request()
      */

+ 1 - 1
tests/Service/File/Storage/ApiLegacyStorageTest.php

@@ -27,7 +27,7 @@ class ApiLegacyStorageTest extends TestCase
         $apiLegacyRequestService
             ->expects(self::once())
             ->method('getContent')
-            ->with('api/files/123/download')
+            ->with('_internal/secure/files/123')
             ->willReturn('xyz');
 
         $result = $apiLegacyStorageTest->read($file);

+ 0 - 281
tests/Service/OnChange/Organization/OnSubdomainChangeTest.php

@@ -1,281 +0,0 @@
-<?php
-
-namespace App\Tests\Service\OnChange\Organization;
-
-use App\Entity\Access\Access;
-use App\Entity\Organization\Organization;
-use App\Entity\Organization\Subdomain;
-use App\Message\Command\MailerCommand;
-use App\Message\Command\Typo3\Typo3UpdateCommand;
-use App\Service\Mailer\Model\SubdomainChangeModel;
-use App\Service\OnChange\OnChangeContext;
-use App\Service\OnChange\Organization\OnSubdomainChange;
-use App\Service\Organization\Utils as OrganizationUtils;
-use App\Service\Typo3\BindFileService;
-use Doctrine\Common\Collections\ArrayCollection;
-use Doctrine\ORM\EntityManagerInterface;
-use PHPUnit\Framework\MockObject\MockObject;
-use PHPUnit\Framework\TestCase;
-use Symfony\Component\Messenger\Envelope;
-use Symfony\Component\Messenger\MessageBusInterface;
-use Symfony\Bundle\SecurityBundle\Security;
-
-class OnSubdomainChangeTest extends TestCase
-{
-    private OrganizationUtils $organizationUtils;
-    private Security $security;
-    private BindFileService $bindFileService;
-    private MessageBusInterface $messageBus;
-    private EntityManagerInterface $entityManager;
-
-    public function setUp():void
-    {
-        $this->organizationUtils = $this->getMockBuilder(OrganizationUtils::class)->disableOriginalConstructor()->getMock();
-        $this->security = $this->getMockBuilder(Security::class)->disableOriginalConstructor()->getMock();
-        $this->bindFileService = $this->getMockBuilder(BindFileService::class)->disableOriginalConstructor()->getMock();
-        $this->messageBus = $this->getMockBuilder(MessageBusInterface::class)->disableOriginalConstructor()->getMock();
-        $this->entityManager = $this->getMockBuilder(EntityManagerInterface::class)->disableOriginalConstructor()->getMock();
-    }
-
-    private function makeOnSubdomainChangeMock(string $methodName): MockObject | OnSubdomainChange {
-        return $this->getMockBuilder(OnSubdomainChange::class)
-            ->setConstructorArgs([$this->organizationUtils, $this->bindFileService, $this->messageBus, $this->security, $this->entityManager])
-            ->setMethodsExcept([$methodName])
-            ->getMock();
-    }
-
-    /**
-     * @see OnSubdomainChange::validate()
-     */
-    public function testValidateIsOk(): void
-    {
-        $onSubdomainChange = $this->makeOnSubdomainChangeMock('validate');
-
-        $context = $this->getMockBuilder(OnChangeContext::class)->disableOriginalConstructor()->getMock();
-        $context->method('isPostRequest')->willReturn(true);
-
-        $organization = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();
-        $organization->expects(self::once())->method('getSubdomains')->willReturn(new ArrayCollection([1,2]));
-
-        $subdomain = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
-        $subdomain->expects(self::once())->method('getOrganization')->willReturn($organization);
-
-        $onSubdomainChange->validate($subdomain, $context);
-    }
-
-    /**
-     * @see OnSubdomainChange::validate()
-     */
-    public function testValidateIsPutRequest(): void
-    {
-        $onSubdomainChange = $this->makeOnSubdomainChangeMock('validate');
-
-        $context = $this->getMockBuilder(OnChangeContext::class)->disableOriginalConstructor()->getMock();
-        $context->method('isPostRequest')->willReturn(false);
-
-        $subdomain = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
-        $subdomain->expects(self::never())->method('getOrganization');
-
-        $onSubdomainChange->validate($subdomain, $context);
-    }
-
-    /**
-     * @see OnSubdomainChange::validate()
-     */
-    public function testValidateMaxReached(): void
-    {
-        $onSubdomainChange = $this->makeOnSubdomainChangeMock('validate');
-
-        $context = $this->getMockBuilder(OnChangeContext::class)->disableOriginalConstructor()->getMock();
-        $context->method('isPostRequest')->willReturn(true);
-
-        $organization = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();
-        $organization->expects(self::once())->method('getSubdomains')->willReturn(new ArrayCollection([1,2,3]));
-
-        $subdomain = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
-        $subdomain->expects(self::once())->method('getOrganization')->willReturn($organization);
-
-        $this->expectException(\RuntimeException::class);
-        $onSubdomainChange->validate($subdomain, $context);
-    }
-
-    /**
-     * @see OnSubdomainChange::beforeChange()
-     */
-    public function testBeforeChangeActivated(): void
-    {
-        $onSubdomainChange = $this->makeOnSubdomainChangeMock('beforeChange');
-
-        // Le sous-domaine qu'on vient d'activer
-        $subdomain = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
-        $subdomain->method('isActive')->willReturn(true);
-
-        // Son état précédent
-        $previousData = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
-        $previousData->method('isActive')->willReturn(false);
-
-        // Le sous domaine qui était actif jusqu'ici, et que le OnChange devrait désactiver
-        $otherSubdomain = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
-        $otherSubdomain->method('isActive')->willReturn(true);
-        $otherSubdomain->expects(self::once())->method('setActive')->with(false);
-
-        $organization = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();
-        $organization->expects(self::once())->method('getSubdomains')->willReturn(new ArrayCollection([$subdomain, $otherSubdomain]));
-
-        $subdomain->method('getOrganization')->willReturn($organization);
-
-        $context = $this->getMockBuilder(OnChangeContext::class)->disableOriginalConstructor()->getMock();
-        $context->method('previousData')->willReturn($previousData);
-        $context->method('isPutRequest')->willReturn(true);
-        $context->method('isPostRequest')->willReturn(false);
-
-        $onSubdomainChange->beforeChange($subdomain, $context);
-    }
-
-    /**
-     * @see OnSubdomainChange::onChange()
-     */
-    public function testOnChangeNoChange(): void
-    {
-        $onSubdomainChange = $this->makeOnSubdomainChangeMock('onChange');
-
-        $this->bindFileService->expects(self::never())->method('registerSubdomain');
-        $this->messageBus->expects(self::never())->method('dispatch');
-        $onSubdomainChange->expects(self::never())->method('sendEmailAfterSubdomainChange');
-        $this->entityManager->expects(self::never())->method('refresh');
-
-        $subdomain = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
-        $context = $this->getMockBuilder(OnChangeContext::class)->disableOriginalConstructor()->getMock();
-
-        $onSubdomainChange->onChange($subdomain, $context);
-    }
-
-    /**
-     * @see OnSubdomainChange::onChange()
-     */
-    public function testOnChangeActivated(): void {
-        $onSubdomainChange = $this->makeOnSubdomainChangeMock('onChange');
-
-        $this->bindFileService->expects(self::never())->method('registerSubdomain');
-        $this->messageBus
-            ->expects(self::once())
-            ->method('dispatch')
-            ->with(self::isInstanceOf(Typo3UpdateCommand::class))
-            ->willReturn(new Envelope(new Typo3UpdateCommand(1)));
-
-        $onSubdomainChange->expects(self::once())->method('sendEmailAfterSubdomainChange');
-
-        // Le sous-domaine qu'on vient d'activer
-        $subdomain = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
-        $subdomain->method('isActive')->willReturn(true);
-
-        // Son état précédent
-        $previousData = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
-        $previousData->method('isActive')->willReturn(false);
-
-        $organization = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();
-        $organization->method('getId')->willReturn(1);
-
-        $subdomain->method('getOrganization')->willReturn($organization);
-
-        $context = $this->getMockBuilder(OnChangeContext::class)->disableOriginalConstructor()->getMock();
-        $context->method('previousData')->willReturn($previousData);
-        $context->method('isPutRequest')->willReturn(true);
-        $context->method('isPostRequest')->willReturn(false);
-
-        $this->entityManager->expects(self::once())->method('refresh')->with($organization);
-
-        $onSubdomainChange->onChange($subdomain, $context);
-    }
-
-    /**
-     * @see OnSubdomainChange::onChange()
-     */
-    public function testOnChangeCreated(): void {
-        $onSubdomainChange = $this->makeOnSubdomainChangeMock('onChange');
-
-        $this->bindFileService->expects(self::once())->method('registerSubdomain');
-        $this->messageBus
-            ->expects(self::once())
-            ->method('dispatch')
-            ->with(self::isInstanceOf(Typo3UpdateCommand::class))
-            ->willReturn(new Envelope(new Typo3UpdateCommand(1)));
-
-        $onSubdomainChange->expects(self::once())->method('sendEmailAfterSubdomainChange');
-
-        // Le sous-domaine qu'on vient d'activer
-        $subdomain = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
-        $subdomain->method('isActive')->willReturn(true);
-
-        $organization = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();
-        $organization->method('getId')->willReturn(1);
-
-        $subdomain->method('getOrganization')->willReturn($organization);
-
-        $context = $this->getMockBuilder(OnChangeContext::class)->disableOriginalConstructor()->getMock();
-        $context->method('previousData')->willReturn(null);
-        $context->method('isPutRequest')->willReturn(false);
-        $context->method('isPostRequest')->willReturn(true);
-
-        $onSubdomainChange->onChange($subdomain, $context);
-    }
-
-    /**
-     * @see OnSubdomainChange::sendEmailAfterSubdomainChange()
-     */
-    public function testSendEmailAfterSubdomainChange(): void {
-        $onSubdomainChange = $this->makeOnSubdomainChangeMock('sendEmailAfterSubdomainChange');
-
-        $subdomain = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
-        $subdomainChangeModel = $this->getMockBuilder(SubdomainChangeModel::class)->disableOriginalConstructor()->getMock();
-
-        $onSubdomainChange
-            ->expects(self::once())
-            ->method('getMailModel')
-            ->with($subdomain)
-            ->willReturn($subdomainChangeModel);
-
-        $this->messageBus
-            ->expects(self::once())
-            ->method('dispatch')
-            ->with(self::isInstanceOf(MailerCommand::class))
-            ->willReturn(new Envelope(new MailerCommand($subdomainChangeModel)));
-
-        $onSubdomainChange->sendEmailAfterSubdomainChange($subdomain);
-    }
-
-    /**
-     * @see OnSubdomainChange::getMailModel()
-     */
-    public function testGetMailModel(): void {
-        $onSubdomainChange = $this->makeOnSubdomainChangeMock('getMailModel');
-
-        $organization = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();
-        $organization->expects(self::once())->method('getId')->willReturn(1);
-
-        $this->organizationUtils
-            ->expects(self::once())
-            ->method('getOrganizationWebsite')
-            ->with($organization)
-            ->willReturn('mysubdomain.opentalent.fr');
-
-        $access = $this->getMockBuilder(Access::class)->disableOriginalConstructor()->getMock();
-        $access->expects(self::once())->method('getId')->willReturn(1);
-        $this->security
-            ->expects(self::once())
-            ->method('getUser')
-            ->willReturn($access);
-
-        $subdomain = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
-        $subdomain->expects(self::exactly(2))->method('getOrganization')->willReturn($organization);
-        $subdomain->expects(self::once())->method('getId')->willReturn(1);
-
-        $mailerModel = $onSubdomainChange->getMailModel($subdomain);
-
-        $this->assertInstanceOf(SubdomainChangeModel::class, $mailerModel);
-        $this->assertEquals($mailerModel->getSenderId(), 1);
-        $this->assertEquals($mailerModel->getOrganizationId(), 1);
-        $this->assertEquals($mailerModel->getSubdomainId(), 1);
-        $this->assertEquals($mailerModel->getUrl(), 'mysubdomain.opentalent.fr');
-    }
-}

+ 319 - 3
tests/Service/Typo3/SubdomainServiceTest.php

@@ -3,10 +3,15 @@
 namespace App\Tests\Service\Typo3;
 
 
+use App\Entity\Access\Access;
 use App\Entity\Organization\Organization;
 use App\Entity\Organization\Subdomain;
+use App\Entity\Person\Person;
+use App\Message\Command\MailerCommand;
+use App\Message\Command\Typo3\Typo3UpdateCommand;
 use App\Repository\Access\AccessRepository;
 use App\Repository\Organization\SubdomainRepository;
+use App\Service\Mailer\Model\SubdomainChangeModel;
 use App\Service\Organization\Utils as OrganizationUtils;
 use App\Service\Typo3\BindFileService;
 use App\Service\Typo3\SubdomainService;
@@ -15,6 +20,7 @@ use Doctrine\ORM\EntityManagerInterface;
 use PHPUnit\Framework\MockObject\MockObject;
 use PHPUnit\Framework\TestCase;
 use Symfony\Bundle\SecurityBundle\Security;
+use Symfony\Component\Messenger\Envelope;
 use Symfony\Component\Messenger\MessageBusInterface;
 
 class TestableSubdomainService extends SubdomainService {
@@ -26,10 +32,15 @@ class TestableSubdomainService extends SubdomainService {
         parent::renameAdminUserToMatchSubdomain($subdomain);
     }
 
-    public function updateTypo3Website($organization) {
+    public function updateTypo3Website($organization): void {
         parent::updateTypo3Website($organization);
     }
 
+    public function getMailModel(Subdomain $subdomain): SubdomainChangeModel
+    {
+        return parent::getMailModel($subdomain);
+    }
+
     public function sendConfirmationEmail(Subdomain $subdomain): void {
         parent::sendConfirmationEmail($subdomain);
     }
@@ -55,8 +66,8 @@ class SubdomainServiceTest extends TestCase
         $this->accessRepository = $this->getMockBuilder(AccessRepository::class)->disableOriginalConstructor()->getMock();
     }
 
-    private function makeOnSubdomainChangeMock(string $methodName): MockObject | SubdomainService {
-        return $this->getMockBuilder(SubdomainService::class)
+    private function makeOnSubdomainChangeMock(string $methodName): MockObject | TestableSubdomainService {
+        return $this->getMockBuilder(TestableSubdomainService::class)
             ->setConstructorArgs([
                 $this->subdomainRepository,
                 $this->entityManager,
@@ -111,6 +122,311 @@ class SubdomainServiceTest extends TestCase
         $this->assertFalse($subdomainService->isValidSubdomain('_abc'));
         $this->assertFalse($subdomainService->isValidSubdomain('abc-'));
         $this->assertFalse($subdomainService->isValidSubdomain(str_repeat('abcdef', 20)));
+    }
+
+    public function testAddNewSubdomain(): void {
+        $subdomainService = $this->makeOnSubdomainChangeMock('addNewSubdomain');
+
+        $organization = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();
+
+        $subdomainService->expects(self::once())->method('isValidSubdomain')->with('sub')->willReturn(True);
+        $subdomainService->expects(self::once())->method('canRegisterNewSubdomain')->with($organization)->willReturn(True);
+        $this->subdomainRepository->expects(self::once())->method('findBy')->with(['subdomain' => 'sub'])->willReturn(0);
+
+        $this->entityManager->expects(self::once())->method('persist');
+        $this->entityManager->expects(self::once())->method('flush');
+
+        $this->bindFileService->expects(self::once())->method('registerSubdomain')->with('sub');
+
+        // Subdomain is not activated by default
+        $subdomainService->expects(self::never())->method('activateSubdomain');
+
+        $subdomain = $subdomainService->addNewSubdomain($organization, 'sub');
+
+        $this->assertEquals($subdomain->getOrganization(), $organization);
+        $this->assertEquals($subdomain->getSubdomain(), 'sub');
+        $this->assertFalse($subdomain->isActive());
+    }
+
+    public function testAddNewSubdomainInvalid(): void
+    {
+        $subdomainService = $this->makeOnSubdomainChangeMock('addNewSubdomain');
+
+        $organization = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();
+
+        $subdomainService->expects(self::once())->method('isValidSubdomain')->with('_sub')->willReturn(False);
+        $subdomainService->expects(self::never())->method('canRegisterNewSubdomain')->with($organization)->willReturn(True);
+        $this->subdomainRepository->expects(self::never())->method('findBy')->with(['subdomain' => '_sub'])->willReturn(0);
+
+        $this->entityManager->expects(self::never())->method('persist');
+        $this->entityManager->expects(self::never())->method('flush');
+        $this->bindFileService->expects(self::never())->method('registerSubdomain')->with('_sub');
+        $subdomainService->expects(self::never())->method('activateSubdomain');
+
+        $this->expectException(\RuntimeException::class);
+        $this->expectExceptionMessage('Not a valid subdomain');
+
+        $subdomainService->addNewSubdomain($organization, '_sub');
+    }
+
+    public function testAddNewSubdomainMaxReached(): void
+    {
+        $subdomainService = $this->makeOnSubdomainChangeMock('addNewSubdomain');
+
+        $organization = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();
+
+        $subdomainService->expects(self::once())->method('isValidSubdomain')->with('_sub')->willReturn(true);
+        $subdomainService->expects(self::once())->method('canRegisterNewSubdomain')->with($organization)->willReturn(false);
+        $this->subdomainRepository->expects(self::never())->method('findBy')->with(['subdomain' => '_sub'])->willReturn(0);
+
+        $this->entityManager->expects(self::never())->method('persist');
+        $this->entityManager->expects(self::never())->method('flush');
+        $this->bindFileService->expects(self::never())->method('registerSubdomain')->with('_sub');
+        $subdomainService->expects(self::never())->method('activateSubdomain');
+
+        $this->expectException(\RuntimeException::class);
+        $this->expectExceptionMessage('This organization can not register new subdomains');
+
+        $subdomainService->addNewSubdomain($organization, '_sub');
+    }
+
+    public function testAddNewSubdomainExisting(): void
+    {
+        $subdomainService = $this->makeOnSubdomainChangeMock('addNewSubdomain');
+
+        $organization = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();
+
+        $subdomainService->expects(self::once())->method('isValidSubdomain')->with('sub')->willReturn(true);
+        $subdomainService->expects(self::once())->method('canRegisterNewSubdomain')->with($organization)->willReturn(true);
+        $this->subdomainRepository->expects(self::once())->method('findBy')->with(['subdomain' => 'sub'])->willReturn(1);
+
+        $this->entityManager->expects(self::never())->method('persist');
+        $this->entityManager->expects(self::never())->method('flush');
+        $this->bindFileService->expects(self::never())->method('registerSubdomain')->with('sub');
+        $subdomainService->expects(self::never())->method('activateSubdomain');
+
+        $this->expectException(\RuntimeException::class);
+        $this->expectExceptionMessage('This subdomain is already registered');
+
+        $subdomainService->addNewSubdomain($organization, 'sub');
+    }
+
+    public function testAddNewSubdomainAndActivate(): void
+    {
+        $subdomainService = $this->makeOnSubdomainChangeMock('addNewSubdomain');
+
+        $organization = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();
+
+        $subdomainService->expects(self::once())->method('isValidSubdomain')->with('sub')->willReturn(true);
+        $subdomainService->expects(self::once())->method('canRegisterNewSubdomain')->with($organization)->willReturn(true);
+        $this->subdomainRepository->expects(self::once())->method('findBy')->with(['subdomain' => 'sub'])->willReturn(0);
+
+        $subdomainService->expects(self::once())->method('activateSubdomain');
+
+        $subdomainService->addNewSubdomain($organization, 'sub', true);
+    }
+
+    public function testActivateSubdomain(): void {
+        $subdomainService = $this->makeOnSubdomainChangeMock('activateSubdomain');
+
+        $organization = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();
+
+        $initialSubdomain = $this->getMockBuilder(Subdomain::class)->getMock();
+        $initialSubdomain->method('getId')->willReturn(1);
+        $initialSubdomain->method('isActive')->willReturn(false);
+
+        $activatedSubdomain = $this->getMockBuilder(Subdomain::class)->getMock();
+        $activatedSubdomain->method('getId')->willReturn(1);
+        $activatedSubdomain->method('isActive')->willReturn(true);
+        $activatedSubdomain->method('getOrganization')->willReturn($organization);
+
+        $subdomainService
+            ->expects(self::once())
+            ->method('setOrganizationActiveSubdomain')
+            ->with($initialSubdomain)
+            ->willReturn($activatedSubdomain);
+
+        $subdomainService
+            ->expects(self::once())
+            ->method('renameAdminUserToMatchSubdomain')
+            ->with($activatedSubdomain);
+
+        $subdomainService
+            ->expects(self::once())
+            ->method('updateTypo3Website')
+            ->with($organization);
+
+        $subdomainService
+            ->expects(self::once())
+            ->method('sendConfirmationEmail')
+            ->with($activatedSubdomain);
+
+        $result = $subdomainService->activateSubdomain($initialSubdomain);
+
+        $this->assertEquals($result, $activatedSubdomain);
+    }
+
+    public function testActivateSubdomainAlreadyActive(): void
+    {
+        $subdomainService = $this->makeOnSubdomainChangeMock('activateSubdomain');
+
+        $subdomain = $this->getMockBuilder(Subdomain::class)->getMock();
+        $subdomain->method('getId')->willReturn(1);
+        $subdomain->method('isActive')->willReturn(true);
+
+        $subdomainService->expects(self::never())->method('setOrganizationActiveSubdomain');
+        $subdomainService->expects(self::never())->method('renameAdminUserToMatchSubdomain');
+        $subdomainService->expects(self::never())->method('updateTypo3Website');
+        $subdomainService->expects(self::never())->method('sendConfirmationEmail');
+
+
+        $this->expectException(\RuntimeException::class);
+        $this->expectExceptionMessage('The subdomain is already active');
+
+        $subdomainService->activateSubdomain($subdomain);
+    }
+
+    public function testActivateSubdomainNotPersisted(): void
+    {
+        $subdomainService = $this->makeOnSubdomainChangeMock('activateSubdomain');
+
+        $subdomain = $this->getMockBuilder(Subdomain::class)->getMock();
+        $subdomain->method('getId')->willReturn(null);
+        $subdomain->method('isActive')->willReturn(false);
+
+        $subdomainService->expects(self::never())->method('setOrganizationActiveSubdomain');
+        $subdomainService->expects(self::never())->method('renameAdminUserToMatchSubdomain');
+        $subdomainService->expects(self::never())->method('updateTypo3Website');
+        $subdomainService->expects(self::never())->method('sendConfirmationEmail');
+
+        $this->expectException(\RuntimeException::class);
+        $this->expectExceptionMessage('Can not activate a non-persisted subdomain');
+
+        $subdomainService->activateSubdomain($subdomain);
+    }
+
+    public function testSetOrganizationActiveSubdomain(): void {
+        $subdomainService = $this->makeOnSubdomainChangeMock('setOrganizationActiveSubdomain');
+
+        $organization = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();
+
+        $subdomain = $this->getMockBuilder(Subdomain::class)->getMock();
+        $subdomain->method('getId')->willReturn(1);
+        $subdomain->method('isActive')->willReturn(false);
+
+        $otherSubdomain1 = $this->getMockBuilder(Subdomain::class)->getMock();
+        $otherSubdomain1->method('getId')->willReturn(2);
+        $otherSubdomain1->method('isActive')->willReturn(true);
+
+        $otherSubdomain2 = $this->getMockBuilder(Subdomain::class)->getMock();
+        $otherSubdomain2->method('getId')->willReturn(3);
+        $otherSubdomain2->method('isActive')->willReturn(false);
+
+        $organization->method('getSubdomains')->willReturn(new ArrayCollection([$otherSubdomain1, $otherSubdomain2]));
+        $subdomain->method('getOrganization')->willReturn($organization);
+
+        // The active subdomain is deactivated
+        $otherSubdomain1->expects(self::once())->method('setActive')->with(false);
+
+        // The inactive subdomain is not modified
+        $otherSubdomain2->expects(self::never())->method('setActive');
+
+        // The new subdomain is activated
+        $subdomain->expects(self::once())->method('setActive')->with(true);
+
+        $this->entityManager->expects(self::once())->method('flush');
+        $this->entityManager->expects(self::once())->method('refresh')->with($organization);
+
+        $result = $subdomainService->setOrganizationActiveSubdomain($subdomain);
+
+        $this->assertEquals($result, $subdomain);
+    }
+
+    public function testRenameAdminUserToMatchSubdomain(): void {
+        $subdomainService = $this->makeOnSubdomainChangeMock('renameAdminUserToMatchSubdomain');
+
+        $organization = $this->getMockBuilder(Organization::class)->getMock();
+        $person = $this->getMockBuilder(Person::class)->getMock();
+        $access = $this->getMockBuilder(Access::class)->getMock();
+        $subdomain = $this->getMockBuilder(Subdomain::class)->getMock();
+
+        $this->accessRepository
+            ->method('findOneBy')
+            ->with(['adminAccess' => 1, 'organization' => $organization])
+            ->willReturn($access);
+
+        $subdomain->method('getSubdomain')->willReturn('sub');
+        $subdomain->method('getOrganization')->willReturn($organization);
+
+        $access->method('getPerson')->willReturn($person);
+
+        $person->expects(self::once())->method('setUsername')->with('adminsub');
+
+        $this->entityManager->expects(self::once())->method('flush');
+
+        $subdomainService->renameAdminUserToMatchSubdomain($subdomain);
+    }
+
+    public function testUpdateTypo3Website(): void {
+        $subdomainService = $this->makeOnSubdomainChangeMock('updateTypo3Website');
+
+        $organization = $this->getMockBuilder(Organization::class)->getMock();
+        $organization->method('getId')->willReturn(1);
+
+        $this->messageBus
+            ->expects(self::once())
+            ->method('dispatch')
+            ->with(self::isInstanceOf(Typo3UpdateCommand::class))
+            ->willReturn(new Envelope(new Typo3UpdateCommand(1)));
+
+        $subdomainService->updateTypo3Website($organization);
+    }
+
+    public function testGetMailModel(): void {
+        $subdomainService = $this->makeOnSubdomainChangeMock('getMailModel');
+
+        $access = $this->getMockBuilder(Access::class)->getMock();
+        $access->method('getId')->willReturn(1);
+        $this->security
+            ->method('getUser')
+            ->willReturn($access);
+
+        $organization = $this->getMockBuilder(Organization::class)->getMock();
+        $organization->method('getId')->willReturn(1);
+
+        $subdomain = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
+        $subdomain->method('getOrganization')->willReturn($organization);
+        $subdomain->method('getId')->willReturn(1);
+
+        $this->organizationUtils
+            ->expects(self::once())
+            ->method('getOrganizationWebsite')
+            ->with($organization)
+            ->willReturn('mysubdomain.opentalent.fr');
+
+        $mailerModel = $subdomainService->getMailModel($subdomain);
+
+        $this->assertInstanceOf(SubdomainChangeModel::class, $mailerModel);
+        $this->assertEquals(1, $mailerModel->getSenderId());
+        $this->assertEquals(1, $mailerModel->getOrganizationId());
+        $this->assertEquals(1, $mailerModel->getSubdomainId());
+        $this->assertEquals('mysubdomain.opentalent.fr', $mailerModel->getUrl());
+    }
+
+    public function testSendConfirmationEmail(): void {
+        $subdomainService = $this->makeOnSubdomainChangeMock('sendConfirmationEmail');
+
+        $subdomain = $this->getMockBuilder(Subdomain::class)->getMock();
+        $subdomainChangeModel = $this->getMockBuilder(SubdomainChangeModel::class)->getMock();
+
+        $subdomainService->method('getMailModel')->willReturn($subdomainChangeModel);
+
+        $this->messageBus
+            ->expects(self::once())
+            ->method('dispatch')
+            ->with(self::isInstanceOf(MailerCommand::class))
+            ->willReturn(new Envelope(new MailerCommand($subdomainChangeModel)));
 
+        $subdomainService->sendConfirmationEmail($subdomain);
     }
 }