Browse Source

complete unit tests

Olivier Massot 1 year ago
parent
commit
c9a48dc7b4

+ 14 - 0
src/ApiResources/Organization/OrganizationMemberCreationRequest.php

@@ -13,6 +13,9 @@ class OrganizationMemberCreationRequest
 {
     private GenderEnum $gender;
 
+    #[Assert\Regex(pattern: '/^[a-z0-9\-]{3,}$/')]
+    private string $username;
+
     #[Assert\Length(
         min: 1,
         minMessage: 'Name must be at least {{ limit }} characters long',
@@ -66,6 +69,17 @@ class OrganizationMemberCreationRequest
     )]
     private string $email;
 
+    public function getUsername(): string
+    {
+        return $this->username;
+    }
+
+    public function setUsername(string $username): self
+    {
+        $this->username = $username;
+        return $this;
+    }
+
     public function getGender(): GenderEnum
     {
         return $this->gender;

+ 27 - 24
src/Service/Organization/OrganizationFactory.php

@@ -85,7 +85,7 @@ class OrganizationFactory
         try {
             // On vérifie si cette organisation n'existe pas déjà
             if ($this->isExistingOrganization($organizationCreationRequest)) {
-                throw new \RuntimeException('An organization named ' . $organizationCreationRequest->getName() . ' already exists.');
+                throw new \RuntimeException('An organization named ' . $organizationCreationRequest->getName() . ' already exists');
             }
 
             // On vérifie la validité et la disponibilité du sous domaine
@@ -113,7 +113,7 @@ class OrganizationFactory
             $this->logger->info("Organization persisted in the DB");
 
         } catch (\Exception $e) {
-            $this->logger->critical("An error happened, operation cancelled : " . $e);
+            $this->logger->critical("An error happened, operation cancelled\n" . $e);
             $this->entityManager->rollback();
             throw $e;
         }
@@ -159,15 +159,15 @@ class OrganizationFactory
     protected function validateSubdomain(string $subdomainValue): void
     {
         if (!$this->subdomainService->isValidSubdomain($subdomainValue)) {
-            throw new \RuntimeException("Not a valid subdomain :" . $subdomainValue);
+            throw new \RuntimeException("Not a valid subdomain : " . $subdomainValue);
         }
 
         if ($this->subdomainService->isReservedSubdomain($subdomainValue)) {
-            throw new \RuntimeException("This subdomain is not available :" . $subdomainValue);
+            throw new \RuntimeException("This subdomain is not available : " . $subdomainValue);
         }
 
         if ($this->subdomainService->isRegistered($subdomainValue)) {
-            throw new \RuntimeException("This subdomain is already registered :" . $subdomainValue);
+            throw new \RuntimeException("This subdomain is already registered : " . $subdomainValue);
         }
     }
 
@@ -239,6 +239,7 @@ class OrganizationFactory
             $this->logger->debug(" - Director access created");
         }
 
+        // Création du sous-domaine
         $subdomain = $this->makeSubdomain($organizationCreationRequest);
         $subdomain->setOrganization($organization);
 
@@ -439,33 +440,35 @@ class OrganizationFactory
     /**
      * Creates an Access object based on the given OrganizationMemberCreationRequest.
      *
-     * @param int|OrganizationMemberCreationRequest $organizationMemberCreationRequest The request object containing the
-     *                                                                                 necessary data for creating a
-     *                                                                                 Person object, or the id of an
-     *                                                                                 existing one.
+     * @param int|OrganizationMemberCreationRequest $creationRequestData The request object containing the
+     *                                                                   necessary data for creating a Person object,
+     *                                                                   or the id of an existing one.
      * @return Access The created Access object.
      * @throws NumberParseException
      */
-    protected function makeAccess(int|OrganizationMemberCreationRequest $organizationMemberCreationRequest): Access
+    protected function makeAccess(
+        int | OrganizationMemberCreationRequest $creationRequestData
+    ): Access
     {
-        if (is_int($organizationMemberCreationRequest)) {
-            $person = $this->personRepository->find($organizationMemberCreationRequest);
+        if (is_int($creationRequestData)) {
+            $person = $this->personRepository->find($creationRequestData);
         } else {
             $person = new Person();
 
-            $person->setUsername(
-                strtolower(str_replace(' ', '-', $organizationMemberCreationRequest->getName()))
-            );
+            $person->setUsername($creationRequestData->getUsername());
             $person->setPassword(ByteString::fromRandom(32)->toString());
+            $person->setGender($creationRequestData->getGender());
+            $person->setName(
+                ucfirst(strtolower($creationRequestData->getName()))
+            );
+            $person->setGivenName(
+                ucfirst(strtolower($creationRequestData->getGivenName()))
+            );
 
-            $person->setGender($organizationMemberCreationRequest->getGender());
-            $person->setName($organizationMemberCreationRequest->getName());
-            $person->setGivenName($organizationMemberCreationRequest->getGivenName());
-
-            $personPostalAddress = $this->makeAccessPostalAddress($organizationMemberCreationRequest);
+            $personPostalAddress = $this->makePersonPostalAddress($creationRequestData);
             $person->addPersonAddressPostal($personPostalAddress);
 
-            $contactPoint = $this->makeAccessContactPoint($organizationMemberCreationRequest);
+            $contactPoint = $this->makePersonContactPoint($creationRequestData);
             $person->addContactPoint($contactPoint);
 
             $this->entityManager->persist($person);
@@ -486,7 +489,7 @@ class OrganizationFactory
      *                                                                             PersonAddressPostal object.
      * @return PersonAddressPostal The created PersonAddressPostal object.
      */
-    protected function makeAccessPostalAddress(OrganizationMemberCreationRequest $organizationMemberCreationRequest): PersonAddressPostal
+    protected function makePersonPostalAddress(OrganizationMemberCreationRequest $organizationMemberCreationRequest): PersonAddressPostal
     {
         $addressPostal = new AddressPostal();
         $addressPostal->setStreetAddress($organizationMemberCreationRequest->getStreetAddress1());
@@ -515,7 +518,7 @@ class OrganizationFactory
      * @return ContactPoint The newly created instance of the ContactPoint class.
      * @throws NumberParseException
      */
-    protected function makeAccessContactPoint(OrganizationMemberCreationRequest $organizationMemberCreationRequest): ContactPoint
+    protected function makePersonContactPoint(OrganizationMemberCreationRequest $organizationMemberCreationRequest): ContactPoint
     {
         $phoneUtil = PhoneNumberUtil::getInstance();
         $phoneNumber = $phoneUtil->parse($organizationMemberCreationRequest->getPhone());
@@ -564,7 +567,7 @@ class OrganizationFactory
 
             return $rootPageUid;
         } else {
-            $this->logger->critical("/!\ A critical error happened while creating the Typo3 website ");
+            $this->logger->critical("/!\ A critical error happened while creating the Typo3 website");
             $this->logger->debug($response->getContent());
         }
 

+ 999 - 16
tests/Unit/Service/Organization/OrganizationFactoryTest.php

@@ -6,14 +6,27 @@ namespace App\Tests\Unit\Service\Organization;
 use App\ApiResources\Organization\OrganizationCreationRequest;
 use App\ApiResources\Organization\OrganizationMemberCreationRequest;
 use App\Entity\Access\Access;
+use App\Entity\Core\AddressPostal;
 use App\Entity\Core\ContactPoint;
+use App\Entity\Core\Country;
+use App\Entity\Education\Cycle;
+use App\Entity\Network\Network;
 use App\Entity\Network\NetworkOrganization;
 use App\Entity\Organization\Organization;
 use App\Entity\Organization\OrganizationAddressPostal;
 use App\Entity\Organization\Parameters;
 use App\Entity\Organization\Settings;
 use App\Entity\Organization\Subdomain;
+use App\Entity\Person\Person;
 use App\Entity\Person\PersonAddressPostal;
+use App\Enum\Core\ContactPointTypeEnum;
+use App\Enum\Education\CycleEnum;
+use App\Enum\Organization\AddressPostalOrganizationTypeEnum;
+use App\Enum\Organization\LegalEnum;
+use App\Enum\Organization\PrincipalTypeEnum;
+use App\Enum\Organization\SettingsProductEnum;
+use App\Enum\Person\AddressPostalPersonTypeEnum;
+use App\Enum\Person\GenderEnum;
 use App\Repository\Core\CountryRepository;
 use App\Repository\Organization\OrganizationRepository;
 use App\Repository\Person\PersonRepository;
@@ -23,10 +36,13 @@ use App\Service\Organization\Utils as OrganizationUtils;
 use App\Service\Typo3\BindFileService;
 use App\Service\Typo3\SubdomainService;
 use App\Service\Typo3\Typo3Service;
+use App\Service\Utils\DatesUtils;
 use Doctrine\ORM\EntityManagerInterface;
 use PHPUnit\Framework\MockObject\MockObject;
 use PHPUnit\Framework\TestCase;
 use Psr\Log\LoggerInterface;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Contracts\HttpClient\ResponseInterface;
 
 class TestableOrganizationFactory extends OrganizationFactory {
     public function isExistingOrganization(OrganizationCreationRequest $organizationCreationRequest): bool
@@ -77,16 +93,16 @@ class TestableOrganizationFactory extends OrganizationFactory {
         return parent::makeCycles();
     }
 
-    public function makeAccess(int|OrganizationMemberCreationRequest $organizationMemberCreationRequest): Access {
-        return parent::makeAccess($organizationMemberCreationRequest);
+    public function makeAccess(int|OrganizationMemberCreationRequest $creationRequestData): Access {
+        return parent::makeAccess($creationRequestData);
     }
 
-    public function makeAccessPostalAddress(OrganizationMemberCreationRequest $organizationMemberCreationRequest): PersonAddressPostal {
-        return parent::makeAccessPostalAddress($organizationMemberCreationRequest);
+    public function makePersonPostalAddress(OrganizationMemberCreationRequest $organizationMemberCreationRequest): PersonAddressPostal {
+        return parent::makePersonPostalAddress($organizationMemberCreationRequest);
     }
 
-    public function makeAccessContactPoint(OrganizationMemberCreationRequest $organizationMemberCreationRequest): ContactPoint {
-        return parent::makeAccessContactPoint($organizationMemberCreationRequest);
+    public function makePersonContactPoint(OrganizationMemberCreationRequest $organizationMemberCreationRequest): ContactPoint {
+        return parent::makePersonContactPoint($organizationMemberCreationRequest);
     }
 
     public function makeSubdomain(OrganizationCreationRequest $organizationCreationRequest): Subdomain {
@@ -100,16 +116,16 @@ class TestableOrganizationFactory extends OrganizationFactory {
 
 class OrganizationFactoryTest extends TestCase
 {
-    private readonly SubdomainService       $subdomainService;
-    private readonly OrganizationRepository $organizationRepository;
-    private readonly CountryRepository      $countryRepository;
-    private readonly OrganizationUtils      $organizationUtils;
-    private readonly Typo3Service           $typo3Service;
-    private readonly DolibarrApiService     $dolibarrApiService;
-    private readonly EntityManagerInterface $entityManager;
-    private readonly PersonRepository       $personRepository;
-    private readonly BindFileService        $bindFileService;
-    private readonly LoggerInterface        $logger;
+    private readonly MockObject | SubdomainService       $subdomainService;
+    private readonly MockObject | OrganizationRepository $organizationRepository;
+    private readonly MockObject | CountryRepository      $countryRepository;
+    private readonly MockObject | OrganizationUtils      $organizationUtils;
+    private readonly MockObject | Typo3Service           $typo3Service;
+    private readonly MockObject | DolibarrApiService     $dolibarrApiService;
+    private readonly MockObject | EntityManagerInterface $entityManager;
+    private readonly MockObject | PersonRepository       $personRepository;
+    private readonly MockObject | BindFileService        $bindFileService;
+    private readonly MockObject | LoggerInterface        $logger;
 
     public function setUp(): void
     {
@@ -125,6 +141,11 @@ class OrganizationFactoryTest extends TestCase
         $this->logger = $this->getMockBuilder(LoggerInterface::class)->disableOriginalConstructor()->getMock();
     }
 
+    public function tearDown(): void
+    {
+        DatesUtils::clearFakeDatetime();
+    }
+
     private function getOrganizationFactoryMockFor(string $methodName): TestableOrganizationFactory | MockObject
     {
         $organizationFactory = $this
@@ -208,4 +229,966 @@ class OrganizationFactoryTest extends TestCase
             $result
         );
     }
+
+    public function testCreateWithRollback(): void {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('create');
+
+        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
+        $organizationCreationRequest->method('getName')->willReturn('foo');
+        $organizationCreationRequest->method('getSubdomain')->willReturn('subdomain');
+        $organizationCreationRequest->method('isClient')->willReturn(false);
+        $organizationCreationRequest->method('getCreateWebsite')->willReturn(true);
+
+        $this->entityManager->expects(self::once())->method('beginTransaction');
+
+        $organizationFactory->expects(self::once())->method('isExistingOrganization')->willReturn(false);
+        $organizationFactory->expects(self::once())->method('validateSubdomain')->with('subdomain');
+
+        $organization = $this->getMockBuilder(Organization::class)->getMock();
+        $organizationFactory
+            ->expects(self::once())
+            ->method('makeOrganizationWithRelations')
+            ->with($organizationCreationRequest)
+            ->willReturn($organization);
+
+        $this->entityManager->expects(self::once())->method('persist')->with($organization);
+        $this->entityManager->expects(self::once())->method('flush')->willThrowException(new \RuntimeException('some error'));
+        $this->entityManager->expects(self::once())->method('rollback');
+
+        $this->dolibarrApiService
+            ->expects(self::never())
+            ->method('createSociety');
+
+        $this->bindFileService
+            ->expects(self::never())
+            ->method('registerSubdomain');
+
+        $organizationFactory
+            ->expects(self::never())
+            ->method('createTypo3Website');
+
+        $this->logger
+            ->method('info')
+            ->withConsecutive(
+                ["Start the creation of a new organization named 'foo'"],
+                ["Subdomain is valid and available : 'subdomain'"],
+                ["Organization created with all its relations"]
+            );
+
+        $this->logger
+            ->method('critical')
+            ->with(
+                $this->matchesRegularExpression('/^An error happened, operation cancelled\nRuntimeException: some error.*/')
+            );
+
+        $this->expectException(\RuntimeException::class);
+
+        $result = $organizationFactory->create($organizationCreationRequest);
+    }
+
+    public function testCreateWithExistingOrganization(): void {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('create');
+
+        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
+        $organizationCreationRequest->method('getName')->willReturn('foo');
+
+        $this->entityManager->expects(self::once())->method('beginTransaction');
+
+        $organizationFactory->expects(self::once())->method('isExistingOrganization')->willReturn(true);
+
+        $organizationFactory->expects(self::never())->method('validateSubdomain');
+
+        $organizationFactory
+            ->expects(self::never())
+            ->method('makeOrganizationWithRelations');
+
+        $this->entityManager->expects(self::never())->method('persist');
+        $this->entityManager->expects(self::never())->method('flush');
+        $this->entityManager->expects(self::once())->method('rollback');
+
+        $this->dolibarrApiService
+            ->expects(self::never())
+            ->method('createSociety');
+
+        $this->bindFileService
+            ->expects(self::never())
+            ->method('registerSubdomain');
+
+        $organizationFactory
+            ->expects(self::never())
+            ->method('createTypo3Website');
+
+        $this->logger
+            ->method('info')
+            ->withConsecutive(
+                ["Start the creation of a new organization named 'foo'"],
+            );
+
+        $this->logger
+            ->method('critical')
+            ->with(
+                $this->matchesRegularExpression("/^An error happened, operation cancelled\nRuntimeException: An organization named foo already exists.*/")
+            );
+
+        $this->expectException(\RuntimeException::class);
+        $this->expectExceptionMessage("An organization named foo already exists");
+
+        $result = $organizationFactory->create($organizationCreationRequest);
+    }
+
+    public function testCreateNoWebsiteAndIsClient(): void {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('create');
+
+        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
+        $organizationCreationRequest->method('getName')->willReturn('foo');
+        $organizationCreationRequest->method('getSubdomain')->willReturn('subdomain');
+        $organizationCreationRequest->method('isClient')->willReturn(true);
+        $organizationCreationRequest->method('getCreateWebsite')->willReturn(false);
+
+        $organizationFactory->method('isExistingOrganization')->willReturn(false);
+
+        $organization = $this->getMockBuilder(Organization::class)->getMock();
+        $organizationFactory
+            ->expects(self::once())
+            ->method('makeOrganizationWithRelations')
+            ->with($organizationCreationRequest)
+            ->willReturn($organization);
+
+        $this->dolibarrApiService
+            ->method('createSociety')
+            ->with($organization, true)
+            ->willReturn(456);
+
+        $organizationFactory
+            ->expects(self::never())
+            ->method('createTypo3Website');
+
+        $this->logger
+            ->expects(self::once())
+            ->method('warning')
+            ->with("Typo3 website creation was not required");
+
+        $result = $organizationFactory->create($organizationCreationRequest);
+
+        $this->assertEquals(
+            $organization,
+            $result
+        );
+    }
+
+    public function testIsExistingOrganization(): void
+    {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('isExistingOrganization');
+
+        $organizationCreationRequest1 = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
+        $organizationCreationRequest1->method('getName')->willReturn('foo');
+
+        $organizationCreationRequest2 = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
+        $organizationCreationRequest2->method('getName')->willReturn('bar');
+
+        $this->organizationRepository->method('count')->willReturnMap([
+            [['name' => 'foo'], 1],
+            [['name' => 'bar'], 0],
+        ]);
+
+        $this->assertTrue($organizationFactory->isExistingOrganization($organizationCreationRequest1));
+        $this->assertFalse($organizationFactory->isExistingOrganization($organizationCreationRequest2));
+    }
+
+    public function testValidateSubdomain(): void {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('validateSubdomain');
+
+        $this->subdomainService->expects(self::once())->method('isValidSubdomain')->willReturn(true);
+        $this->subdomainService->expects(self::once())->method('isReservedSubdomain')->willReturn(false);
+        $this->subdomainService->expects(self::once())->method('isRegistered')->willReturn(false);
+
+        $organizationFactory->validateSubdomain('foo');
+    }
+
+    public function testValidateSubdomainIsNotValid(): void {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('validateSubdomain');
+
+        $this->subdomainService->method('isValidSubdomain')->willReturn(false);
+        $this->subdomainService->method('isReservedSubdomain')->willReturn(false);
+        $this->subdomainService->method('isRegistered')->willReturn(false);
+
+        $this->expectException(\RuntimeException::class);
+        $this->expectExceptionMessage("Not a valid subdomain : foo");
+
+        $organizationFactory->validateSubdomain('foo');
+    }
+
+    public function testValidateSubdomainIsReserved(): void {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('validateSubdomain');
+
+        $this->subdomainService->method('isValidSubdomain')->willReturn(true);
+        $this->subdomainService->method('isReservedSubdomain')->willReturn(true);
+        $this->subdomainService->method('isRegistered')->willReturn(false);
+
+        $this->expectException(\RuntimeException::class);
+        $this->expectExceptionMessage("This subdomain is not available : foo");
+
+        $organizationFactory->validateSubdomain('foo');
+    }
+
+    public function testValidateSubdomainIsRegistered(): void {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('validateSubdomain');
+
+        $this->subdomainService->method('isValidSubdomain')->willReturn(true);
+        $this->subdomainService->method('isReservedSubdomain')->willReturn(false);
+        $this->subdomainService->method('isRegistered')->willReturn(true);
+
+        $this->expectException(\RuntimeException::class);
+        $this->expectExceptionMessage("This subdomain is already registered : foo");
+
+        $organizationFactory->validateSubdomain('foo');
+    }
+
+    public function testMakeOrganizationWithRelations()
+    {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('makeOrganizationWithRelations');
+
+        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
+        $organization = $this->getMockBuilder(Organization::class)->getMock();
+
+        // Création de l'organisation
+        $organizationFactory
+            ->expects(self::once())
+            ->method('makeOrganization')
+            ->with($organizationCreationRequest)
+            ->willReturn($organization);
+
+        // Création des Parameters
+        $parameters = $this->getMockBuilder(Parameters::class)->getMock();
+        $organizationFactory
+            ->expects(self::once())
+            ->method('makeParameters')
+            ->with($organizationCreationRequest)
+            ->willReturn($parameters);
+
+        $organization->expects(self::once())->method('setParameters')->with($parameters);
+
+        // Création des Settings
+        $settings = $this->getMockBuilder(Settings::class)->getMock();
+        $organizationFactory
+            ->expects(self::once())
+            ->method('makeSettings')
+            ->with($organizationCreationRequest)
+            ->willReturn($settings);
+
+        $organization->expects(self::once())->method('setSettings')->with($settings);
+
+        // Création de l'adresse postale
+        $organizationAddressPostal = $this->getMockBuilder(OrganizationAddressPostal::class)->getMock();
+        $organizationFactory
+            ->expects(self::once())
+            ->method('makePostalAddress')
+            ->with($organizationCreationRequest)
+            ->willReturn($organizationAddressPostal);
+
+        $organization->expects(self::once())->method('addOrganizationAddressPostal')->with($organizationAddressPostal);
+
+        // Création du point de contact
+        $contactPoint = $this->getMockBuilder(ContactPoint::class)->getMock();
+        $organizationFactory
+            ->expects(self::once())
+            ->method('makeContactPoint')
+            ->with($organizationCreationRequest)
+            ->willReturn($contactPoint);
+
+        $organization->expects(self::once())->method('addContactPoint')->with($contactPoint);
+
+        // Rattachement au réseau
+        $networkOrganization = $this->getMockBuilder(NetworkOrganization::class)->getMock();
+        $organizationFactory
+            ->expects(self::once())
+            ->method('makeNetworkOrganization')
+            ->with($organizationCreationRequest)
+            ->willReturn($networkOrganization);
+
+        $organization->expects(self::once())->method('addNetworkOrganization')->with($networkOrganization);
+
+        // Créé l'admin
+        $adminAccess = $this->getMockBuilder(Access::class)->getMock();
+        $organizationFactory
+            ->expects(self::once())
+            ->method('makeAdminAccess')
+            ->with($organizationCreationRequest)
+            ->willReturn($adminAccess);
+
+        // Le `$organization->expects(...)->method('addAccess')` est implémenté plus loin,
+        // après la création du président et du directeur
+
+        // Création des cycles
+        $cycle1 = $this->getMockBuilder(Cycle::class)->getMock();
+        $cycle2 = $this->getMockBuilder(Cycle::class)->getMock();
+        $cycle3 = $this->getMockBuilder(Cycle::class)->getMock();
+
+        $organizationFactory
+            ->expects(self::once())
+            ->method('makeCycles')
+            ->willReturn([$cycle1, $cycle2, $cycle3]);
+
+        $organization->expects(self::exactly(3))->method('addCycle')->withConsecutive([$cycle1], [$cycle2], [$cycle3]);
+
+        // Création du président et du directeur
+        $organizationMemberCreationRequest1 = $this->getMockBuilder(OrganizationMemberCreationRequest::class)->getMock();
+        $organizationCreationRequest->method('getPresident')->willReturn($organizationMemberCreationRequest1);
+
+        $organizationMemberCreationRequest2 = $this->getMockBuilder(OrganizationMemberCreationRequest::class)->getMock();
+        $organizationCreationRequest->method('getDirector')->willReturn($organizationMemberCreationRequest2);
+
+        $access1 = $this->getMockBuilder(Access::class)->getMock();
+        $access2 = $this->getMockBuilder(Access::class)->getMock();
+        $organizationFactory
+            ->expects(self::exactly(2))
+            ->method('makeAccess')
+            ->willReturnMap([
+                    [$organizationMemberCreationRequest1, $access1],
+                    [$organizationMemberCreationRequest2, $access2]
+                ]);
+
+        $organization
+            ->expects(self::exactly(3))
+            ->method('addAccess')
+            ->withConsecutive([$adminAccess], [$access1], [$access2]);
+
+        // Création du sous-domaine
+        $subdomain = $this->getMockBuilder(Subdomain::class)->getMock();
+        $organizationFactory
+            ->expects(self::once())
+            ->method('makeSubdomain')
+            ->with($organizationCreationRequest)
+            ->willReturn($subdomain);
+
+        $subdomain->expects(self::once())->method('setOrganization')->with($organization);
+
+        // Enregistrement du sous domaine dans Parameters (retrocompatibilité v1)
+        $organization->method('getParameters')->willReturn($parameters);
+
+        $organizationCreationRequest->method('getSubdomain')->willReturn('foo');
+        $parameters->expects(self::once())->method('setSubDomain')->with('foo');
+        $parameters->expects(self::once())->method('setOtherWebsite')->with('https://foo.opentalent.fr');
+        $this->entityManager->expects(self::once())->method('persist')->with($parameters);
+
+        $result = $organizationFactory->makeOrganizationWithRelations($organizationCreationRequest);
+
+        $this->assertEquals(
+            $result,
+            $organization
+        );
+    }
+
+    public function testMakeOrganizationWithRelationsNoPresidentNoDirector() {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('makeOrganizationWithRelations');
+
+        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
+        $organization = $this->getMockBuilder(Organization::class)->getMock();
+
+        // Création de l'organisation
+        $organizationFactory
+            ->expects(self::once())
+            ->method('makeOrganization')
+            ->with($organizationCreationRequest)
+            ->willReturn($organization);
+
+        $organizationCreationRequest->method('getPresident')->willReturn(null);
+        $organizationCreationRequest->method('getDirector')->willReturn(null);
+
+        // Un seul appel, pour l'adminAccess
+        $organization
+            ->expects(self::once())
+            ->method('addAccess');
+
+        $organizationFactory->makeOrganizationWithRelations($organizationCreationRequest);
+    }
+
+    public function testMakeOrganization()
+    {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('makeOrganization');
+
+        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
+
+        $organizationCreationRequest->method('getName')->willReturn('My Organization');
+        $organizationCreationRequest->method('getLegalStatus')->willReturn(LegalEnum::ASSOCIATION_LAW_1901);
+        $organizationCreationRequest->method('getPrincipalType')->willReturn(PrincipalTypeEnum::ARTISTIC_EDUCATION_ONLY);
+
+        $this->entityManager->expects(self::once())->method('persist')->with($this->isInstanceOf(Organization::class));
+
+        $organization = $organizationFactory->makeOrganization($organizationCreationRequest);
+
+        $this->assertEquals(
+            'My Organization',
+            $organization->getName()
+        );
+
+        $this->assertEquals(
+            LegalEnum::ASSOCIATION_LAW_1901,
+            $organization->getLegalStatus()
+        );
+
+        $this->assertEquals(
+            PrincipalTypeEnum::ARTISTIC_EDUCATION_ONLY,
+            $organization->getPrincipalType()
+        );
+    }
+
+    public function testMakeParameters(): void
+    {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('makeParameters');
+
+        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
+
+        $this->entityManager->expects(self::once())->method('persist')->with($this->isInstanceOf(Parameters::class));
+
+        $parameters = $organizationFactory->makeParameters($organizationCreationRequest);
+
+        $this->assertInstanceOf(Parameters::class, $parameters);
+    }
+
+    public function testMakeSettings(): void
+    {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('makeSettings');
+
+        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
+
+        $organizationCreationRequest->method('getProduct')->willReturn(SettingsProductEnum::ARTIST_PREMIUM);
+
+        $this->entityManager->expects(self::once())->method('persist')->with($this->isInstanceOf(Settings::class));
+
+        $settings = $organizationFactory->makeSettings($organizationCreationRequest);
+
+        $this->assertEquals(
+            SettingsProductEnum::ARTIST_PREMIUM,
+            $settings->getProduct()
+        );
+    }
+
+    public function testMakePostalAddress(): void
+    {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('makePostalAddress');
+
+        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
+        $organizationCreationRequest->method('getStreetAddress1')->willReturn('address1');
+        $organizationCreationRequest->method('getStreetAddress2')->willReturn('address2');
+        $organizationCreationRequest->method('getStreetAddress3')->willReturn('address3');
+        $organizationCreationRequest->method('getPostalCode')->willReturn('00000');
+        $organizationCreationRequest->method('getCity')->willReturn('city');
+        $organizationCreationRequest->method('getCountryId')->willReturn(1);
+
+        $country = $this->getMockBuilder(Country::class)->getMock();
+
+        $this->countryRepository->expects(self::once())->method('find')->with(1)->willReturn($country);
+
+        $this
+            ->entityManager
+            ->expects(self::exactly(2))
+            ->method('persist')
+            ->withConsecutive(
+                [$this->isInstanceOf(AddressPostal::class)],
+                [$this->isInstanceOf(OrganizationAddressPostal::class)]
+            );
+
+        $organizationAddressPostal = $organizationFactory->makePostalAddress($organizationCreationRequest);
+
+        $this->assertEquals(
+            AddressPostalOrganizationTypeEnum::ADDRESS_HEAD_OFFICE,
+            $organizationAddressPostal->getType()
+        );
+
+        $addressPostal = $organizationAddressPostal->getAddressPostal();
+
+        $this->assertEquals(
+            'address1',
+            $addressPostal->getStreetAddress()
+        );
+
+        $this->assertEquals(
+            'address2',
+            $addressPostal->getStreetAddressSecond()
+        );
+
+        $this->assertEquals(
+            'address3',
+            $addressPostal->getStreetAddressThird()
+        );
+
+        $this->assertEquals(
+            '00000',
+            $addressPostal->getPostalCode()
+        );
+
+        $this->assertEquals(
+            'city',
+            $addressPostal->getAddressCity()
+        );
+
+        $this->assertEquals(
+            $country,
+            $addressPostal->getAddressCountry()
+        );
+    }
+
+    public function testMakeContactPoint(): void
+    {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('makeContactPoint');
+
+        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
+
+        $organizationCreationRequest->method('getPhoneNumber')->willReturn('+33102030405');
+        $organizationCreationRequest->method('getEmail')->willReturn('contact@domain.net');
+
+        $this->entityManager->expects(self::once())->method('persist')->with($this->isInstanceOf(ContactPoint::class));
+
+        $contactPoint = $organizationFactory->makeContactPoint($organizationCreationRequest);
+
+        $this->assertEquals(
+            'contact@domain.net',
+            $contactPoint->getEmail()
+        );
+
+        $this->assertEquals(
+            '33',
+            $contactPoint->getTelphone()->getCountryCode()
+        );
+
+        $this->assertEquals(
+            '102030405',
+            $contactPoint->getTelphone()->getNationalNumber()
+        );
+    }
+
+    public function testMakeNetworkOrganization(): void {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('makeNetworkOrganization');
+
+        DatesUtils::setFakeDatetime('2024-01-01');
+
+        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
+        $organizationCreationRequest->method('getParentId')->willReturn(123);
+
+        $this->entityManager->expects(self::once())->method('persist')->with($this->isInstanceOf(NetworkOrganization::class));
+
+        $parent = $this->getMockBuilder(Organization::class)->getMock();
+        $this->organizationRepository->expects(self::once())->method('find')->with(123)->willReturn($parent);
+
+        $network = $this->getMockBuilder(Network::class)->getMock();
+        $networkOrganization = $this->getMockBuilder(NetworkOrganization::class)->getMock();
+        $networkOrganization->method('getNetwork')->willReturn($network);
+        $this->organizationUtils
+            ->expects(self::once())
+            ->method('getActiveNetworkOrganization')
+            ->with($parent)
+            ->willReturn($networkOrganization);
+
+        $networkOrganization = $organizationFactory->makeNetworkOrganization($organizationCreationRequest);
+
+        $this->assertEquals(
+            $parent,
+            $networkOrganization->getParent()
+        );
+
+        $this->assertEquals(
+            $network,
+            $networkOrganization->getNetwork()
+        );
+
+        $this->assertEquals(
+            '2024-01-01',
+            $networkOrganization->getStartDate()->format('Y-m-d')
+        );
+    }
+
+    public function testMakeNetworkOrganizationMissingParent(): void {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('makeNetworkOrganization');
+
+        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
+        $organizationCreationRequest->method('getParentId')->willReturn(123);
+
+        $this->expectException(\RuntimeException::class);
+        $this->expectExceptionMessage('No parent organization found for id 123');
+
+        $this->organizationRepository->expects(self::once())->method('find')->with(123)->willReturn(null);
+
+        $organizationFactory->makeNetworkOrganization($organizationCreationRequest);
+    }
+
+    public function testMakeNetworkOrganizationMissingNetwork(): void {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('makeNetworkOrganization');
+
+        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
+        $organizationCreationRequest->method('getParentId')->willReturn(123);
+
+        $parent = $this->getMockBuilder(Organization::class)->getMock();
+        $this->organizationRepository->expects(self::once())->method('find')->with(123)->willReturn($parent);
+
+        $this->organizationUtils
+            ->expects(self::once())
+            ->method('getActiveNetworkOrganization')
+            ->with($parent)
+            ->willReturn(null);
+
+        $this->expectException(\RuntimeException::class);
+        $this->expectExceptionMessage('No network found for parent 123');
+
+        $this->organizationRepository->expects(self::once())->method('find')->with(123)->willReturn($parent);
+
+        $organizationFactory->makeNetworkOrganization($organizationCreationRequest);
+    }
+
+    public function testMakeAdminAccess()
+    {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('makeAdminAccess');
+
+        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
+
+        $organizationCreationRequest->method('getSubdomain')->willReturn('foo');
+
+        $this
+            ->entityManager
+            ->expects(self::exactly(2))
+            ->method('persist')
+            ->withConsecutive(
+                [$this->isInstanceOf(Person::class)],
+                [$this->isInstanceOf(Access::class)]
+            );
+
+        $adminAccess = $organizationFactory->makeAdminAccess($organizationCreationRequest);
+
+        $this->assertTrue(
+            $adminAccess->getAdminAccess()
+        );
+
+        $this->assertEquals(
+            'adminfoo',
+            $adminAccess->getPerson()->getUsername()
+        );
+
+        $this->assertEquals(
+            32,
+            strlen($adminAccess->getPerson()->getPassword())
+        );
+    }
+
+    public function testMakeCycles(): void
+    {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('makeCycles');
+
+        $cycles = $organizationFactory->makeCycles();
+
+        $cyclesExpectedData = [
+            ['Cycle initiation', 10, CycleEnum::INITIATION_CYCLE],
+            ['Cycle 1', 20, CycleEnum::CYCLE_1],
+            ['Cycle 2', 30, CycleEnum::CYCLE_2],
+            ['Cycle 3', 40, CycleEnum::CYCLE_3],
+            ['Cycle 4', 50, CycleEnum::CYCLE_4],
+            ['Hors cycle', 60, CycleEnum::OUT_CYCLE],
+        ];
+
+        $i = 0;
+        foreach ($cycles as $cycle) {
+            $this->assertEquals(
+                $cyclesExpectedData[$i][0],
+                $cycle->getLabel()
+            );
+            $this->assertEquals(
+                $cyclesExpectedData[$i][1],
+                $cycle->getOrder()
+            );
+            $this->assertEquals(
+                $cyclesExpectedData[$i][2],
+                $cycle->getCycleEnum()
+            );
+            $this->assertFalse(
+                $cycle->getIsSystem()
+            );
+            $i++;
+        }
+    }
+
+    public function testMakeAccessNewPerson(): void
+    {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('makeAccess');
+
+        $organizationMemberCreationRequest = $this->getMockBuilder(OrganizationMemberCreationRequest::class)->getMock();
+
+        $organizationMemberCreationRequest->method('getUsername')->willReturn('bob');
+        $organizationMemberCreationRequest->method('getName')->willReturn('Bob');
+        $organizationMemberCreationRequest->method('getGivenName')->willReturn('bOBBy');
+        $organizationMemberCreationRequest->method('getGender')->willReturn(GenderEnum::MISTER);
+
+        $personAddressPostal = $this->getMockBuilder(PersonAddressPostal::class)->getMock();
+        $organizationFactory
+            ->expects(self::once())
+            ->method('makePersonPostalAddress')
+            ->with($organizationMemberCreationRequest)
+            ->willReturn($personAddressPostal);
+
+        $contactPoint = $this->getMockBuilder(ContactPoint::class)->getMock();
+        $organizationFactory
+                ->expects(self::once())
+                ->method('makePersonContactPoint')
+                ->with($organizationMemberCreationRequest)
+                ->willReturn($contactPoint);
+
+        $this->entityManager
+            ->expects(self::exactly(2))
+            ->method('persist')
+            ->withConsecutive(
+                [$this->isInstanceOf(Person::class)],
+                [$this->isInstanceOf(Access::class)]
+            );
+
+        $access = $organizationFactory->makeAccess($organizationMemberCreationRequest);
+
+        $this->assertInstanceOf(Access::class, $access);
+        $this->assertEquals(
+            'bob',
+            $access->getPerson()->getUsername()
+        );
+        $this->assertTrue(
+            strlen($access->getPerson()->getPassword()) === 32
+        );
+        $this->assertEquals(
+            'Bob',
+            $access->getPerson()->getName()
+        );
+        $this->assertEquals(
+            'Bobby',
+            $access->getPerson()->getGivenName()
+        );
+        $this->assertEquals(
+            [$personAddressPostal],
+            $access->getPerson()->getPersonAddressPostal()->toArray()
+        );
+        $this->assertEquals(
+            [$contactPoint],
+            $access->getPerson()->getContactPoints()->toArray()
+        );
+    }
+
+    public function testMakeAccessExistingPerson(): void
+    {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('makeAccess');
+
+        $person = $this->getMockBuilder(Person::class)->getMock();
+
+        $this->personRepository
+            ->expects(self::once())
+            ->method('find')
+            ->with(123)
+            ->willReturn($person);
+
+        $this->entityManager
+            ->expects(self::once())
+            ->method('persist')
+            ->with($this->isInstanceOf(Access::class));
+
+        $access = $organizationFactory->makeAccess(123);
+
+        $this->assertInstanceOf(Access::class, $access);
+        $this->assertEquals(
+            $person,
+            $access->getPerson()
+        );
+    }
+
+    public function testMakeAccessPostalAddress()
+    {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('makePersonPostalAddress');
+
+        $organizationMemberCreationRequest = $this->getMockBuilder(OrganizationMemberCreationRequest::class)->getMock();
+
+        $organizationMemberCreationRequest->method('getStreetAddress1')->willReturn('Aaa');
+        $organizationMemberCreationRequest->method('getStreetAddress2')->willReturn('Bbb');
+        $organizationMemberCreationRequest->method('getStreetAddress3')->willReturn(null);
+        $organizationMemberCreationRequest->method('getPostalCode')->willReturn('00000');
+        $organizationMemberCreationRequest->method('getCity')->willReturn('city');
+        $organizationMemberCreationRequest->method('getCountryId')->willReturn(123);
+
+        $country = $this->getMockBuilder(Country::class)->getMock();
+        $this->countryRepository
+            ->expects(self::once())
+            ->method('find')
+            ->with(123)
+            ->willReturn($country);
+
+        $this->entityManager
+            ->expects(self::exactly(2))
+            ->method('persist')
+            ->withConsecutive(
+                [$this->isInstanceOf(AddressPostal::class)],
+                [$this->isInstanceOf(PersonAddressPostal::class)]
+            );
+
+        $personPostalAddress = $organizationFactory->makePersonPostalAddress($organizationMemberCreationRequest);
+
+        $this->assertEquals(
+            AddressPostalPersonTypeEnum::ADDRESS_PRINCIPAL,
+            $personPostalAddress->getType()
+        );
+
+        $postalAddress = $personPostalAddress->getAddressPostal();
+        $this->assertEquals('Aaa', $postalAddress->getStreetAddress());
+        $this->assertEquals('Bbb', $postalAddress->getStreetAddressSecond());
+        $this->assertEquals(null, $postalAddress->getStreetAddressThird());
+        $this->assertEquals('00000', $postalAddress->getPostalCode());
+        $this->assertEquals('city', $postalAddress->getAddressCity());
+        $this->assertEquals($country, $postalAddress->getAddressCountry());
+    }
+
+    public function testMakeAccessContactPoint(): void
+    {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('makePersonContactPoint');
+
+        $organizationMemberCreationRequest = $this->getMockBuilder(OrganizationMemberCreationRequest::class)->getMock();
+
+        $organizationMemberCreationRequest->method('getPhone')->willReturn('+33102030405');
+        $organizationMemberCreationRequest->method('getEmail')->willReturn('email@domain.com');
+        $organizationMemberCreationRequest->method('getMobile')->willReturn('+33607080910');
+
+        $this->entityManager
+            ->expects(self::once())
+            ->method('persist')
+            ->with($this->isInstanceOf(ContactPoint::class));
+
+        $contactPoint = $organizationFactory->makePersonContactPoint($organizationMemberCreationRequest);
+
+        $this->assertEquals(
+            ContactPointTypeEnum::PRINCIPAL,
+            $contactPoint->getContactType()
+        );
+
+        $this->assertEquals(
+            '33',
+            $contactPoint->getTelphone()->getCountryCode()
+        );
+        $this->assertEquals(
+            '102030405',
+            $contactPoint->getTelphone()->getNationalNumber()
+        );
+
+        $this->assertEquals(
+            '33',
+            $contactPoint->getMobilPhone()->getCountryCode()
+        );
+        $this->assertEquals(
+            '607080910',
+            $contactPoint->getMobilPhone()->getNationalNumber()
+        );
+
+        $this->assertEquals(
+            'email@domain.com',
+            $contactPoint->getEmail()
+        );
+    }
+
+    public function testMakePersonContactPointNoMobile(): void {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('makePersonContactPoint');
+
+        $organizationMemberCreationRequest = $this->getMockBuilder(OrganizationMemberCreationRequest::class)->getMock();
+
+        $organizationMemberCreationRequest->method('getPhone')->willReturn('+33102030405');
+        $organizationMemberCreationRequest->method('getMobile')->willReturn(null);
+
+        $contactPoint = $organizationFactory->makePersonContactPoint($organizationMemberCreationRequest);
+
+        $this->assertEquals(
+            null,
+            $contactPoint->getMobilPhone()
+        );
+    }
+
+    public function testMakeSubdomain(): void
+    {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('makeSubdomain');
+
+        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
+
+        $organizationCreationRequest->method('getSubdomain')->willReturn('subdomain');
+
+        $this->entityManager
+            ->expects(self::once())
+            ->method('persist')
+            ->with($this->isInstanceOf(Subdomain::class));
+
+        $subdomain = $organizationFactory->makeSubdomain($organizationCreationRequest);
+
+        $this->assertEquals(
+            'subdomain',
+            $subdomain->getSubdomain()
+        );
+
+        $this->assertTrue(
+            $subdomain->isActive()
+        );
+    }
+
+    public function testCreateTypo3Website(): void {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('createTypo3Website');
+
+        $organization = $this->getMockBuilder(Organization::class)->getMock();
+        $organization->method('getId')->willReturn(123);
+
+        $response = $this->getMockBuilder(ResponseInterface::class)->disableOriginalConstructor()->getMock();
+        $response->method('getStatusCode')->willReturn(Response::HTTP_OK);
+        $response->method('getContent')->willReturn('456');
+
+        $this->typo3Service->expects(self::once())->method('createSite')->with(123)->willReturn($response);
+
+        $organization->expects(self::once())->method('setCmsId')->with(456);
+        $this->entityManager->expects(self::once())->method('persist')->with($organization);
+        $this->entityManager->expects(self::once())->method('flush');
+
+        $result = $organizationFactory->createTypo3Website($organization);
+
+        $this->assertEquals(
+            456,
+            $result
+        );
+    }
+
+    public function testCreateTypo3WebsiteWithError(): void {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('createTypo3Website');
+
+        $organization = $this->getMockBuilder(Organization::class)->getMock();
+        $organization->method('getId')->willReturn(123);
+
+        $response = $this->getMockBuilder(ResponseInterface::class)->disableOriginalConstructor()->getMock();
+        $response->method('getStatusCode')->willReturn(Response::HTTP_FORBIDDEN);
+        $response->method('getContent')->willReturn('');
+
+        $this->typo3Service->expects(self::once())->method('createSite')->with(123)->willReturn($response);
+
+        $this->logger
+            ->expects(self::once())
+            ->method('critical')
+            ->with('/!\ A critical error happened while creating the Typo3 website');
+
+        $result = $organizationFactory->createTypo3Website($organization);
+
+        $this->assertNull($result);
+    }
+
+    public function testCreateTypo3WebsiteWithInvalidResponse(): void {
+        $organizationFactory = $this->getOrganizationFactoryMockFor('createTypo3Website');
+
+        $organization = $this->getMockBuilder(Organization::class)->getMock();
+        $organization->method('getId')->willReturn(123);
+
+        $response = $this->getMockBuilder(ResponseInterface::class)->disableOriginalConstructor()->getMock();
+        $response->method('getStatusCode')->willReturn(Response::HTTP_OK);
+        $response->method('getContent')->willReturn('<html lang="fr">Login page</html>');
+
+        $this->typo3Service->expects(self::once())->method('createSite')->with(123)->willReturn($response);
+
+        $this->logger
+            ->expects(self::once())
+            ->method('critical')
+            ->with('/!\ A critical error happened while creating the Typo3 website');
+
+        $result = $organizationFactory->createTypo3Website($organization);
+
+        $this->assertNull($result);
+    }
+
 }