소스 검색

add service tests and minor fixes

Olivier Massot 3 년 전
부모
커밋
86e4df53c3

+ 8 - 2
src/Service/Access/OptionalsRoles/CriteriaNotationOptionalRole.php

@@ -12,9 +12,15 @@ class CriteriaNotationOptionalRole implements OptionalsRolesInterface {
     public function __construct(
         private AccessRepository $accessRepository
     )
-    {
-    }
+    {}
 
+    /**
+     * Return true if the given access is an active teacher and administration has not restricted criteria edition to
+     * the admin users.
+     *
+     * @param Access $access
+     * @return bool
+     */
     public function support(Access $access): bool
     {
         $isActiveTeacher = $this->accessRepository->hasGotFunctionAtThisDate($access, FunctionEnum::TEACHER(), new \DateTime('now'));

+ 11 - 12
src/Service/Constraint/AbstractTimeConstraintUtils.php

@@ -8,14 +8,14 @@ namespace App\Service\Constraint;
  */
 abstract class AbstractTimeConstraintUtils
 {
-    const NULL = 0;
-    const INF = 1;
-    const EQUAL = 3;
-    const SUP = 5;
-    const CANCEL_OPERATION = 9;
-    const START_KEY = 'start';
-    const END_KEY = 'end';
-    const NULL_VALUE = 'NULL';
+    public const NULL = 0;
+    public const INF = 1;
+    public const EQUAL = 3;
+    public const SUP = 5;
+    public const CANCEL_OPERATION = 9;
+    public const START_KEY = 'start';
+    public const END_KEY = 'end';
+    public const NULL_VALUE = 'NULL';
 
     /**
      * Retourne true si l'utilisateur veux une période précise
@@ -34,10 +34,9 @@ abstract class AbstractTimeConstraintUtils
      * @return array
      * @see DateTimeConstraintTest::testAddConstraint()
      */
-    protected function addConstraint(array $contraints, array $newContraint): array{
+    protected function addConstraint(array $contraints, array $newContraint): array {
         $contraints = $this->mergeConstraint($contraints,$newContraint,self::START_KEY);
-        $contraints = $this->mergeConstraint($contraints,$newContraint,self::END_KEY);
-        return $contraints;
+        return $this->mergeConstraint($contraints,$newContraint,self::END_KEY);
     }
 
     /**
@@ -106,4 +105,4 @@ abstract class AbstractTimeConstraintUtils
             $constraints[$key] = [];
         return $constraints[$key];
     }
-}
+}

+ 15 - 12
src/Service/Constraint/ActivityYearConstraint.php

@@ -9,7 +9,7 @@ use Doctrine\ORM\EntityManagerInterface;
 
 /**
  * Classe ActivityYearConstraint qui définie l'année de début (et de fin dans le cas d'une période custom)
- * par rapport au contraintes temporelles choisies par un utilisateur.
+ * par rapport aux contraintes temporelles choisies par un utilisateur.
  */
 class ActivityYearConstraint extends AbstractTimeConstraintUtils
 {
@@ -20,7 +20,8 @@ class ActivityYearConstraint extends AbstractTimeConstraintUtils
     { }
 
     /**
-     * Main méthode
+     * Main method
+     *
      * @param int $accessID
      * @return array
      * @throws \Exception
@@ -42,19 +43,19 @@ class ActivityYearConstraint extends AbstractTimeConstraintUtils
             $year = $access->getActivityYear();
             if($historical['present']) $contraints = $this->addConstraint($contraints, $this->presentConstraint($year));
             if($historical['past']) $contraints = $this->addConstraint($contraints, $this->pastConstraint($year));
-            if($historical['future']) $contraints = $this->addConstraint($contraints, $this->futurConstraint($year));
+            if($historical['future']) $contraints = $this->addConstraint($contraints, $this->futureConstraint($year));
         }
         return $this->cleanConstraints($contraints);
     }
 
     /**
-     * Retourne le tableau des années comprise dans la période custom
+     * Retourne le tableau des années comprises dans la période custom
      * @param Access $access
      * @param string $dateStart
      * @param string $dateEnd
      * @return string[]
      */
-    private function getRangeYear(Access $access, string $dateStart, string $dateEnd): array{
+    protected function getRangeYear(Access $access, string $dateStart, string $dateEnd): array {
         $organization = $access->getOrganization();
         return [
             OrganizationUtils::START_DATE_KEY => $this->organizationUtils->getActivityYearSwitchDate($organization, new \DateTime($dateStart)),
@@ -65,10 +66,10 @@ class ActivityYearConstraint extends AbstractTimeConstraintUtils
     /**
      * Une période est dans le présent si :
      * - l'année de début est égale (=) à l'année à afficher
-     * @param $year
+     * @param int $year
      * @return array
      */
-    private function presentConstraint(int $year): array{
+    protected function presentConstraint(int $year): array {
         return [
           self::START_KEY => [
               $year => self::EQUAL
@@ -82,7 +83,7 @@ class ActivityYearConstraint extends AbstractTimeConstraintUtils
      * @param $year
      * @return array
      */
-    private function pastConstraint($year): array{
+    protected function pastConstraint($year): array {
         return [
             self::END_KEY => [
                 $year => self::INF
@@ -91,12 +92,12 @@ class ActivityYearConstraint extends AbstractTimeConstraintUtils
     }
 
     /**
-     * Une période est dans le future si :
+     * Une période est dans le futur si :
      * - l'année de début est plus grande (>) à l'année à afficher
      * @param $year
      * @return array
      */
-    private function futurConstraint($year): array{
+    protected function futureConstraint($year): array {
         return [
             self::START_KEY => [
                 $year => self::SUP
@@ -106,12 +107,14 @@ class ActivityYearConstraint extends AbstractTimeConstraintUtils
 
     /**
      * Une période est dans une contrainte custom si :
+     *
      * - l'année de début est plus grande ou égale (>=) à l'année de départ
      * - l'année de début est plus petite ou égale (<=) à l'année de fin
-     * @param $year
+     *
+     * @param $years
      * @return array
      */
-    private function customConstraint($years): array{
+    protected function customConstraint($years): array {
         return [
             self::START_KEY => [
                 $years[OrganizationUtils::START_DATE_KEY]  => self::SUP + self::EQUAL,

+ 7 - 7
src/Service/Constraint/DateTimeConstraint.php

@@ -22,12 +22,12 @@ class DateTimeConstraint extends AbstractTimeConstraintUtils
     { }
 
     /**
-     * Main méthode
+     * Main méthod
      * @param int $accessID
      * @return array
      * @throws \Exception
      */
-    public function invoke(int $accessID): array
+    public function invoke(int $accessID): array  // TODO: pas moyen de refactorer avec la méthode invoke de ActivityYearConstraint?
     {
         $access = $this->entityManager->getRepository(Access::class)->find($accessID);
         $historical = $access->getHistorical();
@@ -57,7 +57,7 @@ class DateTimeConstraint extends AbstractTimeConstraintUtils
      * @param string $dateEnd
      * @return string[]
      */
-    private function getCustomPeriods(string $dateStart, string $dateEnd): array{
+    protected function getCustomPeriods(string $dateStart, string $dateEnd): array {
         return [
             OrganizationUtils::START_DATE_KEY => $dateStart,
             OrganizationUtils::END_DATE_KEY => $dateEnd
@@ -72,7 +72,7 @@ class DateTimeConstraint extends AbstractTimeConstraintUtils
      * @throws \Exception
      * @see DateTimeConstraintTest::testGetPeriodsToday()
      */
-    private function getPeriods(Access $access): array {
+    protected function getPeriods(Access $access): array {
         $organization = $access->getOrganization();
         $activityYear = $access->getActivityYear();
         $currentActivityYear = $this->organizationUtils->getOrganizationCurrentActivityYear($organization);
@@ -99,7 +99,7 @@ class DateTimeConstraint extends AbstractTimeConstraintUtils
      *
      * @see DateTimeConstraintTest::testPresentConstrain()
      */
-    private function presentConstraint(array $periods): array {
+    protected function presentConstraint(array $periods): array {
         return [
           self::START_KEY => [
               $periods[OrganizationUtils::END_DATE_KEY] => self::INF + self::EQUAL
@@ -118,7 +118,7 @@ class DateTimeConstraint extends AbstractTimeConstraintUtils
      * @return array
      * @see DateTimeConstraintTest::testPastConstrain()
      */
-    private function pastConstraint($periods): array{
+    protected function pastConstraint($periods): array{
         return [
             self::END_KEY => [
                 $periods[OrganizationUtils::START_DATE_KEY] => self::INF
@@ -133,7 +133,7 @@ class DateTimeConstraint extends AbstractTimeConstraintUtils
      * @return array
      * @see DateTimeConstraintTest::testFuturConstrain()
      */
-    private function futureConstraint($periods): array{
+    protected function futureConstraint($periods): array{
         return [
             self::START_KEY => [
                 $periods[OrganizationUtils::END_DATE_KEY] => self::SUP

+ 1 - 1
src/Service/Core/AddressPostalUtils.php

@@ -7,7 +7,7 @@ use App\Entity\Core\AddressPostal;
 class AddressPostalUtils
 {
     /**
-     * Concatenate and return the three streetAddress parts of the given address
+     * Concatenate the three streetAddress parts of the given address
      *
      * @param AddressPostal $addressPostal
      * @param string $separator

+ 109 - 0
tests/Service/Access/OptionalsRoles/CriteriaNotationOptionalRoleTest.php

@@ -0,0 +1,109 @@
+<?php /** @noinspection DuplicatedCode */
+
+use App\Entity\Access\Access;
+use App\Entity\Organization\Organization;
+use App\Entity\Organization\Parameters;
+use App\Enum\Access\FunctionEnum;
+use App\Repository\Access\AccessRepository;
+use App\Service\Access\OptionalsRoles\CriteriaNotationOptionalRole;
+use PHPUnit\Framework\TestCase;
+
+class CriteriaNotationOptionalRoleTest extends TestCase
+{
+    private AccessRepository $accessRepository;
+
+    public function setUp(): void {
+        $this->accessRepository = $this->getMockBuilder(AccessRepository::class)->disableOriginalConstructor()->getMock();
+    }
+
+    public function testSupportIsSupported(): void
+    {
+        $criteriaNotationOptionalRole = $this
+            ->getMockBuilder(CriteriaNotationOptionalRole::class)
+            ->setConstructorArgs([$this->accessRepository])
+            ->setMethodsExcept(['support'])
+            ->getMock();
+
+        $parameters = $this->getMockBuilder(Parameters::class)->getMock();
+        $parameters->method('getEditCriteriaNotationByAdminOnly')->willReturn(false);
+
+        $organization = $this->getMockBuilder(Organization::class)->getMock();
+        $organization->method('getParameters')->willReturn($parameters);
+
+        $access = $this->getMockBuilder(Access::class)->getMock();
+        $access->method('getOrganization')->willReturn($organization);
+
+        $this->accessRepository
+            ->method('hasGotFunctionAtThisDate')
+            ->with($access, FunctionEnum::TEACHER(), self::isInstanceOf(DateTime::class))
+            ->willReturn(true);
+
+        $this->assertTrue(
+            $criteriaNotationOptionalRole->support($access)
+        );
+    }
+
+    public function testSupportIsNotActiveTeacher(): void
+    {
+        $criteriaNotationOptionalRole = $this
+            ->getMockBuilder(CriteriaNotationOptionalRole::class)
+            ->setConstructorArgs([$this->accessRepository])
+            ->setMethodsExcept(['support'])
+            ->getMock();
+
+        $parameters = $this->getMockBuilder(Parameters::class)->getMock();
+        $parameters->method('getEditCriteriaNotationByAdminOnly')->willReturn(false);
+
+        $organization = $this->getMockBuilder(Organization::class)->getMock();
+        $organization->method('getParameters')->willReturn($parameters);
+
+        $access = $this->getMockBuilder(Access::class)->getMock();
+        $access->method('getOrganization')->willReturn($organization);
+
+        $this->accessRepository
+            ->method('hasGotFunctionAtThisDate')
+            ->with($access, FunctionEnum::TEACHER(), self::isInstanceOf(DateTime::class))
+            ->willReturn(false);
+
+        $this->assertFalse(
+            $criteriaNotationOptionalRole->support($access)
+        );
+    }
+
+    public function testSupportIsActiveTeacherButAdminOnly(): void
+    {
+        $criteriaNotationOptionalRole = $this
+            ->getMockBuilder(CriteriaNotationOptionalRole::class)
+            ->setConstructorArgs([$this->accessRepository])
+            ->setMethodsExcept(['support'])
+            ->getMock();
+
+        $parameters = $this->getMockBuilder(Parameters::class)->getMock();
+        $parameters->method('getEditCriteriaNotationByAdminOnly')->willReturn(true);
+
+        $organization = $this->getMockBuilder(Organization::class)->getMock();
+        $organization->method('getParameters')->willReturn($parameters);
+
+        $access = $this->getMockBuilder(Access::class)->getMock();
+        $access->method('getOrganization')->willReturn($organization);
+
+        $this->accessRepository
+            ->method('hasGotFunctionAtThisDate')
+            ->with($access, FunctionEnum::TEACHER(), self::isInstanceOf(DateTime::class))
+            ->willReturn(true);
+
+        $this->assertFalse(
+            $criteriaNotationOptionalRole->support($access)
+        );
+    }
+
+    public function testGetRole() {
+        $criteriaNotationOptionalRole = $this
+            ->getMockBuilder(CriteriaNotationOptionalRole::class)
+            ->setConstructorArgs([$this->accessRepository])
+            ->setMethodsExcept(['getRole'])
+            ->getMock();
+
+        $this->assertEquals("ROLE_CRITERIANOTATION", $criteriaNotationOptionalRole->getRole());
+    }
+}

+ 311 - 0
tests/Service/Constraint/ActivityYearConstraintTest.php

@@ -0,0 +1,311 @@
+<?php /** @noinspection DuplicatedCode */
+
+namespace App\Tests\Service\Constraint;
+
+use App\Entity\Access\Access;
+use App\Entity\Organization\Organization;
+use App\Repository\Access\AccessRepository;
+use App\Service\Constraint\ActivityYearConstraint;
+use App\Service\Organization\Utils as OrganizationUtils;
+use Doctrine\ORM\EntityManagerInterface;
+use PHPUnit\Framework\TestCase;
+
+class TestableActivityYearConstraint extends ActivityYearConstraint {
+    public function hasCustomPeriods($historical): bool { return parent::hasCustomPeriods($historical); }
+    public function addConstraint(array $contraints, array $newContraint): array { return parent::addConstraint($contraints, $newContraint); }
+    public function cleanConstraints(array $constraints): array { return parent::cleanConstraints($constraints); }
+
+    public function getRangeYear(Access $access, string $dateStart, string $dateEnd): array { return parent::getRangeYear($access, $dateStart, $dateEnd); }
+    public function presentConstraint(int $year): array { return parent::presentConstraint($year); }
+    public function pastConstraint($year): array { return parent::pastConstraint($year); }
+    public function futureConstraint($year): array { return parent::futureConstraint($year); }
+    public function customConstraint($years): array { return parent::customConstraint($years); }
+}
+
+
+class ActivityYearConstraintTest extends TestCase
+{
+    private EntityManagerInterface $entityManager;
+    private OrganizationUtils $organizationUtils;
+
+    public function setUp(): void {
+        $this->entityManager = $this->getMockBuilder(EntityManagerInterface::class)->disableOriginalConstructor()->getMock();
+        $this->organizationUtils = $this->getMockBuilder(OrganizationUtils::class)->disableOriginalConstructor()->getMock();
+    }
+
+    public function testInvokePresentNoCustomPeriods(): void
+    {
+        $activityYearConstraint = $this->getMockBuilder(TestableActivityYearConstraint::class)
+            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
+            ->setMethodsExcept(['invoke'])
+            ->getMock();
+
+        $access = $this->getMockBuilder(Access::class)->getMock();
+
+        $accessRepository = $this->getMockBuilder(AccessRepository::class)->disableOriginalConstructor()->getMock();
+        $accessRepository->method('find')->with(123)->willReturn($access);
+
+        $this->entityManager->method('getRepository')->with(Access::class)->willReturn($accessRepository);
+
+        $historical = ["future" => false,"past" => false,"present" => true,"dateStart" => null,"dateEnd" => null];
+
+        $access->method('getHistorical')->willReturn($historical);
+        $access->method('getActivityYear')->willReturn(2020);
+
+        $activityYearConstraint->method('hasCustomPeriods')->with($historical)->willReturn(false);
+
+        $constraint = ['foo'];
+        $activityYearConstraint->method('presentConstraint')->willReturn($constraint);
+
+        $activityYearConstraint
+            ->expects(self::once())
+            ->method('addConstraint')
+            ->with(['start' => [], 'end' => []] , $constraint)
+            ->willReturn(['bar']);
+
+        $activityYearConstraint
+            ->expects(self::once())
+            ->method('cleanConstraints')
+            ->with(['bar']);
+
+        $activityYearConstraint->invoke(123);
+    }
+
+    public function testInvokePastNoCustomPeriods(): void
+    {
+        $activityYearConstraint = $this->getMockBuilder(TestableActivityYearConstraint::class)
+            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
+            ->setMethodsExcept(['invoke'])
+            ->getMock();
+
+        $access = $this->getMockBuilder(Access::class)->getMock();
+
+        $accessRepository = $this->getMockBuilder(AccessRepository::class)->disableOriginalConstructor()->getMock();
+        $accessRepository->method('find')->with(123)->willReturn($access);
+
+        $this->entityManager->method('getRepository')->with(Access::class)->willReturn($accessRepository);
+
+        $historical = ["future" => false,"past" => true,"present" => false,"dateStart" => null,"dateEnd" => null];
+
+        $access->method('getHistorical')->willReturn($historical);
+        $access->method('getActivityYear')->willReturn(2020);
+
+        $activityYearConstraint->method('hasCustomPeriods')->with($historical)->willReturn(false);
+
+        $constraint = ['foo'];
+        $activityYearConstraint->method('pastConstraint')->willReturn($constraint);
+
+        $activityYearConstraint
+            ->expects(self::once())
+            ->method('addConstraint')
+            ->with(['start' => [], 'end' => []] , $constraint)
+            ->willReturn(['bar']);
+
+        $activityYearConstraint
+            ->expects(self::once())
+            ->method('cleanConstraints')
+            ->with(['bar']);
+
+        $activityYearConstraint->invoke(123);
+    }
+
+    public function testInvokeFutureNoCustomPeriods(): void
+    {
+        $activityYearConstraint = $this->getMockBuilder(TestableActivityYearConstraint::class)
+            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
+            ->setMethodsExcept(['invoke'])
+            ->getMock();
+
+        $access = $this->getMockBuilder(Access::class)->getMock();
+
+        $accessRepository = $this->getMockBuilder(AccessRepository::class)->disableOriginalConstructor()->getMock();
+        $accessRepository->method('find')->with(123)->willReturn($access);
+
+        $this->entityManager->method('getRepository')->with(Access::class)->willReturn($accessRepository);
+
+        $historical = ["future" => true,"past" => false,"present" => false,"dateStart" => null,"dateEnd" => null];
+
+        $access->method('getHistorical')->willReturn($historical);
+        $access->method('getActivityYear')->willReturn(2020);
+
+        $activityYearConstraint->method('hasCustomPeriods')->with($historical)->willReturn(false);
+
+        $constraint = ['foo'];
+        $activityYearConstraint->method('futureConstraint')->willReturn($constraint);
+
+        $activityYearConstraint
+            ->expects(self::once())
+            ->method('addConstraint')
+            ->with(['start' => [], 'end' => []] , $constraint)
+            ->willReturn(['bar']);
+
+        $activityYearConstraint
+            ->expects(self::once())
+            ->method('cleanConstraints')
+            ->with(['bar']);
+
+        $activityYearConstraint->invoke(123);
+    }
+
+    public function testInvokeMultiplePeriodsNoCustom(): void
+    {
+        $activityYearConstraint = $this->getMockBuilder(TestableActivityYearConstraint::class)
+            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
+            ->setMethodsExcept(['invoke'])
+            ->getMock();
+
+        $access = $this->getMockBuilder(Access::class)->getMock();
+
+        $accessRepository = $this->getMockBuilder(AccessRepository::class)->disableOriginalConstructor()->getMock();
+        $accessRepository->method('find')->with(123)->willReturn($access);
+
+        $this->entityManager->method('getRepository')->with(Access::class)->willReturn($accessRepository);
+
+        $historical = ["future" => true,"past" => true,"present" => true,"dateStart" => null,"dateEnd" => null];
+
+        $access->method('getHistorical')->willReturn($historical);
+        $access->method('getActivityYear')->willReturn(2020);
+
+        $activityYearConstraint->method('hasCustomPeriods')->with($historical)->willReturn(false);
+
+        $activityYearConstraint->method('pastConstraint')->willReturn(['pastConstraint']);
+        $activityYearConstraint->method('presentConstraint')->willReturn(['presentConstraint']);
+        $activityYearConstraint->method('futureConstraint')->willReturn(['futureConstraint']);
+
+        $activityYearConstraint
+            ->expects(self::exactly(3))
+            ->method('addConstraint')
+            ->withConsecutive(
+                [['start' => [], 'end' => []] , ['presentConstraint']],
+                [['start' => [], 'end' => []] , ['pastConstraint']],
+                [['start' => [], 'end' => []] , ['futureConstraint']]
+            )->willReturn(['start' => [], 'end' => []]);
+
+        $activityYearConstraint
+            ->expects(self::once())
+            ->method('cleanConstraints')
+            ->with(['start' => [], 'end' => []]);
+
+        $activityYearConstraint->invoke(123);
+    }
+
+    public function testInvokeWithCustom(): void {
+        $activityYearConstraint = $this->getMockBuilder(TestableActivityYearConstraint::class)
+            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
+            ->setMethodsExcept(['invoke'])
+            ->getMock();
+
+        $access = $this->getMockBuilder(Access::class)->getMock();
+
+        $accessRepository = $this->getMockBuilder(AccessRepository::class)->disableOriginalConstructor()->getMock();
+        $accessRepository->method('find')->with(123)->willReturn($access);
+
+        $this->entityManager->method('getRepository')->with(Access::class)->willReturn($accessRepository);
+
+        $historical = ["future" => false,"past" => false,"present" => true,"dateStart" => '2020-01-01',"dateEnd" => '2020-02-01'];
+
+        $access->method('getHistorical')->willReturn($historical);
+        $access->method('getActivityYear')->willReturn(2020);
+
+        $activityYearConstraint->method('hasCustomPeriods')->with($historical)->willReturn(true);
+
+        $activityYearConstraint->expects(self::never())->method('pastConstraint');
+        $activityYearConstraint->expects(self::never())->method('presentConstraint');
+        $activityYearConstraint->expects(self::never())->method('futureConstraint');
+
+        $activityYearConstraint
+            ->method('getRangeYear')
+            ->with($access, '2020-01-01', '2020-02-01')
+            ->willReturn(['dateStart' => 2020, 'dateEnd' => 2020]);
+
+        $activityYearConstraint->method('customConstraint')->with(['dateStart' => 2020, 'dateEnd' => 2020])->willReturn(['bar']);
+
+        $activityYearConstraint
+            ->expects(self::once())
+            ->method('addConstraint')
+            ->with(['start' => [], 'end' => []], ['bar'])
+            ->willReturn(['start' => [], 'end' => []]);
+
+        $activityYearConstraint
+            ->expects(self::once())
+            ->method('cleanConstraints')
+            ->with(['start' => [], 'end' => []]);
+
+        $activityYearConstraint->invoke(123);
+    }
+
+    public function testGetRangeYear(): void {
+        $activityYearConstraint = $this->getMockBuilder(TestableActivityYearConstraint::class)
+            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
+            ->setMethodsExcept(['getRangeYear'])
+            ->getMock();
+
+        $organization = $this->getMockBuilder(Organization::class)->getMock();
+        $access = $this->getMockBuilder(Access::class)->getMock();
+        $access->method('getOrganization')->willReturn($organization);
+
+        $this->organizationUtils
+            ->method('getActivityYearSwitchDate')
+            ->with($organization, self::isInstanceOf(\DateTime::class))
+            ->willReturnOnConsecutiveCalls(
+              2020, 2020, 2020, 2022
+            );
+
+        $this->assertEquals(
+            ['dateStart' => 2020, 'dateEnd' => 2020],
+            $activityYearConstraint->getRangeYear($access, '2020-01-01', '2020-12-31')
+        );
+        $this->assertEquals(
+            ['dateStart' => 2020, 'dateEnd' => 2022],
+            $activityYearConstraint->getRangeYear($access, '2020-01-01', '2022-12-31')
+        );
+    }
+
+    public function testPresentConstraint(): void {
+        $activityYearConstraint = $this->getMockBuilder(TestableActivityYearConstraint::class)
+            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
+            ->setMethodsExcept(['presentConstraint'])
+            ->getMock();
+
+        $this->assertEquals(
+            ['start' => [2020 => 3]],
+            $activityYearConstraint->presentConstraint(2020)
+        );
+    }
+
+    public function testPastConstraint(): void {
+        $activityYearConstraint = $this->getMockBuilder(TestableActivityYearConstraint::class)
+            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
+            ->setMethodsExcept(['pastConstraint'])
+            ->getMock();
+
+        $this->assertEquals(
+            ['end' => [2020 => 1]],
+            $activityYearConstraint->pastConstraint(2020)
+        );
+    }
+
+    public function testFutureConstraint(): void {
+        $activityYearConstraint = $this->getMockBuilder(TestableActivityYearConstraint::class)
+            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
+            ->setMethodsExcept(['futureConstraint'])
+            ->getMock();
+
+        $this->assertEquals(
+            ['start' => [2020 => 5]],
+            $activityYearConstraint->futureConstraint(2020)
+        );
+    }
+
+    public function testCustomConstraint(): void {
+        $activityYearConstraint = $this->getMockBuilder(TestableActivityYearConstraint::class)
+            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
+            ->setMethodsExcept(['customConstraint'])
+            ->getMock();
+
+        $this->assertEquals(
+            ['start' => [2020 => 8, 2022 => 4]],
+            $activityYearConstraint->customConstraint(['dateStart' => 2020, 'dateEnd' => 2022])
+        );
+    }
+}

+ 243 - 12
tests/Service/Constraint/DateTimeConstraintTest.php

@@ -4,6 +4,7 @@ namespace App\Tests\Service\Constraint;
 use App\Entity\Access\Access;
 use App\Entity\Organization\Organization;
 use App\Entity\Organization\Parameters;
+use App\Repository\Access\AccessRepository;
 use App\Service\Constraint\DateTimeConstraint;
 use App\Service\Organization\Utils as OrganizationUtils;
 use App\Tests\TestToolsTrait;
@@ -11,19 +12,31 @@ use Doctrine\ORM\EntityManagerInterface;
 use PHPUnit\Framework\MockObject\MockObject;
 use PHPUnit\Framework\TestCase;
 
+class TestableDateTimeConstraint extends DateTimeConstraint {
+    public function hasCustomPeriods($historical): bool { return parent::hasCustomPeriods($historical); }
+    public function addConstraint(array $contraints, array $newContraint): array { return parent::addConstraint($contraints, $newContraint); }
+    public function cleanConstraints(array $constraints): array { return parent::cleanConstraints($constraints); }
+
+    public function getCustomPeriods(string $dateStart, string $dateEnd): array { return parent::getCustomPeriods($dateStart, $dateEnd); }
+    public function getPeriods(Access $access): array { return parent::getPeriods($access); }
+    public function presentConstraint(array $periods): array { return parent::presentConstraint($periods); }
+    public function pastConstraint($periods): array { return parent::pastConstraint($periods); }
+    public function futureConstraint($periods): array { return parent::futureConstraint($periods); }
+}
+
 
 class DateTimeConstraintTest extends TestCase
 {
    use TestToolsTrait;
 
    private MockObject | OrganizationUtils $organizationUtils;
-   private MockObject | EntityManagerInterface $em;
+   private MockObject | EntityManagerInterface $entityManager;
 
    private array $periods;
 
    public function setUp(): void
    {
-       $this->em = $this->getMockBuilder(EntityManagerInterface::class)->disableOriginalConstructor()->getMock();
+       $this->entityManager = $this->getMockBuilder(EntityManagerInterface::class)->disableOriginalConstructor()->getMock();
        $this->organizationUtils = $this->getMockBuilder(OrganizationUtils::class)->disableOriginalConstructor()->getMock();
 
        $this->periods = [
@@ -32,6 +45,224 @@ class DateTimeConstraintTest extends TestCase
        ];
    }
 
+   public function testInvokePresentNoCustomPeriods(): void {
+       $dateTimeConstraint = $this->getMockBuilder(TestableDateTimeConstraint::class)
+           ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
+           ->setMethodsExcept(['invoke'])
+           ->getMock();
+
+       $access = $this->getMockBuilder(Access::class)->getMock();
+
+       $accessRepository = $this->getMockBuilder(AccessRepository::class)->disableOriginalConstructor()->getMock();
+       $accessRepository->method('find')->with(123)->willReturn($access);
+
+       $this->entityManager->method('getRepository')->with(Access::class)->willReturn($accessRepository);
+
+       $historical = ["future" => false,"past" => false,"present" => true,"dateStart" => null,"dateEnd" => null];
+       $access->method('getHistorical')->willReturn($historical);
+
+       $dateTimeConstraint->method('hasCustomPeriods')->with($historical)->willReturn(false);
+
+       $periods = ['dateStart' => '2020-01-01', 'dateEnd' => '2020-12-31'];
+       $dateTimeConstraint->method('getPeriods')->with($access)->willReturn($periods);
+
+       $constraint = ['foo'];
+       $dateTimeConstraint->method('presentConstraint')->with($periods)->willReturn($constraint);
+
+       $dateTimeConstraint
+           ->expects(self::once())
+           ->method('addConstraint')
+           ->with(['start' => [], 'end' => []] , $constraint)
+           ->willReturn(['bar']);
+
+       $dateTimeConstraint
+           ->expects(self::once())
+           ->method('cleanConstraints')
+           ->with(['bar']);
+
+       $dateTimeConstraint->invoke(123);
+   }
+
+    public function testInvokePastNoCustomPeriods(): void
+    {
+        $dateTimeConstraint = $this->getMockBuilder(TestableDateTimeConstraint::class)
+            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
+            ->setMethodsExcept(['invoke'])
+            ->getMock();
+
+        $access = $this->getMockBuilder(Access::class)->getMock();
+
+        $accessRepository = $this->getMockBuilder(AccessRepository::class)->disableOriginalConstructor()->getMock();
+        $accessRepository->method('find')->with(123)->willReturn($access);
+
+        $this->entityManager->method('getRepository')->with(Access::class)->willReturn($accessRepository);
+
+        $historical = ["future" => false,"past" => true,"present" => false,"dateStart" => null,"dateEnd" => null];
+
+        $access->method('getHistorical')->willReturn($historical);
+
+        $dateTimeConstraint->method('hasCustomPeriods')->with($historical)->willReturn(false);
+
+        $periods = ['dateStart' => '2020-01-01', 'dateEnd' => '2020-12-31'];
+        $dateTimeConstraint->method('getPeriods')->with($access)->willReturn($periods);
+
+        $constraint = ['foo'];
+        $dateTimeConstraint->method('pastConstraint')->with($periods)->willReturn($constraint);
+
+        $dateTimeConstraint
+            ->expects(self::once())
+            ->method('addConstraint')
+            ->with(['start' => [], 'end' => []] , $constraint)
+            ->willReturn(['bar']);
+
+        $dateTimeConstraint
+            ->expects(self::once())
+            ->method('cleanConstraints')
+            ->with(['bar']);
+
+        $dateTimeConstraint->invoke(123);
+    }
+
+    public function testInvokeFutureNoCustomPeriods(): void
+    {
+        $dateTimeConstraint = $this->getMockBuilder(TestableDateTimeConstraint::class)
+            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
+            ->setMethodsExcept(['invoke'])
+            ->getMock();
+
+        $access = $this->getMockBuilder(Access::class)->getMock();
+
+        $accessRepository = $this->getMockBuilder(AccessRepository::class)->disableOriginalConstructor()->getMock();
+        $accessRepository->method('find')->with(123)->willReturn($access);
+
+        $this->entityManager->method('getRepository')->with(Access::class)->willReturn($accessRepository);
+
+        $historical = ["future" => true,"past" => false,"present" => false,"dateStart" => null,"dateEnd" => null];
+
+        $access->method('getHistorical')->willReturn($historical);
+
+        $dateTimeConstraint->method('hasCustomPeriods')->with($historical)->willReturn(false);
+
+        $periods = ['dateStart' => '2020-01-01', 'dateEnd' => '2020-12-31'];
+        $dateTimeConstraint->method('getPeriods')->with($access)->willReturn($periods);
+
+        $constraint = ['foo'];
+        $dateTimeConstraint->method('futureConstraint')->with($periods)->willReturn($constraint);
+
+        $dateTimeConstraint
+            ->expects(self::once())
+            ->method('addConstraint')
+            ->with(['start' => [], 'end' => []] , $constraint)
+            ->willReturn(['bar']);
+
+        $dateTimeConstraint
+            ->expects(self::once())
+            ->method('cleanConstraints')
+            ->with(['bar']);
+
+        $dateTimeConstraint->invoke(123);
+    }
+
+    public function testInvokeMultiplePeriodsNoCustom(): void
+    {
+        $dateTimeConstraint = $this->getMockBuilder(TestableDateTimeConstraint::class)
+            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
+            ->setMethodsExcept(['invoke'])
+            ->getMock();
+
+        $access = $this->getMockBuilder(Access::class)->getMock();
+
+        $accessRepository = $this->getMockBuilder(AccessRepository::class)->disableOriginalConstructor()->getMock();
+        $accessRepository->method('find')->with(123)->willReturn($access);
+
+        $this->entityManager->method('getRepository')->with(Access::class)->willReturn($accessRepository);
+
+        $historical = ["future" => true,"past" => true,"present" => true,"dateStart" => null,"dateEnd" => null];
+
+        $access->method('getHistorical')->willReturn($historical);
+        $dateTimeConstraint->method('getPeriods')->with($access)->willReturn(['dateStart' => '2020-01-01', 'dateEnd' => '2020-12-31']);
+
+        $dateTimeConstraint->method('hasCustomPeriods')->with($historical)->willReturn(false);
+
+        $dateTimeConstraint->method('pastConstraint')->willReturn(['pastConstraint']);
+        $dateTimeConstraint->method('presentConstraint')->willReturn(['presentConstraint']);
+        $dateTimeConstraint->method('futureConstraint')->willReturn(['futureConstraint']);
+
+        $dateTimeConstraint
+            ->expects(self::exactly(3))
+            ->method('addConstraint')
+            ->withConsecutive(
+                [['start' => [], 'end' => []] , ['presentConstraint']],
+                [['start' => [], 'end' => []] , ['pastConstraint']],
+                [['start' => [], 'end' => []] , ['futureConstraint']]
+            )->willReturn(['start' => [], 'end' => []]);
+
+        $dateTimeConstraint
+            ->expects(self::once())
+            ->method('cleanConstraints')
+            ->with(['start' => [], 'end' => []]);
+
+        $dateTimeConstraint->invoke(123);
+    }
+
+    public function testInvokeWithCustom(): void {
+        $dateTimeConstraint = $this->getMockBuilder(TestableDateTimeConstraint::class)
+            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
+            ->setMethodsExcept(['invoke'])
+            ->getMock();
+
+        $access = $this->getMockBuilder(Access::class)->getMock();
+
+        $accessRepository = $this->getMockBuilder(AccessRepository::class)->disableOriginalConstructor()->getMock();
+        $accessRepository->method('find')->with(123)->willReturn($access);
+
+        $this->entityManager->method('getRepository')->with(Access::class)->willReturn($accessRepository);
+
+        $historical = ["future" => false,"past" => false,"present" => true,"dateStart" => '2020-01-01',"dateEnd" => '2020-02-01'];
+
+        $access->method('getHistorical')->willReturn($historical);
+
+        $dateTimeConstraint->method('hasCustomPeriods')->with($historical)->willReturn(true);
+
+        $dateTimeConstraint->expects(self::never())->method('pastConstraint');
+        $dateTimeConstraint->expects(self::never())->method('futureConstraint');
+
+        $dateTimeConstraint
+            ->method('getCustomPeriods')
+            ->with('2020-01-01', '2020-02-01')
+            ->willReturn(['dateStart' => 2020, 'dateEnd' => 2020]);
+
+        $dateTimeConstraint->expects(self::once())
+            ->method('presentConstraint')
+            ->with(['dateStart' => 2020, 'dateEnd' => 2020])
+            ->willReturn(['bar']);
+
+        $dateTimeConstraint
+            ->expects(self::once())
+            ->method('addConstraint')
+            ->with(['start' => [], 'end' => []], ['bar'])
+            ->willReturn(['start' => [], 'end' => []]);
+
+        $dateTimeConstraint
+            ->expects(self::once())
+            ->method('cleanConstraints')
+            ->with(['start' => [], 'end' => []]);
+
+        $dateTimeConstraint->invoke(123);
+    }
+
+   public function testGetCustomPeriods(): void {
+       $dateTimeConstraint = $this->getMockBuilder(TestableDateTimeConstraint::class)
+           ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
+           ->setMethodsExcept(['getCustomPeriods'])
+           ->getMock();
+
+       $this->assertEquals(
+           ['dateStart' => '2020-01-01', 'dateEnd' => '2020-12-31'],
+           $dateTimeConstraint->getCustomPeriods('2020-01-01', '2020-12-31')
+       );
+   }
+
    /**
     * @see DateTimeConstraint::presentConstraint()
     */
@@ -47,8 +278,8 @@ class DateTimeConstraintTest extends TestCase
            ]
        ];
 
-       $dateTimeConstraint = $this->getMockBuilder(DateTimeConstraint::class)
-           ->setConstructorArgs([$this->em, $this->organizationUtils])
+       $dateTimeConstraint = $this->getMockBuilder(TestableDateTimeConstraint::class)
+           ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
            ->setMethodsExcept(['presentConstraint'])
            ->getMock();
 
@@ -68,8 +299,8 @@ class DateTimeConstraintTest extends TestCase
             ]
         ];
 
-        $dateTimeConstraint = $this->getMockBuilder(DateTimeConstraint::class)
-            ->setConstructorArgs([$this->em, $this->organizationUtils])
+        $dateTimeConstraint = $this->getMockBuilder(TestableDateTimeConstraint::class)
+            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
             ->setMethodsExcept(['pastConstraint'])
             ->getMock();
 
@@ -89,8 +320,8 @@ class DateTimeConstraintTest extends TestCase
             ]
         ];
 
-        $dateTimeConstraint = $this->getMockBuilder(DateTimeConstraint::class)
-            ->setConstructorArgs([$this->em, $this->organizationUtils])
+        $dateTimeConstraint = $this->getMockBuilder(TestableDateTimeConstraint::class)
+            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
             ->setMethodsExcept(['futureConstraint'])
             ->getMock();
 
@@ -130,8 +361,8 @@ class DateTimeConstraintTest extends TestCase
             ->with($organization, 2020)
             ->willReturn(['dateStart' => 'YEAR-09-01', 'dateEnd' => ($activityYear + 1) . '-08-31']); // dateStart will be overwritten
 
-        $dateTimeConstraint = $this->getMockBuilder(DateTimeConstraint::class)
-            ->setConstructorArgs([$this->em, $this->organizationUtils])
+        $dateTimeConstraint = $this->getMockBuilder(TestableDateTimeConstraint::class)
+            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
             ->setMethodsExcept(['getPeriods'])
             ->getMock();
 
@@ -160,8 +391,8 @@ class DateTimeConstraintTest extends TestCase
             ->with($organization, 2020)
             ->willReturn(['dateStart' => '2020-09-01', 'dateEnd' => '2021-08-31']);
 
-        $dateTimeConstraint = $this->getMockBuilder(DateTimeConstraint::class)
-            ->setConstructorArgs([$this->em, $this->organizationUtils])
+        $dateTimeConstraint = $this->getMockBuilder(TestableDateTimeConstraint::class)
+            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
             ->setMethodsExcept(['getPeriods'])
             ->getMock();
 

+ 44 - 0
tests/Service/Core/AddressPostalUtilsTest.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace App\Test\Service\Access;
+
+use App\Entity\Core\AddressPostal;
+use App\Service\Core\AddressPostalUtils;
+use PHPUnit\Framework\TestCase;
+
+class AddressPostalUtilsTest extends TestCase
+{
+    public function testGetFullStreetAddress(): void {
+        $addressPostalUtils = new AddressPostalUtils(); // Cf. bug when SUT has only one method, can't be mocked
+
+        $addressPostal = $this->getMockBuilder(AddressPostal::class)->getMock();
+        $addressPostal->method('getStreetAddress')->willReturn('Abc');
+        $addressPostal->method('getStreetAddressSecond')->willReturn('Def  ');
+        $addressPostal->method('getStreetAddressThird')->willReturn('  Ghi ');
+
+        $this->assertEqualsCanonicalizing(
+            "Abc\nDef\nGhi",
+            $addressPostalUtils->getFullStreetAddress($addressPostal)
+        );
+
+        $this->assertEqualsCanonicalizing(
+            "Abc Def Ghi",
+            $addressPostalUtils->getFullStreetAddress($addressPostal, ' ')
+        );
+    }
+
+    public function testGetFullStreetAddressWithMissing(): void {
+        $addressPostalUtils = new AddressPostalUtils(); // Cf. bug when SUT has only one method, can't be mocked
+
+        $addressPostal = $this->getMockBuilder(AddressPostal::class)->getMock();
+        $addressPostal->method('getStreetAddress')->willReturn('Abc');
+        $addressPostal->method('getStreetAddressSecond')->willReturn('');
+        $addressPostal->method('getStreetAddressThird')->willReturn('  Def');
+
+        $this->assertEqualsCanonicalizing(
+            "Abc\nDef",
+            $addressPostalUtils->getFullStreetAddress($addressPostal)
+        );
+    }
+
+}

+ 29 - 1
tests/Service/Core/ContactPointUtilsTest.php

@@ -52,7 +52,8 @@ class ContactPointUtilsTest extends TestCase
     /**
      * @see Utils::getPersonContactPointPrincipal()
      */
-    public function testGetPersonContactPointPrincipalNotExisting(){
+    public function testGetPersonContactPointPrincipalNotExisting(): void
+    {
         $person = $this->getMockBuilder(Person::class)->disableOriginalConstructor()->getMock();
         $access = $this->getMockBuilder(Access::class)->disableOriginalConstructor()->getMock();
         $access->method('getPerson')->willReturn($person);
@@ -72,4 +73,31 @@ class ContactPointUtilsTest extends TestCase
             $contactPointUtils->getPersonContactPointPrincipal($access)
         );
     }
+
+    /**
+     * @see Utils::getPersonContactPointPrincipal()
+     */
+    public function testGetPersonContactPointPrincipalMoreThanOne(): void {
+        $person = $this->getMockBuilder(Person::class)->disableOriginalConstructor()->getMock();
+        $access = $this->getMockBuilder(Access::class)->disableOriginalConstructor()->getMock();
+        $access->method('getPerson')->willReturn($person);
+
+        $contactPoint1 = $this->getMockBuilder(ContactPoint::class)->disableOriginalConstructor()->getMock();
+        $contactPoint2 = $this->getMockBuilder(ContactPoint::class)->disableOriginalConstructor()->getMock();
+
+        $this->contactPointRepository
+            ->method('getByTypeAndPerson')
+            ->with(ContactPointTypeEnum::PRINCIPAL()->getValue(), $access->getPerson())
+            ->willReturn([$contactPoint1, $contactPoint2]);
+
+        $contactPointUtils = $this
+            ->getMockBuilder(ContactPointUtils::class)
+            ->setConstructorArgs([$this->contactPointRepository])
+            ->setMethodsExcept(['getPersonContactPointPrincipal'])
+            ->getMock();
+
+        $this->expectException(\RuntimeException::class);
+
+        $contactPointUtils->getPersonContactPointPrincipal($access);
+    }
 }

+ 21 - 1
tests/Service/Cotisation/UtilsTest.php

@@ -328,7 +328,7 @@ class UtilsTest extends TestCase
     /**
      * @see Utils::getCurrentCotisationYear()
      */
-    public function testGetCurrentCotisationYear(): void
+    public function testGetCurrentCotisationYearDefault(): void
     {
         $cotisationUtils = $this->getMockBuilder(CotisationUtils::class)
             ->setConstructorArgs([$this->networkUtils, $this->organizationUtils, $this->networkOrganizationRepository, $this->cotisationApiResourcesRepository])
@@ -343,4 +343,24 @@ class UtilsTest extends TestCase
 
         $this->assertEquals($expectedYear, $cotisationUtils->getCurrentCotisationYear());
     }
+
+    /**
+     * @see Utils::getCurrentCotisationYear()
+     */
+    public function testGetCurrentCotisationYear(): void
+    {
+        $cotisationUtils = $this->getMockBuilder(CotisationUtils::class)
+            ->setConstructorArgs([$this->networkUtils, $this->organizationUtils, $this->networkOrganizationRepository, $this->cotisationApiResourcesRepository])
+            ->setMethodsExcept(['getCurrentCotisationYear'])
+            ->getMock();
+
+        $this->assertEquals(
+            2022,
+            $cotisationUtils->getCurrentCotisationYear(new \DateTime('2022-03-01'))
+        );
+        $this->assertEquals(
+            2023,
+            $cotisationUtils->getCurrentCotisationYear(new \DateTime('2022-10-01'))
+        );
+    }
 }

+ 102 - 0
tests/Service/Dolibarr/DolibarrApiServiceTest.php

@@ -80,6 +80,28 @@ class DolibarrApiServiceTest extends TestCase
         $this->assertEquals(null, $dolibarrApiService->getActiveContract($socId));
     }
 
+    /**
+     * @see DolibarrApiService::getActiveContract()
+     */
+    public function testGetActiveContractError(): void {
+        $dolibarrApiService = $this->getMockBuilder(DolibarrApiService::class)
+            ->setConstructorArgs([$this->client])
+            ->setMethodsExcept(['getActiveContract'])
+            ->getMock();
+
+        $socId = 1;
+
+        $dolibarrApiService
+            ->expects(self::once())
+            ->method('getJsonContent')
+            ->with("contracts", ["limit" => "1", "sqlfilters" => "statut=1", "thirdparty_ids" => $socId])
+            ->willThrowException(new HttpException(500));
+
+        $this->expectException(HttpException::class);
+
+        $dolibarrApiService->getActiveContract($socId);
+    }
+
     /**
      * @see DolibarrApiService::getBills()
      */
@@ -120,6 +142,31 @@ class DolibarrApiServiceTest extends TestCase
         $this->assertEquals([], $dolibarrApiService->getBills($socId));
     }
 
+    /**
+     * @see DolibarrApiService::getBills()
+     */
+    public function testGetBillsError(): void {
+        $dolibarrApiService = $this->getMockBuilder(DolibarrApiService::class)
+            ->setConstructorArgs([$this->client])
+            ->setMethodsExcept(['getBills'])
+            ->getMock();
+
+        $socId = 1;
+
+        $dolibarrApiService
+            ->expects(self::once())
+            ->method('getJsonContent')
+            ->with("invoices", ["sortfield" => "datef", "sortorder" => "DESC", "limit" => 5, "sqlfilters" => "fk_soc=" . $socId])
+            ->willThrowException(new HttpException(500));
+
+        $this->expectException(HttpException::class);
+
+        $dolibarrApiService->getBills($socId);
+    }
+
+    /**
+     * @see DolibarrApiService::getAllClients()
+     */
     public function testGetAllClients(): void
     {
         $dolibarrApiService = $this->getMockBuilder(DolibarrApiService::class)
@@ -136,6 +183,9 @@ class DolibarrApiServiceTest extends TestCase
         $this->assertEquals([['id' => 10], ['id' => 20]], $dolibarrApiService->getAllClients());
     }
 
+    /**
+     * @see DolibarrApiService::getContacts()
+     */
     public function testGetContacts() {
         $dolibarrApiService = $this->getMockBuilder(DolibarrApiService::class)
             ->setConstructorArgs([$this->client])
@@ -153,6 +203,9 @@ class DolibarrApiServiceTest extends TestCase
         $this->assertEquals([['id' => 10], ['id' => 20]], $dolibarrApiService->getContacts($socId));
     }
 
+    /**
+     * @see DolibarrApiService::getContacts()
+     */
     public function testGetContactsMissing(): void {
         $dolibarrApiService = $this->getMockBuilder(DolibarrApiService::class)
             ->setConstructorArgs([$this->client])
@@ -170,6 +223,32 @@ class DolibarrApiServiceTest extends TestCase
         $this->assertEquals([], $dolibarrApiService->getContacts($socId));
     }
 
+    /**
+     * @see DolibarrApiService::getContacts()
+     */
+    public function testGetContactsError(): void {
+        $dolibarrApiService = $this->getMockBuilder(DolibarrApiService::class)
+            ->setConstructorArgs([$this->client])
+            ->setMethodsExcept(['getContacts'])
+            ->getMock();
+
+        $socId = 1;
+
+        $dolibarrApiService
+            ->expects(self::once())
+            ->method('getJsonContent')
+            ->with("contacts", ['limit' => 1000, 'thirdparty_ids' => $socId])
+            ->willThrowException(new HttpException(500));
+
+
+        $this->expectException(HttpException::class);
+
+        $dolibarrApiService->getContacts($socId);
+    }
+
+    /**
+     * @see DolibarrApiService::getActiveOpentalentContacts()
+     */
     public function testGetActiveOpentalentContacts() {
         $dolibarrApiService = $this->getMockBuilder(DolibarrApiService::class)
             ->setConstructorArgs([$this->client])
@@ -187,6 +266,9 @@ class DolibarrApiServiceTest extends TestCase
         $this->assertEquals([['id' => 10], ['id' => 20]], $dolibarrApiService->getActiveOpentalentContacts($socId));
     }
 
+    /**
+     * @see DolibarrApiService::getActiveOpentalentContacts()
+     */
     public function testGetActiveOpentalentContactsMissing(): void {
         $dolibarrApiService = $this->getMockBuilder(DolibarrApiService::class)
             ->setConstructorArgs([$this->client])
@@ -204,5 +286,25 @@ class DolibarrApiServiceTest extends TestCase
         $this->assertEquals([], $dolibarrApiService->getActiveOpentalentContacts($socId));
     }
 
+    /**
+     * @see DolibarrApiService::getActiveOpentalentContacts()
+     */
+    public function testGetActiveOpentalentContactsError(): void {
+        $dolibarrApiService = $this->getMockBuilder(DolibarrApiService::class)
+            ->setConstructorArgs([$this->client])
+            ->setMethodsExcept(['getActiveOpentalentContacts'])
+            ->getMock();
 
+        $socId = 1;
+
+        $dolibarrApiService
+            ->expects(self::once())
+            ->method('getJsonContent')
+            ->with("contacts?limit=1000&t.statut=1&thirdparty_ids=" . $socId . "&sqlfilters=(te.2iopen_person_id%3A%3E%3A0)")
+            ->willThrowException(new HttpException(500));
+
+        $this->expectException(HttpException::class);
+
+        $dolibarrApiService->getActiveOpentalentContacts($socId);
+    }
 }

+ 73 - 0
tests/Service/Dolibarr/DolibarrSyncServiceTest.php

@@ -38,6 +38,7 @@ use Psr\Log\LoggerInterface;
 use RuntimeException;
 use Symfony\Contracts\HttpClient\ResponseInterface;
 use Symfony\Contracts\Translation\TranslatorInterface;
+use Symfony\Component\HttpClient\Exception\ServerException;
 
 class TestableDolibarrSyncService extends DolibarrSyncService {
     public function getDolibarrSocietiesIndex(): array { return parent::getDolibarrSocietiesIndex(); }
@@ -1247,4 +1248,76 @@ class DolibarrSyncServiceTest extends TestCase
             $dolibarrSyncService->formatPhoneNumber($phoneNumber)
         );
     }
+
+    public function testValidateResponse(): void {
+        $dolibarrSyncService = $this->getMockBuilder(TestableDolibarrSyncService::class)
+            ->setConstructorArgs([$this->organizationRepository, $this->accessRepository, $this->functionTypeRepository,
+                $this->dolibarrApiService, $this->addressPostalUtils, $this->arrayUtils, $this->translator, $this->logger])
+            ->setMethodsExcept(['validateResponse'])
+            ->getMock();
+
+        $response = $this->getMockBuilder(ResponseInterface::class)->getMock();
+        $response->method('toArray')->willReturn(['a' => 1]);
+
+        $operation = $this->getMockBuilder(CreateOperation::class)->disableOriginalConstructor()->getMock();
+        $operation->method('getData')->willReturn(['a' => 1]);
+
+        $dolibarrSyncService->expects(self::exactly(2))->method('sanitizeDolibarrData')->with(['a' => 1])->willReturn(['a' => 1]);
+
+        $this->arrayUtils->expects(self::once())->method('getChanges')->with(['a' => 1], ['a' => 1], true)->willReturn([]);
+
+        $dolibarrSyncService->validateResponse($response, $operation);
+    }
+
+    public function testValidateResponseInvalid(): void {
+        $dolibarrSyncService = $this->getMockBuilder(TestableDolibarrSyncService::class)
+            ->setConstructorArgs([$this->organizationRepository, $this->accessRepository, $this->functionTypeRepository,
+                $this->dolibarrApiService, $this->addressPostalUtils, $this->arrayUtils, $this->translator, $this->logger])
+            ->setMethodsExcept(['validateResponse'])
+            ->getMock();
+
+        $response = $this->getMockBuilder(ResponseInterface::class)->getMock();
+        $response->method('toArray')->willReturn(['a' => 1]);
+
+        $operation = $this->getMockBuilder(CreateOperation::class)->disableOriginalConstructor()->getMock();
+        $operation->method('getData')->willReturn(['a' => 0]);
+
+        $dolibarrSyncService->expects(self::exactly(2))
+            ->method('sanitizeDolibarrData')
+            ->willReturnMap([
+                [['a' => 1], ['a' => 1]],
+                [['a' => 0], ['a' => 0]]
+            ]);
+
+        $this->arrayUtils->expects(self::once())->method('getChanges')->with(['a' => 1], ['a' => 0], true)->willReturn(['a' => 0]);
+
+        $this->expectException(RuntimeException::class);
+
+        $dolibarrSyncService->validateResponse($response, $operation);
+    }
+
+    public function testValidateResponseRequestError(): void {
+        $dolibarrSyncService = $this->getMockBuilder(TestableDolibarrSyncService::class)
+            ->setConstructorArgs([$this->organizationRepository, $this->accessRepository, $this->functionTypeRepository,
+                $this->dolibarrApiService, $this->addressPostalUtils, $this->arrayUtils, $this->translator, $this->logger])
+            ->setMethodsExcept(['validateResponse'])
+            ->getMock();
+
+        $response = $this->getMockBuilder(ResponseInterface::class)->getMock();
+        $response->method('getInfo')->willReturnMap([
+            ['http_code', '200'], ['url', 'http://url.com'], ['response_headers', []]
+        ]);
+        $response->method('getContent')->willReturn("");
+        $response->method('toArray')->willThrowException(new ServerException($response));
+
+        $operation = $this->getMockBuilder(CreateOperation::class)->disableOriginalConstructor()->getMock();
+        $operation->method('getData')->willReturn(['a' => 0]);
+
+        $dolibarrSyncService->expects(self::never())->method('sanitizeDolibarrData');
+        $this->arrayUtils->expects(self::never())->method('getChanges');
+
+        $this->expectException(RuntimeException::class);
+
+        $dolibarrSyncService->validateResponse($response, $operation);
+    }
 }

+ 35 - 0
tests/Service/Elasticsearch/EducationNotationUpdaterTest.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace App\Tests\Service\Elasticsearch;
+
+use App\Service\Elasticsearch\EducationNotationUpdater;
+use FOS\ElasticaBundle\Persister\ObjectPersister;
+use PHPUnit\Framework\TestCase;
+
+class TestableEducationNotationUpdater extends EducationNotationUpdater {
+    // To avoid the bug when mocking classes with only one method;
+    // can be removed when the tested class will have at least 2 methods
+    public function foo() {}
+}
+
+class EducationNotationUpdaterTest extends TestCase
+{
+    private ObjectPersister $objectPersister;
+
+    public function setUp(): void {
+        $this->objectPersister = $this->getMockBuilder(ObjectPersister::class)->disableOriginalConstructor()->getMock();
+    }
+
+    public function testUpdate(): void {
+        $educationNotationUpdater = $this->getMockBuilder(TestableEducationNotationUpdater::class)
+            ->setConstructorArgs([$this->objectPersister])
+            ->setMethodsExcept(['update'])
+            ->getMock();
+
+        $educationNotations = ['foo'];
+
+        $this->objectPersister->expects(self::once())->method('replaceMany')->with($educationNotations);
+
+        $educationNotationUpdater->update($educationNotations);
+    }
+}

+ 186 - 0
tests/Service/Export/BaseExporterTest.php

@@ -0,0 +1,186 @@
+<?php
+
+namespace App\Tests\Service\Export;
+
+use App\ApiResources\Export\ExportRequest;
+use App\Entity\Access\Access;
+use App\Entity\Core\File;
+use App\Entity\Organization\Organization;
+use App\Enum\Core\FileTypeEnum;
+use App\Repository\Access\AccessRepository;
+use App\Repository\Core\FileRepository;
+use App\Service\Export\BaseExporter;
+use App\Service\Export\Model\ExportModelInterface;
+use App\Service\ServiceIterator\EncoderIterator;
+use App\Service\Storage\LocalStorage;
+use Doctrine\ORM\EntityManagerInterface;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+use Psr\Log\LoggerInterface;
+use Twig\Environment;
+
+class TestableBaseExporter extends BaseExporter {
+    public function buildModel(ExportRequest $exportRequest): ExportModelInterface { return parent::buildModel($exportRequest); }
+    public function getBasename(): string { return parent::getBasename(); }
+    public function getTemplatePath(): string { return parent::getTemplatePath(); }
+    public function render(ExportModelInterface $model): string { return parent::render($model); }
+    public function encode(string $html, string $format): string { return parent::encode($html, $format); }
+    public function getFileBasename(ExportRequest $exportRequest): string { return parent::getFileBasename($exportRequest); }
+    public function getFileType(): FileTypeEnum { return parent::getFileType(); }
+}
+
+
+class BaseExporterTest extends TestCase
+{
+    private AccessRepository $accessRepository;
+    private FileRepository $fileRepository;
+    private Environment $twig;
+    private EncoderIterator $encoderIterator;
+    private EntityManagerInterface $entityManager;
+    private LocalStorage $storage;
+    private LoggerInterface $logger;
+
+    public function setUp(): void {
+        $this->accessRepository = $this->getMockBuilder(AccessRepository::class)->disableOriginalConstructor()->getMock();
+        $this->fileRepository = $this->getMockBuilder(FileRepository::class)->disableOriginalConstructor()->getMock();
+        $this->twig = $this->getMockBuilder(Environment::class)->disableOriginalConstructor()->getMock();
+        $this->encoderIterator = $this->getMockBuilder(EncoderIterator::class)->disableOriginalConstructor()->getMock();
+        $this->entityManager = $this->getMockBuilder(EntityManagerInterface::class)->disableOriginalConstructor()->getMock();
+        $this->storage = $this->getMockBuilder(LocalStorage::class)->disableOriginalConstructor()->getMock();
+        $this->logger = $this->getMockBuilder(LoggerInterface::class)->disableOriginalConstructor()->getMock();
+    }
+
+    private function getBaseExporterMockFor(string $method): MockObject | TestableBaseExporter
+    {
+        $exporter = $this->getMockBuilder(TestableBaseExporter::class)
+            ->setMethodsExcept([$method, 'setAccessRepository', 'setFileRepository', 'setTwig', 'setEncoderIterator',
+                'setEntityManager', 'setStorage', 'setLogger'])
+            ->getMock();
+
+        $exporter->setAccessRepository($this->accessRepository);
+        $exporter->setFileRepository($this->fileRepository);
+        $exporter->setTwig($this->twig);
+        $exporter->setEncoderIterator($this->encoderIterator);
+        $exporter->setEntityManager($this->entityManager);
+        $exporter->setStorage($this->storage);
+        $exporter->setLogger($this->logger);
+
+        return $exporter;
+    }
+
+    public function testSupport(): void {
+        $exporter = $this->getBaseExporterMockFor('support');
+
+        $exportRequest = $this->getMockBuilder(ExportRequest::class)->disableOriginalConstructor()->getMock();
+
+        $this->assertFalse($exporter->support($exportRequest));
+    }
+
+    public function testExport(): void {
+        $exporter = $this->getBaseExporterMockFor('export');
+
+        $exportRequest = $this->getMockBuilder(ExportRequest::class)->disableOriginalConstructor()->getMock();
+        $exportRequest->method('getRequesterId')->willReturn(123);
+        $exportRequest->method('getFileId')->willReturn(456);
+
+        $model = $this->getMockBuilder(ExportModelInterface::class)->disableOriginalConstructor()->getMock();
+        $exporter->expects(self::once())->method('buildModel')->with($exportRequest)->willReturn($model);
+
+        $html = "<div>foo</div>";
+        $exporter->expects(self::once())->method('render')->with($model)->willReturn($html);
+
+        $data = 'azerty';
+        $exporter->expects(self::once())->method('encode')->with($html)->willReturn($data);
+
+        $access = $this->getMockBuilder(Access::class)->disableOriginalConstructor()->getMock();
+        $this->accessRepository->method('find')->with(123, null, null)->willReturn($access);
+
+        $file = $this->getMockBuilder(File::class)->disableOriginalConstructor()->getMock();
+        $this->fileRepository->method('find')->with(456, null, null)->willReturn($file);
+
+        $this->storage->expects(self::once())->method('writeFile')->with($file, $data, $access);
+
+        $exporter->export($exportRequest);
+    }
+
+    public function testExportNewFile(): void {
+        $exporter = $this->getBaseExporterMockFor('export');
+
+        $exportRequest = $this->getMockBuilder(ExportRequest::class)->disableOriginalConstructor()->getMock();
+        $exportRequest->method('getRequesterId')->willReturn(123);
+        $exportRequest->method('getFileId')->willReturn(null);
+
+        $model = $this->getMockBuilder(ExportModelInterface::class)->disableOriginalConstructor()->getMock();
+        $exporter->expects(self::once())->method('buildModel')->with($exportRequest)->willReturn($model);
+
+        $html = "<div>foo</div>";
+        $exporter->expects(self::once())->method('render')->with($model)->willReturn($html);
+
+        $data = 'azerty';
+        $exporter->expects(self::once())->method('encode')->with($html)->willReturn($data);
+
+        $organization = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();
+        $access = $this->getMockBuilder(Access::class)->disableOriginalConstructor()->getMock();
+        $access->method('getOrganization')->willReturn($organization);
+        $this->accessRepository->method('find')->with(123, null, null)->willReturn($access);
+
+        $file = $this->getMockBuilder(File::class)->disableOriginalConstructor()->getMock();
+        $exporter->expects(self::once())->method('prepareFile')->willReturn($file);
+
+        $this->storage->expects(self::once())->method('writeFile')->with($file, $data, $access);
+
+        $exporter->export($exportRequest);
+    }
+
+    public function testExportUndefinedRequester(): void {
+        $exporter = $this->getBaseExporterMockFor('export');
+
+        $exportRequest = $this->getMockBuilder(ExportRequest::class)->disableOriginalConstructor()->getMock();
+        $exportRequest->method('getRequesterId')->willReturn(123);
+        $exportRequest->method('getFileId')->willReturn(456);
+
+        $model = $this->getMockBuilder(ExportModelInterface::class)->disableOriginalConstructor()->getMock();
+        $exporter->expects(self::once())->method('buildModel')->with($exportRequest)->willReturn($model);
+
+        $html = "<div>foo</div>";
+        $exporter->expects(self::once())->method('render')->with($model)->willReturn($html);
+
+        $data = 'azerty';
+        $exporter->expects(self::once())->method('encode')->with($html)->willReturn($data);
+
+        $this->accessRepository->method('find')->with(123, null, null)->willReturn(null);
+
+        $this->storage->expects(self::never())->method('writeFile');
+
+        $this->expectException(\RuntimeException::class);
+
+        $exporter->export($exportRequest);
+    }
+
+    public function testExportUndefinedOrganization(): void {
+        $exporter = $this->getBaseExporterMockFor('export');
+
+        $exportRequest = $this->getMockBuilder(ExportRequest::class)->disableOriginalConstructor()->getMock();
+        $exportRequest->method('getRequesterId')->willReturn(123);
+        $exportRequest->method('getFileId')->willReturn(null);
+
+        $model = $this->getMockBuilder(ExportModelInterface::class)->disableOriginalConstructor()->getMock();
+        $exporter->expects(self::once())->method('buildModel')->with($exportRequest)->willReturn($model);
+
+        $html = "<div>foo</div>";
+        $exporter->expects(self::once())->method('render')->with($model)->willReturn($html);
+
+        $data = 'azerty';
+        $exporter->expects(self::once())->method('encode')->with($html)->willReturn($data);
+
+        $access = $this->getMockBuilder(Access::class)->disableOriginalConstructor()->getMock();
+        $access->method('getOrganization')->willReturn(null);
+        $this->accessRepository->method('find')->with(123, null, null)->willReturn($access);
+
+        $this->storage->expects(self::never())->method('writeFile');
+
+        $this->expectException(\RuntimeException::class);
+
+        $exporter->export($exportRequest);
+    }
+}