From 18f67801c7ff4d8a8f1671327288d84a1a0ea413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 8 Sep 2025 14:18:02 +0000 Subject: [PATCH] Fix bugs in api endpoint to filter tickets, and add parameters byAddresseeGroup and byCreator --- .junie/guidelines.md | 8 +- .../ChillTicketBundle/chill.api.specs.yaml | 26 ++- .../Controller/TicketListApiController.php | 31 +++ .../Repository/TicketACLAwareRepository.php | 35 ++- .../TicketACLAwareRepositoryInterface.php | 3 +- ...tListApiControllerByAddresseeGroupTest.php | 211 ++++++++++++++++++ ...TicketListApiControllerByAddresseeTest.php | 13 +- ...etListApiControllerByAddresseeToMeTest.php | 4 +- ...ketListApiControllerByCreatedAfterTest.php | 12 +- ...etListApiControllerByCreatedBeforeTest.php | 10 +- .../TicketListApiControllerByCreatorTest.php | 203 +++++++++++++++++ ...piControllerByResponseTimeExceededTest.php | 7 +- ...ApiControllerCurrentStateEmergencyTest.php | 7 +- ...icketListApiControllerCurrentStateTest.php | 7 +- .../TicketListApiControllerMotivesTest.php | 13 +- .../TicketListApiControllerTest.php | 13 +- .../TicketACLAwareRepositoryTest.php | 46 ++++ 17 files changed, 611 insertions(+), 38 deletions(-) create mode 100644 src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByAddresseeGroupTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByCreatorTest.php diff --git a/.junie/guidelines.md b/.junie/guidelines.md index 97a2be27d..0de3e87ad 100644 --- a/.junie/guidelines.md +++ b/.junie/guidelines.md @@ -234,15 +234,9 @@ This must be a decision made by a human, not by an AI. Every AI task must abort #### Running Tests -The tests are run from the project's root (not from the bundle's root). +The tests are run from the project's root (not from the bundle's root: so, do not change the directory to any bundle directory before running tests). ```bash -# Run all tests -vendor/bin/phpunit - -# Run tests for a specific bundle -vendor/bin/phpunit --testsuite NameBundle - # Run a specific test file vendor/bin/phpunit path/to/TestFile.php diff --git a/src/Bundle/ChillTicketBundle/chill.api.specs.yaml b/src/Bundle/ChillTicketBundle/chill.api.specs.yaml index 65aec91da..cbd4ab0ce 100644 --- a/src/Bundle/ChillTicketBundle/chill.api.specs.yaml +++ b/src/Bundle/ChillTicketBundle/chill.api.specs.yaml @@ -137,7 +137,7 @@ paths: type: string - name: byAddressee in: query - description: the id of the addressee to search for. The search is also performed against user groups. + description: "The id of the addressee to search for. The search is also performed against user groups: the api endpoint filter for ticket assigned to the groups of the user given as parameter." required: false style: form explode: false @@ -154,6 +154,30 @@ paths: allowEmptyValue: true schema: type: string + - name: byAddresseeGroup + in: query + description: the id of the addressee users group to search for. + required: false + style: form + explode: false + schema: + type: array + items: + type: integer + format: integer + minimum: 1 + - name: byCreator + in: query + description: The id of the creator to search for. + required: false + style: form + explode: false + schema: + type: array + items: + type: integer + format: integer + minimum: 1 responses: 200: description: OK diff --git a/src/Bundle/ChillTicketBundle/src/Controller/TicketListApiController.php b/src/Bundle/ChillTicketBundle/src/Controller/TicketListApiController.php index a181faf82..74d52ee01 100644 --- a/src/Bundle/ChillTicketBundle/src/Controller/TicketListApiController.php +++ b/src/Bundle/ChillTicketBundle/src/Controller/TicketListApiController.php @@ -13,6 +13,7 @@ namespace Chill\TicketBundle\Controller; use Chill\MainBundle\Pagination\PaginatorFactoryInterface; use Chill\MainBundle\Repository\UserRepositoryInterface; +use Chill\MainBundle\Repository\UserGroupRepositoryInterface; use Chill\MainBundle\Serializer\Model\Collection; use Chill\PersonBundle\Repository\PersonRepository; use Chill\PersonBundle\Security\Authorization\PersonVoter; @@ -44,6 +45,7 @@ final readonly class TicketListApiController private ClockInterface $clock, ParameterBagInterface $parameterBag, private UserRepositoryInterface $userRepository, + private UserGroupRepositoryInterface $userGroupRepository, ) { $this->expectedTimeResponseDelay = new \DateInterval($parameterBag->get('chill_ticket')['ticket']['response_time_exceeded_delay']); } @@ -152,6 +154,35 @@ final readonly class TicketListApiController } } + if ($request->query->has('byCreator')) { + $userIds = explode(',', $request->query->get('byCreator')); + foreach ($userIds as $id) { + if (!is_numeric($id) || 0 === ((int) $id)) { + throw new BadRequestHttpException('Only numbers are allowed in by creator parameter'); + } + + $params['byCreator'][] = $user = $this->userRepository->find($id); + if (null === $user) { + throw new BadRequestHttpException('User not found'); + } + } + } + + if ($request->query->has('byAddresseeGroup')) { + $groupIds = explode(',', $request->query->get('byAddresseeGroup')); + foreach ($groupIds as $id) { + if (!is_numeric($id) || 0 === ((int) $id)) { + throw new BadRequestHttpException('Only numbers are allowed in by addressee group parameter'); + } + + $group = $this->userGroupRepository->find($id); + if (null === $group) { + throw new BadRequestHttpException('User group not found'); + } + $params['byAddresseeGroup'][] = $group; + } + } + $nb = $this->ticketRepository->countTickets($params); $paginator = $this->paginatorFactory->create($nb); diff --git a/src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepository.php b/src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepository.php index eb2da9b6e..3167757e6 100644 --- a/src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepository.php +++ b/src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepository.php @@ -104,8 +104,9 @@ final readonly class TicketACLAwareRepository implements TicketACLAwareRepositor } if (array_key_exists('byMotives', $params)) { + $byMotives = $qb->expr()->orX(); foreach ($params['byMotives'] as $motive) { - $qb->andWhere( + $byMotives->add( $qb->expr()->exists(sprintf( 'SELECT 1 FROM %s tp_motive_%d WHERE tp_motive_%d.ticket = t AND tp_motive_%d.motive = :motive_%d AND tp_motive_%d.endDate IS NULL @@ -120,6 +121,7 @@ final readonly class TicketACLAwareRepository implements TicketACLAwareRepositor ); $qb->setParameter(sprintf('motive_%d', $i), $motive); } + $qb->andWhere($byMotives); } if (array_key_exists('byCreatedAfter', $params)) { @@ -128,8 +130,13 @@ final readonly class TicketACLAwareRepository implements TicketACLAwareRepositor } if (array_key_exists('byCreatedBefore', $params)) { - $qb->andWhere('t.createdAt <= :opening_after'); - $qb->setParameter('opening_after', $params['byCreatedBefore']); + $qb->andWhere('t.createdAt <= :opening_before'); + $qb->setParameter('opening_before', $params['byCreatedBefore']); + } + + if (array_key_exists('byCreator', $params)) { + $qb->andWhere('t.createdBy IN (:creators)'); + $qb->setParameter('creators', $params['byCreator']); } if (array_key_exists('byAddressee', $params)) { @@ -166,6 +173,28 @@ final readonly class TicketACLAwareRepository implements TicketACLAwareRepositor $qb->andWhere($orx); } + if (array_key_exists('byAddresseeGroup', $params)) { + $addresseeGroupOr = $qb->expr()->orX(); + foreach ($params['byAddresseeGroup'] as $addresseeGroup) { + $addresseeGroupOr->add( + $qb->expr()->exists(sprintf( + 'SELECT 1 FROM %s tp_addressee_%d JOIN tp_addressee_%d.addresseeGroup group_%d WHERE tp_addressee_%d.ticket = t + AND tp_addressee_%d.endDate IS NULL AND tp_addressee_%d.addresseeGroup = :addressee_%d', + AddresseeHistory::class, + ++$i, + $i, + $i, + $i, + $i, + $i, + $i, + )) + ); + $qb->setParameter(sprintf('addressee_%d', $i), $addresseeGroup); + } + $qb->andWhere($addresseeGroupOr); + } + return $qb; } } diff --git a/src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepositoryInterface.php b/src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepositoryInterface.php index 59527fb73..952c4166c 100644 --- a/src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepositoryInterface.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\TicketBundle\Repository; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Entity\UserGroup; use Chill\PersonBundle\Entity\Person; use Chill\TicketBundle\Entity\EmergencyStatusEnum; use Chill\TicketBundle\Entity\Motive; @@ -21,7 +22,7 @@ use Chill\TicketBundle\Entity\Ticket; /** * Repository to find tickets, taking care of permissions. * - * @phpstan-type TicketACLAwareRepositoryParam array{byPerson?: list, byCurrentState?: list, byCurrentStateEmergency?: list, byMotives?: list, byCreatedBefore?: \DateTimeImmutable, byCreatedAfter?: \DateTimeImmutable, byAddressee?: list} + * @phpstan-type TicketACLAwareRepositoryParam array{byPerson?: list, byCurrentState?: list, byCurrentStateEmergency?: list, byMotives?: list, byCreatedBefore?: \DateTimeImmutable, byCreatedAfter?: \DateTimeImmutable, byAddressee?: list, byAddresseeGroup?: list, byCreator?: list} */ interface TicketACLAwareRepositoryInterface { diff --git a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByAddresseeGroupTest.php b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByAddresseeGroupTest.php new file mode 100644 index 000000000..94c1bf705 --- /dev/null +++ b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByAddresseeGroupTest.php @@ -0,0 +1,211 @@ +prophesize(Security::class); + $security->isGranted('ROLE_USER')->willReturn(true); + + $group = new UserGroup(); + + $groupRepository = $this->prophesize(UserGroupRepositoryInterface::class); + $groupRepository->find(10)->willReturn($group); + + $ticketRepository = $this->prophesize(TicketACLAwareRepositoryInterface::class); + $tickets = [new Ticket(), new Ticket()]; + $ticketRepository->countTickets( + Argument::that(fn ($params) => isset($params['byAddresseeGroup']) && in_array($group, $params['byAddresseeGroup'], true)) + )->shouldBeCalled()->willReturn(2); + $ticketRepository->findTickets( + Argument::that(fn ($params) => isset($params['byAddresseeGroup']) && in_array($group, $params['byAddresseeGroup'], true)), + 0, + 10 + )->shouldBeCalled()->willReturn($tickets); + + $paginator = $this->prophesize(PaginatorInterface::class); + $paginator->getCurrentPageFirstItemNumber()->willReturn(0); + $paginator->getItemsPerPage()->willReturn(10); + + $paginatorFactory = $this->prophesize(PaginatorFactoryInterface::class); + $paginatorFactory->create(2)->willReturn($paginator->reveal()); + + $serializer = $this->prophesize(SerializerInterface::class); + $serializer->serialize( + Argument::that(fn (Collection $collection) => $collection->getItems() === $tickets), + 'json', + ['groups' => 'read:simple'] + )->willReturn('{"items":[{},{}],"pagination":{}}'); + + $personRepository = $this->prophesize(PersonRepository::class); + $motiveRepository = $this->prophesize(MotiveRepository::class); + $userRepository = $this->prophesize(UserRepositoryInterface::class); + + $controller = new TicketListApiController( + $security->reveal(), + $ticketRepository->reveal(), + $paginatorFactory->reveal(), + $serializer->reveal(), + $personRepository->reveal(), + $motiveRepository->reveal(), + new MockClock(), + new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), + $userRepository->reveal(), + $groupRepository->reveal(), + ); + + $request = new Request(query: ['byAddresseeGroup' => '10']); + + $response = $controller->listTicket($request); + + self::assertInstanceOf(JsonResponse::class, $response); + self::assertSame('{"items":[{},{}],"pagination":{}}', $response->getContent()); + } + + public function testListTicketWithMultipleByAddresseeGroupFilter(): void + { + $security = $this->prophesize(Security::class); + $security->isGranted('ROLE_USER')->willReturn(true); + + $group1 = new UserGroup(); + $group2 = new UserGroup(); + + $groupRepository = $this->prophesize(UserGroupRepositoryInterface::class); + $groupRepository->find(10)->willReturn($group1); + $groupRepository->find(20)->willReturn($group2); + + $ticketRepository = $this->prophesize(TicketACLAwareRepositoryInterface::class); + $tickets = [new Ticket(), new Ticket()]; + $ticketRepository->countTickets( + Argument::that(fn ($params) => isset($params['byAddresseeGroup']) + && in_array($group1, $params['byAddresseeGroup'], true) + && in_array($group2, $params['byAddresseeGroup'], true)) + )->shouldBeCalled()->willReturn(2); + $ticketRepository->findTickets( + Argument::that(fn ($params) => isset($params['byAddresseeGroup']) + && in_array($group1, $params['byAddresseeGroup'], true) + && in_array($group2, $params['byAddresseeGroup'], true)), + 0, + 10 + )->shouldBeCalled()->willReturn($tickets); + + $paginator = $this->prophesize(PaginatorInterface::class); + $paginator->getCurrentPageFirstItemNumber()->willReturn(0); + $paginator->getItemsPerPage()->willReturn(10); + + $paginatorFactory = $this->prophesize(PaginatorFactoryInterface::class); + $paginatorFactory->create(2)->willReturn($paginator->reveal()); + + $serializer = $this->prophesize(SerializerInterface::class); + $serializer->serialize( + Argument::that(fn (Collection $collection) => $collection->getItems() === $tickets), + 'json', + ['groups' => 'read:simple'] + )->willReturn('{"items":[{},{}],"pagination":{}}'); + + $personRepository = $this->prophesize(PersonRepository::class); + $motiveRepository = $this->prophesize(MotiveRepository::class); + $userRepository = $this->prophesize(UserRepositoryInterface::class); + + $controller = new TicketListApiController( + $security->reveal(), + $ticketRepository->reveal(), + $paginatorFactory->reveal(), + $serializer->reveal(), + $personRepository->reveal(), + $motiveRepository->reveal(), + new MockClock(), + new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), + $userRepository->reveal(), + $groupRepository->reveal(), + ); + + $request = new Request(query: ['byAddresseeGroup' => '10,20']); + + $response = $controller->listTicket($request); + + self::assertInstanceOf(JsonResponse::class, $response); + self::assertSame('{"items":[{},{}],"pagination":{}}', $response->getContent()); + } + + public function testListTicketWithByAddresseeGroupFilterGroupNotFound(): void + { + self::expectException(BadRequestHttpException::class); + self::expectExceptionMessage('User group not found'); + + $security = $this->prophesize(Security::class); + $security->isGranted('ROLE_USER')->willReturn(true); + + $group1 = new UserGroup(); + + $groupRepository = $this->prophesize(UserGroupRepositoryInterface::class); + $groupRepository->find(10)->willReturn($group1); + $groupRepository->find(20)->willReturn(null); // not found + + $ticketRepository = $this->prophesize(TicketACLAwareRepositoryInterface::class); + $paginatorFactory = $this->prophesize(PaginatorFactoryInterface::class); + $serializer = $this->prophesize(SerializerInterface::class); + $personRepository = $this->prophesize(PersonRepository::class); + $motiveRepository = $this->prophesize(MotiveRepository::class); + $userRepository = $this->prophesize(UserRepositoryInterface::class); + + $controller = new TicketListApiController( + $security->reveal(), + $ticketRepository->reveal(), + $paginatorFactory->reveal(), + $serializer->reveal(), + $personRepository->reveal(), + $motiveRepository->reveal(), + new MockClock(), + new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), + $userRepository->reveal(), + $groupRepository->reveal(), + ); + + $request = new Request(query: ['byAddresseeGroup' => '10,20']); + + // should throw + $controller->listTicket($request); + } +} diff --git a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByAddresseeTest.php b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByAddresseeTest.php index 0508bdfa6..3a4099fae 100644 --- a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByAddresseeTest.php +++ b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByAddresseeTest.php @@ -15,6 +15,7 @@ use Chill\MainBundle\Entity\User; use Chill\MainBundle\Pagination\PaginatorFactoryInterface; use Chill\MainBundle\Pagination\PaginatorInterface; use Chill\MainBundle\Repository\UserRepositoryInterface; +use Chill\MainBundle\Repository\UserGroupRepositoryInterface; use Chill\MainBundle\Serializer\Model\Collection; use Chill\PersonBundle\Repository\PersonRepository; use Chill\TicketBundle\Controller\TicketListApiController; @@ -92,7 +93,8 @@ final class TicketListApiControllerByAddresseeTest extends TestCase $motiveRepository->reveal(), new MockClock(), new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request with byAddressee filter @@ -165,7 +167,8 @@ final class TicketListApiControllerByAddresseeTest extends TestCase $motiveRepository->reveal(), new MockClock(), new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request with multiple byAddressee filter @@ -212,7 +215,8 @@ final class TicketListApiControllerByAddresseeTest extends TestCase $motiveRepository->reveal(), new MockClock(), new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request with byAddressee filter with non-existent user @@ -281,7 +285,8 @@ final class TicketListApiControllerByAddresseeTest extends TestCase $motiveRepository->reveal(), new MockClock(), new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request with both byAddressee and byAddresseeToMe filters diff --git a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByAddresseeToMeTest.php b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByAddresseeToMeTest.php index f9d5738e7..ebc984e7e 100644 --- a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByAddresseeToMeTest.php +++ b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByAddresseeToMeTest.php @@ -13,6 +13,7 @@ namespace Chill\TicketBundle\Tests\Controller; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Pagination\PaginatorFactoryInterface; +use Chill\MainBundle\Repository\UserGroupRepositoryInterface; use Chill\MainBundle\Pagination\PaginatorInterface; use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Serializer\Model\Collection; @@ -90,7 +91,8 @@ final class TicketListApiControllerByAddresseeToMeTest extends TestCase $motiveRepository->reveal(), new MockClock(), new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request with byAddresseeToMe filter diff --git a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByCreatedAfterTest.php b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByCreatedAfterTest.php index 72eabd43d..c11de568d 100644 --- a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByCreatedAfterTest.php +++ b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByCreatedAfterTest.php @@ -14,6 +14,7 @@ namespace Chill\TicketBundle\Tests\Controller; use Chill\MainBundle\Pagination\PaginatorFactoryInterface; use Chill\MainBundle\Pagination\PaginatorInterface; use Chill\MainBundle\Repository\UserRepositoryInterface; +use Chill\MainBundle\Repository\UserGroupRepositoryInterface; use Chill\MainBundle\Serializer\Model\Collection; use Chill\PersonBundle\Repository\PersonRepository; use Chill\TicketBundle\Controller\TicketListApiController; @@ -50,7 +51,7 @@ final class TicketListApiControllerByCreatedAfterTest extends TestCase $tickets = [new Ticket(), new Ticket()]; // The date in RFC3339 format - $dateString = '2025-05-15T15:05:00Z'; + $dateString = '2025-05-15T15:05:00+00:00'; $date = \DateTimeImmutable::createFromFormat(\DateTimeImmutable::RFC3339, $dateString); $ticketRepository->countTickets( @@ -97,7 +98,8 @@ final class TicketListApiControllerByCreatedAfterTest extends TestCase $motiveRepository->reveal(), new MockClock(), new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request with byCreatedAfter filter in RFC3339 format @@ -170,7 +172,8 @@ final class TicketListApiControllerByCreatedAfterTest extends TestCase $motiveRepository->reveal(), new MockClock(), new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request with byCreatedAfter filter in RFC3339_EXTENDED format @@ -212,7 +215,8 @@ final class TicketListApiControllerByCreatedAfterTest extends TestCase $motiveRepository->reveal(), new MockClock(), new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request with byCreatedAfter filter in invalid format diff --git a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByCreatedBeforeTest.php b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByCreatedBeforeTest.php index 55fa1911d..da0f16e47 100644 --- a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByCreatedBeforeTest.php +++ b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByCreatedBeforeTest.php @@ -13,6 +13,7 @@ namespace Chill\TicketBundle\Tests\Controller; use Chill\MainBundle\Pagination\PaginatorFactoryInterface; use Chill\MainBundle\Pagination\PaginatorInterface; +use Chill\MainBundle\Repository\UserGroupRepositoryInterface; use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Serializer\Model\Collection; use Chill\PersonBundle\Repository\PersonRepository; @@ -97,7 +98,8 @@ final class TicketListApiControllerByCreatedBeforeTest extends TestCase $motiveRepository->reveal(), new MockClock(), new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request with byCreatedAfter filter in RFC3339 format @@ -170,7 +172,8 @@ final class TicketListApiControllerByCreatedBeforeTest extends TestCase $motiveRepository->reveal(), new MockClock(), new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request with byCreatedAfter filter in RFC3339_EXTENDED format @@ -212,7 +215,8 @@ final class TicketListApiControllerByCreatedBeforeTest extends TestCase $motiveRepository->reveal(), new MockClock(), new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request with byCreatedAfter filter in invalid format diff --git a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByCreatorTest.php b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByCreatorTest.php new file mode 100644 index 000000000..a8828f545 --- /dev/null +++ b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByCreatorTest.php @@ -0,0 +1,203 @@ +prophesize(Security::class); + $security->isGranted('ROLE_USER')->willReturn(true); + + $user = new User(); + + $userRepository = $this->prophesize(UserRepositoryInterface::class); + $userRepository->find(1)->willReturn($user); + + $ticketRepository = $this->prophesize(TicketACLAwareRepositoryInterface::class); + $tickets = [new Ticket(), new Ticket()]; + $ticketRepository->countTickets( + Argument::that(fn ($params) => isset($params['byCreator']) && in_array($user, $params['byCreator'], true)) + )->shouldBeCalled()->willReturn(2); + $ticketRepository->findTickets( + Argument::that(fn ($params) => isset($params['byCreator']) && in_array($user, $params['byCreator'], true)), + 0, + 10 + )->shouldBeCalled()->willReturn($tickets); + + $paginator = $this->prophesize(PaginatorInterface::class); + $paginator->getCurrentPageFirstItemNumber()->willReturn(0); + $paginator->getItemsPerPage()->willReturn(10); + + $paginatorFactory = $this->prophesize(PaginatorFactoryInterface::class); + $paginatorFactory->create(2)->willReturn($paginator->reveal()); + + $serializer = $this->prophesize(SerializerInterface::class); + $serializer->serialize( + Argument::that(fn (Collection $collection) => $collection->getItems() === $tickets), + 'json', + ['groups' => 'read:simple'] + )->willReturn('{"items":[{},{}],"pagination":{}}'); + + $personRepository = $this->prophesize(PersonRepository::class); + $motiveRepository = $this->prophesize(MotiveRepository::class); + + $controller = new TicketListApiController( + $security->reveal(), + $ticketRepository->reveal(), + $paginatorFactory->reveal(), + $serializer->reveal(), + $personRepository->reveal(), + $motiveRepository->reveal(), + new MockClock(), + new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() + ); + + $request = new Request(query: ['byCreator' => '1']); + + $response = $controller->listTicket($request); + + $this->assertInstanceOf(JsonResponse::class, $response); + $this->assertEquals('{"items":[{},{}],"pagination":{}}', $response->getContent()); + } + + public function testListTicketWithMultipleByCreatorFilter(): void + { + $security = $this->prophesize(Security::class); + $security->isGranted('ROLE_USER')->willReturn(true); + + $user1 = new User(); + $user2 = new User(); + + $userRepository = $this->prophesize(UserRepositoryInterface::class); + $userRepository->find(1)->willReturn($user1); + $userRepository->find(2)->willReturn($user2); + + $ticketRepository = $this->prophesize(TicketACLAwareRepositoryInterface::class); + $tickets = [new Ticket()]; + $ticketRepository->countTickets( + Argument::that(fn ($params) => isset($params['byCreator']) && in_array($user1, $params['byCreator'], true) && in_array($user2, $params['byCreator'], true)) + )->willReturn(1); + $ticketRepository->findTickets( + Argument::that(fn ($params) => isset($params['byCreator']) && in_array($user1, $params['byCreator'], true) && in_array($user2, $params['byCreator'], true)), + 0, + 10 + )->willReturn($tickets); + + $paginator = $this->prophesize(PaginatorInterface::class); + $paginator->getCurrentPageFirstItemNumber()->willReturn(0); + $paginator->getItemsPerPage()->willReturn(10); + + $paginatorFactory = $this->prophesize(PaginatorFactoryInterface::class); + $paginatorFactory->create(1)->willReturn($paginator->reveal()); + + $serializer = $this->prophesize(SerializerInterface::class); + $serializer->serialize( + Argument::that(fn (Collection $collection) => $collection->getItems() === $tickets), + 'json', + ['groups' => 'read:simple'] + )->willReturn('{"items":[{}],"pagination":{}}'); + + $personRepository = $this->prophesize(PersonRepository::class); + $motiveRepository = $this->prophesize(MotiveRepository::class); + + $controller = new TicketListApiController( + $security->reveal(), + $ticketRepository->reveal(), + $paginatorFactory->reveal(), + $serializer->reveal(), + $personRepository->reveal(), + $motiveRepository->reveal(), + new MockClock(), + new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() + ); + + $request = new Request(query: ['byCreator' => '1,2']); + $response = $controller->listTicket($request); + + $this->assertInstanceOf(JsonResponse::class, $response); + $this->assertEquals('{"items":[{}],"pagination":{}}', $response->getContent()); + } + + public function testListTicketWithByCreatorFilterUserNotFound(): void + { + $this->expectException(BadRequestHttpException::class); + + $security = $this->prophesize(Security::class); + $security->isGranted('ROLE_USER')->willReturn(true); + + $userRepository = $this->prophesize(UserRepositoryInterface::class); + $userRepository->find(99)->willReturn(null); + + $ticketRepository = $this->prophesize(TicketACLAwareRepositoryInterface::class); + + $paginator = $this->prophesize(PaginatorInterface::class); + $paginator->getCurrentPageFirstItemNumber()->willReturn(0); + $paginator->getItemsPerPage()->willReturn(10); + + $paginatorFactory = $this->prophesize(PaginatorFactoryInterface::class); + $paginatorFactory->create(0)->willReturn($paginator->reveal()); + + $serializer = $this->prophesize(SerializerInterface::class); + $personRepository = $this->prophesize(PersonRepository::class); + $motiveRepository = $this->prophesize(MotiveRepository::class); + + $controller = new TicketListApiController( + $security->reveal(), + $ticketRepository->reveal(), + $paginatorFactory->reveal(), + $serializer->reveal(), + $personRepository->reveal(), + $motiveRepository->reveal(), + new MockClock(), + new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() + ); + + $request = new Request(query: ['byCreator' => '99']); + $controller->listTicket($request); + } +} diff --git a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByResponseTimeExceededTest.php b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByResponseTimeExceededTest.php index 9c5f386e2..54fae993b 100644 --- a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByResponseTimeExceededTest.php +++ b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByResponseTimeExceededTest.php @@ -13,6 +13,7 @@ namespace Chill\TicketBundle\Tests\Controller; use Chill\MainBundle\Pagination\PaginatorFactoryInterface; use Chill\MainBundle\Pagination\PaginatorInterface; +use Chill\MainBundle\Repository\UserGroupRepositoryInterface; use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Serializer\Model\Collection; use Chill\PersonBundle\Repository\PersonRepository; @@ -107,7 +108,8 @@ final class TicketListApiControllerByResponseTimeExceededTest extends TestCase $motiveRepository->reveal(), $mockClock, new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request with byResponseTimeExceeded parameter only @@ -191,7 +193,8 @@ final class TicketListApiControllerByResponseTimeExceededTest extends TestCase $motiveRepository->reveal(), $mockClock, new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request with both byCreatedAfter and byResponseTimeExceeded parameters diff --git a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerCurrentStateEmergencyTest.php b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerCurrentStateEmergencyTest.php index 8f39db1ff..ea4636ffe 100644 --- a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerCurrentStateEmergencyTest.php +++ b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerCurrentStateEmergencyTest.php @@ -13,6 +13,7 @@ namespace Chill\TicketBundle\Tests\Controller; use Chill\MainBundle\Pagination\PaginatorFactoryInterface; use Chill\MainBundle\Pagination\PaginatorInterface; +use Chill\MainBundle\Repository\UserGroupRepositoryInterface; use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Serializer\Model\Collection; use Chill\PersonBundle\Repository\PersonRepository; @@ -87,7 +88,8 @@ final class TicketListApiControllerCurrentStateEmergencyTest extends TestCase $motiveRepository->reveal(), new MockClock(), new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request with emergency filter @@ -128,7 +130,8 @@ final class TicketListApiControllerCurrentStateEmergencyTest extends TestCase $motiveRepository->reveal(), new MockClock(), new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request with invalid emergency filter diff --git a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerCurrentStateTest.php b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerCurrentStateTest.php index f54258af2..15c8bcf51 100644 --- a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerCurrentStateTest.php +++ b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerCurrentStateTest.php @@ -13,6 +13,7 @@ namespace Chill\TicketBundle\Tests\Controller; use Chill\MainBundle\Pagination\PaginatorFactoryInterface; use Chill\MainBundle\Pagination\PaginatorInterface; +use Chill\MainBundle\Repository\UserGroupRepositoryInterface; use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Serializer\Model\Collection; use Chill\PersonBundle\Repository\PersonRepository; @@ -87,7 +88,8 @@ final class TicketListApiControllerCurrentStateTest extends TestCase $motiveRepository->reveal(), new MockClock(), new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request with person filter @@ -128,7 +130,8 @@ final class TicketListApiControllerCurrentStateTest extends TestCase $motiveRepository->reveal(), new MockClock(), new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request with person filter diff --git a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerMotivesTest.php b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerMotivesTest.php index 9369ab7a7..858a352b3 100644 --- a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerMotivesTest.php +++ b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerMotivesTest.php @@ -13,6 +13,7 @@ namespace Chill\TicketBundle\Tests\Controller; use Chill\MainBundle\Pagination\PaginatorFactoryInterface; use Chill\MainBundle\Pagination\PaginatorInterface; +use Chill\MainBundle\Repository\UserGroupRepositoryInterface; use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Serializer\Model\Collection; use Chill\PersonBundle\Repository\PersonRepository; @@ -92,7 +93,8 @@ final class TicketListApiControllerMotivesTest extends TestCase $motiveRepository->reveal(), new MockClock(), new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request with multiple motives filter @@ -157,7 +159,8 @@ final class TicketListApiControllerMotivesTest extends TestCase $motiveRepository->reveal(), new MockClock(), new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request with single motive filter @@ -200,7 +203,8 @@ final class TicketListApiControllerMotivesTest extends TestCase $motiveRepository->reveal(), new MockClock(), new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request with non-existent motive @@ -238,7 +242,8 @@ final class TicketListApiControllerMotivesTest extends TestCase $motiveRepository->reveal(), new MockClock(), new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request with non-integer motive parameter diff --git a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerTest.php b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerTest.php index 76e5e6998..d9b8dd697 100644 --- a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerTest.php +++ b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerTest.php @@ -14,6 +14,7 @@ namespace Chill\TicketBundle\Tests\Controller; use Chill\MainBundle\Pagination\PaginatorFactoryInterface; use Chill\MainBundle\Pagination\PaginatorInterface; use Chill\MainBundle\Repository\UserRepositoryInterface; +use Chill\MainBundle\Repository\UserGroupRepositoryInterface; use Chill\MainBundle\Serializer\Model\Collection; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Repository\PersonRepository; @@ -81,7 +82,8 @@ final class TicketListApiControllerTest extends TestCase $motiveRepository->reveal(), new MockClock(), new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request @@ -142,7 +144,8 @@ final class TicketListApiControllerTest extends TestCase $motiveRepository->reveal(), new MockClock(), new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request with person filter @@ -181,7 +184,8 @@ final class TicketListApiControllerTest extends TestCase $motiveRepository->reveal(), new MockClock(), new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request @@ -223,7 +227,8 @@ final class TicketListApiControllerTest extends TestCase $motiveRepository->reveal(), new MockClock(), new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]), - $userRepository->reveal() + $userRepository->reveal(), + $this->prophesize(UserGroupRepositoryInterface::class)->reveal() ); // Create request with person filter diff --git a/src/Bundle/ChillTicketBundle/tests/Repository/TicketACLAwareRepositoryTest.php b/src/Bundle/ChillTicketBundle/tests/Repository/TicketACLAwareRepositoryTest.php index ef81f5058..695c5d76d 100644 --- a/src/Bundle/ChillTicketBundle/tests/Repository/TicketACLAwareRepositoryTest.php +++ b/src/Bundle/ChillTicketBundle/tests/Repository/TicketACLAwareRepositoryTest.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\TicketBundle\Tests\Repository; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Entity\UserGroup; use Chill\PersonBundle\DataFixtures\Helper\RandomPersonHelperTrait; use Chill\TicketBundle\Entity\EmergencyStatusEnum; use Chill\TicketBundle\Entity\Motive; @@ -139,4 +140,49 @@ class TicketACLAwareRepositoryTest extends KernelTestCase self::assertIsArray($actual); } + + public function testFindByCreator(): void + { + $users = $this->entityManager->createQuery('SELECT u FROM '.User::class.' u') + ->setMaxResults(2) + ->getResult(); + + if ([] === $users) { + throw new \UnexpectedValueException('No users found'); + } + + $actual = $this->repository->findTickets(['byCreator' => $users]); + + self::assertIsArray($actual); + } + + public function testCountByCreator(): void + { + $users = $this->entityManager->createQuery('SELECT u FROM '.User::class.' u') + ->setMaxResults(2) + ->getResult(); + + if ([] === $users) { + throw new \UnexpectedValueException('No users found'); + } + + $count = $this->repository->countTickets(['byCreator' => $users]); + + self::assertIsInt($count); + } + + public function testFindByAddresseeGroup(): void + { + $userGroups = $this->entityManager->createQuery('SELECT ug FROM '.UserGroup::class.' ug') + ->setMaxResults(2) + ->getResult(); + + if ([] === $userGroups) { + throw new \UnexpectedValueException('No users found'); + } + + $actual = $this->repository->findTickets(['byCreator' => $userGroups]); + + self::assertIsArray($actual); + } }