소스 검색

fix merge conflicts

Olivier Massot 2 년 전
부모
커밋
0138f3bb02

+ 1 - 0
config/opentalent/enum.yaml

@@ -128,6 +128,7 @@ parameters:
           education_year: 'App\Enum\Education\YearEnum'
           education_period: 'App\Enum\Education\PeriodEnum'
           education_periodicity: 'App\Enum\Education\PeriodicityEnum'
+          education_cycle: 'App\Enum\Education\CycleEnum'
           advanced_education_notation: 'App\Enum\Education\AdvancedEducationNotationTypeEnum'
 
           #billing

+ 1 - 0
config/opentalent/products.yaml

@@ -15,6 +15,7 @@ parameters:
           - Enum
           - LicenceCmfOrganizationER
           - DownloadRequest
+          - SubdomainAvailability
         roles:
           - ROLE_IMPORT
           - ROLE_TAGG

+ 0 - 0
config/routes/prod/.gitkeep


+ 21 - 10
src/ApiResources/Access/AdminAccess.php

@@ -11,7 +11,9 @@ use App\ApiResources\ApiResourcesInterface;
 use App\State\Processor\Access\AdminAccessProcessor;
 use App\State\Provider\Access\AdminAccessProvider;
 use JetBrains\PhpStorm\Pure;
+use Symfony\Component\Process\Process;
 use Symfony\Component\Validator\Constraints as Assert;
+
 /**
  * Classe resource qui contient les champs d'un compte admin
  */
@@ -21,44 +23,53 @@ class AdminAccess implements ApiResourcesInterface
     #[ApiProperty(identifier: true)]
     public ?int $id = null;
 
+
+    private int $organizationId;
+
     private ?string $username = null;
 
     #[Assert\Email(message: 'invalid-email-format', mode: 'strict')]
     private ?string $email = null;
 
-    #[Pure]
-    public function __construct()
+    public function getId(): ?int
     {
+        return $this->id;
     }
 
-    public function getId() : ?int
+    public function setId(?int $id): self
     {
-        return $this->id;
+        $this->id = $id;
+        return $this;
     }
 
-    public function setId(?int $id) : self
+    public function getOrganizationId(): ?int
     {
-        $this->id = $id;
+        return $this->organizationId;
+    }
+
+    public function setOrganizationId(?int $organizationId): self
+    {
+        $this->organizationId = $organizationId;
         return $this;
     }
 
-    public function getUsername() : ?string
+    public function getUsername(): ?string
     {
         return $this->username;
     }
 
-    public function setUsername(?string $username) : self
+    public function setUsername(?string $username): self
     {
         $this->username = $username;
         return $this;
     }
 
-    public function getEmail() : ?string
+    public function getEmail(): ?string
     {
         return $this->email;
     }
 
-    public function setEmail(?string $email) : self
+    public function setEmail(?string $email): self
     {
         $this->email = $email;
         return $this;

+ 90 - 0
src/ApiResources/Organization/Subdomain/SubdomainAvailability.php

@@ -0,0 +1,90 @@
+<?php
+declare (strict_types=1);
+
+namespace App\ApiResources\Organization\Subdomain;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Get;
+use App\State\Provider\Organization\Subdomain\SubdomainAvailabilityProvider;
+
+#[ApiResource(
+    operations: [
+        new Get(
+            uriTemplate: '/subdomains/is_available',
+            security: 'is_granted("ROLE_ORGANIZATION_VIEW") or is_granted("ROLE_ORGANIZATION")',
+            provider: SubdomainAvailabilityProvider::class
+        )
+    ]
+)]
+class SubdomainAvailability
+{
+    /**
+     * Id 'bidon' ajouté par défaut pour permettre la construction
+     * de l'IRI par api platform
+     *
+     * @var int
+     */
+    #[ApiProperty(identifier: true)]
+    protected int $id = 0;
+
+    /**
+     * The subdomain
+     * @var string
+     */
+    private string $subdomain;
+
+    /**
+     * Is the subdomain available (not registered nor reserved)
+     * @var bool
+     */
+    private bool $available;
+
+    /**
+     * @return int
+     */
+    public function getId(): int
+    {
+        return $this->id;
+    }
+
+    /**
+     * @param int $id
+     */
+    public function setId(int $id): void
+    {
+        $this->id = $id;
+    }
+
+    /**
+     * @return string
+     */
+    public function getSubdomain(): string
+    {
+        return $this->subdomain;
+    }
+
+    /**
+     * @param string $subdomain
+     */
+    public function setSubdomain(string $subdomain): void
+    {
+        $this->subdomain = $subdomain;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isAvailable(): bool
+    {
+        return $this->available;
+    }
+
+    /**
+     * @param bool $available
+     */
+    public function setAvailable(bool $available): void
+    {
+        $this->available = $available;
+    }
+}

+ 3 - 0
src/Entity/Access/Access.php

@@ -8,6 +8,8 @@ use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter;
 use ApiPlatform\Metadata\ApiFilter;
 use ApiPlatform\Metadata\Get;
 use ApiPlatform\Metadata\Put;
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Serializer\Filter\GroupFilter;
 use App\Entity\AccessWish\AccessWish;
 use App\Entity\Billing\AccessBilling;
 use App\Entity\Billing\AccessFictionalIntangible;
@@ -77,6 +79,7 @@ use Symfony\Component\Serializer\Annotation\Groups;
 #[ApiFilter(filterClass: BooleanFilter::class, properties: ['person.isPhysical'])]
 #[ApiFilter(filterClass: FullNameFilter::class)]
 #[ApiFilter(filterClass: InFilter::class, properties: ['id'])]
+#[ApiFilter(GroupFilter::class, arguments: ['whitelist' => ['access_people_ref']])]
 class Access implements UserInterface, PasswordAuthenticatedUserInterface
 {
     #[ORM\Id]

+ 4 - 2
src/Entity/Billing/AccessBilling.php

@@ -4,12 +4,14 @@ declare (strict_types=1);
 namespace App\Entity\Billing;
 
 use ApiPlatform\Metadata\Get;
-use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Put;
 use App\Entity\Access\Access;
 //use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
-use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\ORM\Mapping as ORM;
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\GetCollection;
 use Doctrine\Common\Collections\Collection;
+use Doctrine\Common\Collections\ArrayCollection;
 
 /**
  * Classe ... qui ...

+ 3 - 1
src/Entity/Billing/AccessFictionalIntangible.php

@@ -3,6 +3,7 @@ declare(strict_types=1);
 
 namespace App\Entity\Billing;
 
+use ApiPlatform\Metadata\ApiResource;
 use App\Entity\Access\Access;
 use App\Entity\Access\AccessFamily;
 use App\Entity\Product\FictionalIntangible;
@@ -14,6 +15,7 @@ use Doctrine\Common\Collections\Collection;
 /**
  * Classe ... qui ...
  */
+#[ApiResource(operations: [])]
 //#[Auditable]
 #[ORM\Entity]
 class AccessFictionalIntangible
@@ -111,4 +113,4 @@ class AccessFictionalIntangible
 
         return $this;
     }
-}
+}

+ 1 - 0
src/Entity/Booking/ExamenConvocation.php

@@ -17,6 +17,7 @@ use Doctrine\ORM\Mapping as ORM;
 //#[Auditable]
 #[ApiResource(operations: [])]
 #[ORM\Entity]
+#[ApiResource(operations: [])]
 class ExamenConvocation
 {
     #[ORM\Id]

+ 14 - 8
src/Entity/Message/AbstractMessage.php

@@ -19,15 +19,10 @@ use Symfony\Bridge\Doctrine\Types\UuidType;
 #[OrganizationDefaultValue(fieldName: "organization")]
 abstract class AbstractMessage
 {
-    #[ORM\Id]
-    #[ORM\Column]
-    #[ORM\GeneratedValue]
-    protected ?int $id = null;
-
-    #[ORM\Column(length: 255, nullable: false)]
-    protected string $discr;
-
     /**
+     * IMPORTANT! Cette propriété doit être déclarée avant l'id, pour prévenir un bug doctrine
+     * @see https://github.com/doctrine/orm/issues/7215
+     *
      * @var UuidInterface|null
      */
     #[ORM\Column(type: "uuid", unique: true)]
@@ -35,6 +30,17 @@ abstract class AbstractMessage
     #[ORM\CustomIdGenerator(class: UuidGenerator::class)]
     protected ?UuidInterface $uuid = null;
 
+    /**
+     * @var int|null
+     */
+    #[ORM\Id]
+    #[ORM\Column]
+    #[ORM\GeneratedValue]
+    protected ?int $id = null;
+
+    #[ORM\Column(length: 255, nullable: false)]
+    protected string $discr;
+
     #[ORM\ManyToOne]
     #[ORM\JoinColumn(nullable: true)]
     protected Organization $organization;

+ 14 - 1
src/Entity/Organization/Parameters.php

@@ -173,7 +173,8 @@ class Parameters
     #[ORM\Column(options: ['default' => true])]
     private bool $periodValidation = true;
 
-
+    #[ORM\Column(type: "boolean", options: ["default" => false])]
+    private bool $notifyAdministrationAbsence = false;
 
     #[Pure]
     public function __construct()
@@ -687,4 +688,16 @@ class Parameters
     {
         $this->periodValidation = $periodValidation;
     }
+
+    public function getNotifyAdministrationAbsence(): bool
+    {
+        return $this->notifyAdministrationAbsence;
+    }
+
+    public function setNotifyAdministrationAbsence(bool $notifyAdministrationAbsence): self
+    {
+        $this->notifyAdministrationAbsence = $notifyAdministrationAbsence;
+        return $this;
+    }
+
 }

+ 18 - 8
src/Entity/Organization/Subdomain.php

@@ -3,7 +3,6 @@ declare (strict_types=1);
 
 namespace App\Entity\Organization;
 
-use ApiPlatform\Action\NotFoundAction;
 use ApiPlatform\Metadata\Post;
 use ApiPlatform\Metadata\GetCollection;
 use ApiPlatform\Metadata\Put;
@@ -23,14 +22,25 @@ use Symfony\Component\Validator\Constraints as Assert;
 
 /**
  * Sous-domaine enregistré par une organisation
+ *
+ * Security:
+ *   @see \App\Doctrine\Organization\CurrentOrganizationSubdomainExtension
  */
-#[ApiResource(operations: [
-    new Get(
-        controller: NotFoundAction::class,
-        read: false,
-        output: false
-    ),
-])]
+#[ApiResource(
+    operations: [
+        new Get(
+            security: '(is_granted("ROLE_ORGANIZATION_VIEW") or is_granted("ROLE_ORGANIZATION")) and object.getOrganization().getId() == user.getOrganization().getId()'
+        ),
+        new Put(
+            security: 'is_granted("ROLE_ORGANIZATION") and object.getOrganization().getId() == user.getOrganization().getId()'
+        ),
+        new GetCollection(),
+        new Post(
+            security: 'is_granted("ROLE_ORGANIZATION")'
+        )
+    ],
+    processor: SubdomainProcessor::class
+)]
 //#[Auditable]
 #[ORM\Entity(repositoryClass: SubdomainRepository::class)]
 #[OrganizationDefaultValue(fieldName: "organization")]

+ 1 - 0
src/Entity/Person/Person.php

@@ -34,6 +34,7 @@ class Person implements UserInterface, PasswordAuthenticatedUserInterface
     #[ORM\Id]
     #[ORM\Column]
     #[ORM\GeneratedValue]
+    #[Groups(["access_people_ref"])]
     private ?int $id = null;
 
     #[ORM\Column(length: 180, unique: true, nullable: true)]

+ 2 - 3
src/EventListener/DoctrineFilter/DoctrineFilterListener.php

@@ -38,8 +38,7 @@ class DoctrineFilterListener
 
         /** @var Access $access */
         $access = $this->security->getUser();
-
-        if($access){
+        if ($access) {
             /** @var DateTimeFilter $dateTimeFilter */
             $dateTimeFilter = $this->entityManager->getFilters()->getFilter('date_time_filter');
             $dateTimeFilter->setParameter('accessId', $access->getId());
@@ -53,4 +52,4 @@ class DoctrineFilterListener
             $activityYearFilter->setActivityYearConstraint($this->activityYearConstraint);
         }
     }
-}
+}

+ 2 - 2
src/Filter/DoctrineFilter/DateTimeFilter.php

@@ -44,7 +44,7 @@ final class DateTimeFilter extends AbstractTimeFilter
             DateTimeConstraint::START_KEY => $startFieldName,
             DateTimeConstraint::END_KEY => $endFieldName
         ];
-      
+
         return $this->constructQuery($constraints, $targetTableAlias, $fields);
     }
 
@@ -55,4 +55,4 @@ final class DateTimeFilter extends AbstractTimeFilter
     public function setDateTimeConstraint(DateTimeConstraint $dateTimeConstraint): void{
         $this->dateTimeConstraint = $dateTimeConstraint;
     }
-}
+}

+ 11 - 4
src/Filter/Utils/InFilter.php

@@ -11,6 +11,10 @@ use Symfony\Component\PropertyInfo\Type;
 
 /**
  * Is property included in the given CSV array
+ *
+ * Ex:
+ *
+ *    /api/accesses?id[in]=123,124,125
  */
 final class InFilter extends AbstractFilter {
     /**
@@ -39,11 +43,14 @@ final class InFilter extends AbstractFilter {
             return;
         }
 
+        if (!isset($value['in'])) {
+            return;
+        }
+
         $alias = $queryBuilder->getRootAliases()[0];
         $parameterName = $queryNameGenerator->generateParameterName($property); // Generate a unique parameter name to avoid collisions with other filters
         $queryBuilder
-            ->andWhere(sprintf('%s.%s IN (:%s)', $alias, $property, $parameterName))
-            ->setParameter($parameterName, explode(',', $value));
+            ->andWhere(sprintf('%s.%s IN (%s)', $alias, $property, $value['in']));
     }
 
     /**
@@ -59,12 +66,12 @@ final class InFilter extends AbstractFilter {
 
         $description = [];
         foreach ($this->properties as $property => $strategy) {
-            $description["$property"] = [
+            $description[$property . "[in]"] = [
                 'property' => $property,
                 'type' => Type::BUILTIN_TYPE_STRING,
                 'required' => false,
                 'swagger' => [
-                    'description' => 'Filtre permettant d\'utiliser les IN',
+                    'description' => 'Filtre permettant d\'utiliser les IN. (usage: `id[in]=1,2,3`)',
                     'name' => 'In Filter',
                     'type' => 'Utils Filter',
                 ],

+ 17 - 0
src/Repository/Organization/SubdomainRepository.php

@@ -3,6 +3,7 @@ declare(strict_types=1);
 
 namespace App\Repository\Organization;
 
+use App\Entity\Organization\Organization;
 use App\Entity\Organization\Subdomain;
 use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
 use Doctrine\Persistence\ManagerRegistry;
@@ -19,4 +20,20 @@ class SubdomainRepository extends ServiceEntityRepository
     {
         parent::__construct($registry, Subdomain::class);
     }
+
+    /**
+     * Returns the current active subdomain of an organization, or null if there are'nt any
+     *
+     * @param Organization $organization
+     * @return Subdomain
+     */
+    public function getActiveSubdomainOf(Organization $organization): ?Subdomain {
+        $qb = $this->createQueryBuilder('subdomain');
+        return $qb
+                ->where('subdomain.organization = :organization')
+                ->andWhere($qb->expr()->eq('subdomain.active', true))
+                ->setParameter('organization', $organization)
+                ->getQuery()
+                ->getOneOrNullResult();
+    }
 }

+ 2 - 1
src/Service/Access/AdminAccessUtils.php

@@ -36,11 +36,12 @@ class AdminAccessUtils
         $contactPoint = $this->contactPointUtils->getPersonContactPointPrincipal($administrator);
         if(!$contactPoint) return null;
 
-        $adminAccess = new AdminAccess();
+        $adminAccess = new AdminAccess($organization->getId());
         $adminAccess
             ->setId($administrator->getId())
             ->setUsername($administrator->getPerson()->getUserIdentifier())
             ->setEmail($contactPoint->getEmail())
+            ->setOrganizationId($organization->getId())
         ;
         return $adminAccess;
     }

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

@@ -11,6 +11,7 @@ use App\Repository\Access\AccessRepository;
 use App\Repository\Organization\SubdomainRepository;
 use App\Service\Mailer\Model\SubdomainChangeModel;
 use App\Service\Organization\Utils as OrganizationUtils;
+use Doctrine\DBAL\Connection;
 use Doctrine\ORM\EntityManagerInterface;
 use Symfony\Bundle\SecurityBundle\Security;
 use Symfony\Component\Console\Exception\InvalidArgumentException;
@@ -83,6 +84,16 @@ class SubdomainService
         return preg_match($regex, $subdomainValue) !== 0;
     }
 
+    /**
+     * Returns true if the subdomain has already been registered
+     *
+     * @param string $subdomainValue
+     * @return bool
+     */
+    public function isRegistered(string $subdomainValue): bool {
+        return count($this->subdomainRepository->findBy(['subdomain' => $subdomainValue])) !== 0;
+    }
+
     /**
      * Register a new subdomain for the organization
      * Is $activate is true, makes this new subdomain the active one too.
@@ -110,8 +121,7 @@ class SubdomainService
             throw new \RuntimeException('This subdomain is not available');
         }
 
-        // Vérifie que le sous-domaine n'est pas déjà utilisé
-        if ($this->subdomainRepository->findBy(['subdomain' => $subdomainValue])) {
+        if ($this->isRegistered($subdomainValue)) {
             throw new \RuntimeException('This subdomain is already registered');
         }
 
@@ -140,7 +150,10 @@ class SubdomainService
      * @return Subdomain
      */
     public function activateSubdomain(Subdomain $subdomain): Subdomain {
-        if ($subdomain->isActive()) {
+
+        $currentActiveSubdomain = $this->subdomainRepository->getActiveSubdomainOf($subdomain->getOrganization());
+
+        if ($currentActiveSubdomain && $subdomain->getId() === $currentActiveSubdomain->getId()) {
             throw new \RuntimeException('The subdomain is already active');
         }
         if (!$subdomain->getId()) {
@@ -160,6 +173,7 @@ class SubdomainService
         return $subdomain;
     }
 
+
     /**
      * The subdomain becomes the only active subdomain of its organization.
      * New state is persisted is database.

+ 2 - 1
src/State/Processor/Access/AdminAccessProcessor.php

@@ -53,6 +53,7 @@ class AdminAccessProcessor implements ProcessorInterface
         }
 
         $contactPoint->setEmail($data->getEmail());
+        $this->entityManager->persist($contactPoint);
         $this->entityManager->flush();
     }
-}
+}

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

@@ -12,6 +12,7 @@ use App\Entity\Organization\Subdomain;
 use App\Repository\Organization\SubdomainRepository;
 use App\Service\Typo3\SubdomainService;
 use Doctrine\ORM\EntityManagerInterface;
+use Symfony\Bundle\SecurityBundle\Security;
 
 /**
  * Custom Processor gérant la resource Subdomain
@@ -19,7 +20,8 @@ use Doctrine\ORM\EntityManagerInterface;
 class SubdomainProcessor implements ProcessorInterface
 {
     public function __construct(
-        private readonly SubdomainService $subdomainService
+        private readonly SubdomainService $subdomainService,
+        private Security $security
     ) {}
 
     /**
@@ -36,6 +38,12 @@ class SubdomainProcessor implements ProcessorInterface
             throw new \RuntimeException('not supported', 500);
         }
 
+        $access = $this->security->getUser();
+        if ($data->getOrganization()->getId() !== $access->getOrganization()->getId()) {
+            // TODO: voir à déplacer dans un voter?
+            throw new \RuntimeException('forbidden', 500);
+        }
+
         if ($operation instanceof Post) {
             // Create a new subdomain
             $subdomain = $this->subdomainService->addNewSubdomain(

+ 52 - 0
src/State/Provider/Organization/Subdomain/SubdomainAvailabilityProvider.php

@@ -0,0 +1,52 @@
+<?php
+
+namespace App\State\Provider\Organization\Subdomain;
+
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\Operation;
+use ApiPlatform\State\ProviderInterface;
+use App\ApiResources\Organization\Subdomain\SubdomainAvailability;
+use App\Service\File\Exception\FileNotFoundException;
+use App\Service\Typo3\SubdomainService;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Custom provider permettant de tester la disponibilité d'un sous-domaine
+ */
+final class SubdomainAvailabilityProvider implements ProviderInterface
+{
+    public function __construct(
+        private SubdomainService $subdomainService
+    ) {}
+
+    /**
+     * @param Operation $operation
+     * @param array<mixed> $uriVariables
+     * @param array<mixed> $context
+     * @return Response|RedirectResponse
+     * @throws FileNotFoundException
+     */
+    public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?SubdomainAvailability
+    {
+        if ($operation instanceof GetCollection) {
+            throw new \RuntimeException('not supported', 500);
+        }
+
+        $filters = $context['filters'] ?? [];
+        $subdomain = $filters['subdomain'] ?? null;
+        if ($subdomain === null) {
+            throw new \RuntimeException('missing parameter: subdomain', 500);
+        }
+
+        $available =
+            !$this->subdomainService->isRegistered($subdomain) &&
+            !$this->subdomainService->isReservedSubdomain($subdomain);
+
+        $subdomainAvailability = new SubdomainAvailability();
+        $subdomainAvailability->setSubdomain($subdomain);
+        $subdomainAvailability->setAvailable($available);
+
+        return $subdomainAvailability;
+    }
+}

+ 2 - 0
src/Validator/Organization/Parameters/MobytCredentialsValidator.php

@@ -12,6 +12,8 @@ use Symfony\Component\Validator\ConstraintValidator;
 /**
  * Classe control que les logins/mot de passe mobyt sont OK.
  */
+// NB : La validation de smsSenderName est faite 
+//      en annotation dans l'entité Parameters
 class MobytCredentialsValidator extends ConstraintValidator
 {
     public function __construct(private readonly MobytService $mobytService){}

+ 37 - 10
tests/Unit/Service/Typo3/SubdomainServiceTest.php

@@ -146,6 +146,21 @@ class SubdomainServiceTest extends TestCase
         $this->assertFalse($subdomainService->isReservedSubdomain('foo'));
     }
 
+    public function testIsRegisteredSubdomain(): void {
+        $subdomainService = $this->makeSubdomainServiceMockFor('isRegistered');
+
+        $this->subdomainRepository->method('findBy')->willReturnCallback(function ($args) {
+            if ($args === ['subdomain' => 'sub']) {
+                $subdomain = $this->getMockBuilder(Subdomain::class)->getMock();
+                return [$subdomain];
+            }
+            return [];
+        });
+
+        $this->assertTrue($subdomainService->isRegistered('sub'));
+        $this->assertFalse($subdomainService->isRegistered('foo'));
+    }
+
     public function testAddNewSubdomain(): void {
         $subdomainService = $this->makeSubdomainServiceMockFor('addNewSubdomain');
 
@@ -154,7 +169,7 @@ class SubdomainServiceTest extends TestCase
         $subdomainService->expects(self::once())->method('isValidSubdomain')->with('sub')->willReturn(True);
         $subdomainService->expects(self::once())->method('canRegisterNewSubdomain')->with($organization)->willReturn(True);
         $subdomainService->expects(self::once())->method('isReservedSubdomain')->with('sub')->willReturn(false);
-        $this->subdomainRepository->expects(self::once())->method('findBy')->with(['subdomain' => 'sub'])->willReturn(0);
+        $subdomainService->expects(self::once())->method('isRegistered')->with('sub')->willReturn(false);
 
         $this->entityManager->expects(self::once())->method('persist');
         $this->entityManager->expects(self::once())->method('flush');
@@ -177,10 +192,10 @@ class SubdomainServiceTest extends TestCase
 
         $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);
+        $subdomainService->expects(self::once())->method('isValidSubdomain')->with('_sub')->willReturn(false);
+        $subdomainService->expects(self::never())->method('canRegisterNewSubdomain')->with($organization)->willReturn(true);
         $subdomainService->expects(self::never())->method('isReservedSubdomain')->with($organization)->willReturn(false);
-        $this->subdomainRepository->expects(self::never())->method('findBy')->with(['subdomain' => '_sub'])->willReturn(0);
+        $subdomainService->expects(self::never())->method('isRegistered')->with('sub')->willReturn(false);
 
         $this->entityManager->expects(self::never())->method('persist');
         $this->entityManager->expects(self::never())->method('flush');
@@ -202,7 +217,7 @@ class SubdomainServiceTest extends TestCase
         $subdomainService->expects(self::once())->method('isValidSubdomain')->with('_sub')->willReturn(true);
         $subdomainService->expects(self::once())->method('canRegisterNewSubdomain')->with($organization)->willReturn(false);
         $subdomainService->expects(self::never())->method('isReservedSubdomain')->with($organization)->willReturn(false);
-        $this->subdomainRepository->expects(self::never())->method('findBy')->with(['subdomain' => '_sub'])->willReturn(0);
+        $subdomainService->expects(self::never())->method('isRegistered')->with('sub')->willReturn(false);
 
         $this->entityManager->expects(self::never())->method('persist');
         $this->entityManager->expects(self::never())->method('flush');
@@ -224,7 +239,7 @@ class SubdomainServiceTest extends TestCase
         $subdomainService->expects(self::once())->method('isValidSubdomain')->with('_sub')->willReturn(true);
         $subdomainService->expects(self::once())->method('canRegisterNewSubdomain')->with($organization)->willReturn(true);
         $subdomainService->expects(self::once())->method('isReservedSubdomain')->with('_sub')->willReturn(true);
-        $this->subdomainRepository->expects(self::never())->method('findBy')->with(['subdomain' => '_sub'])->willReturn(0);
+        $subdomainService->expects(self::never())->method('isRegistered')->with('sub')->willReturn(false);
 
         $this->entityManager->expects(self::never())->method('persist');
         $this->entityManager->expects(self::never())->method('flush');
@@ -246,7 +261,7 @@ class SubdomainServiceTest extends TestCase
         $subdomainService->expects(self::once())->method('isValidSubdomain')->with('sub')->willReturn(true);
         $subdomainService->expects(self::once())->method('canRegisterNewSubdomain')->with($organization)->willReturn(true);
         $subdomainService->expects(self::once())->method('isReservedSubdomain')->with('sub')->willReturn(false);
-        $this->subdomainRepository->expects(self::once())->method('findBy')->with(['subdomain' => 'sub'])->willReturn(1);
+        $subdomainService->expects(self::once())->method('isRegistered')->with('sub')->willReturn(true);
 
         $this->entityManager->expects(self::never())->method('persist');
         $this->entityManager->expects(self::never())->method('flush');
@@ -268,7 +283,7 @@ class SubdomainServiceTest extends TestCase
         $subdomainService->expects(self::once())->method('isValidSubdomain')->with('sub')->willReturn(true);
         $subdomainService->expects(self::once())->method('canRegisterNewSubdomain')->with($organization)->willReturn(true);
         $subdomainService->expects(self::once())->method('isReservedSubdomain')->with('sub')->willReturn(false);
-        $this->subdomainRepository->expects(self::once())->method('findBy')->with(['subdomain' => 'sub'])->willReturn(0);
+        $subdomainService->expects(self::once())->method('isRegistered')->with('sub')->willReturn(false);
 
         $subdomainService->expects(self::once())->method('activateSubdomain');
 
@@ -280,12 +295,19 @@ class SubdomainServiceTest extends TestCase
 
         $organization = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();
 
+        $previousActiveSubdomain = $this->getMockBuilder(Subdomain::class)->getMock();
+        $previousActiveSubdomain->method('getId')->willReturn(1);
+        $previousActiveSubdomain->method('isActive')->willReturn(false);
+
+        $this->subdomainRepository->method('getActiveSubdomainOf')->with($organization)->willReturn($previousActiveSubdomain);
+
         $initialSubdomain = $this->getMockBuilder(Subdomain::class)->getMock();
-        $initialSubdomain->method('getId')->willReturn(1);
+        $initialSubdomain->method('getId')->willReturn(2);
         $initialSubdomain->method('isActive')->willReturn(false);
+        $initialSubdomain->method('getOrganization')->willReturn($organization);
 
         $activatedSubdomain = $this->getMockBuilder(Subdomain::class)->getMock();
-        $activatedSubdomain->method('getId')->willReturn(1);
+        $activatedSubdomain->method('getId')->willReturn(2);
         $activatedSubdomain->method('isActive')->willReturn(true);
         $activatedSubdomain->method('getOrganization')->willReturn($organization);
 
@@ -319,10 +341,15 @@ class SubdomainServiceTest extends TestCase
     {
         $subdomainService = $this->makeSubdomainServiceMockFor('activateSubdomain');
 
+        $organization = $this->getMockBuilder(Organization::class)->getMock();
+
         $subdomain = $this->getMockBuilder(Subdomain::class)->getMock();
+        $subdomain->method('getOrganization')->willReturn($organization);
         $subdomain->method('getId')->willReturn(1);
         $subdomain->method('isActive')->willReturn(true);
 
+        $this->subdomainRepository->method('getActiveSubdomainOf')->with($organization)->willReturn($subdomain);
+
         $subdomainService->expects(self::never())->method('setOrganizationActiveSubdomain');
         $subdomainService->expects(self::never())->method('renameAdminUserToMatchSubdomain');
         $subdomainService->expects(self::never())->method('updateTypo3Website');