From 8e2e676e3dc5a41c0039eeebeb0996eaedae1157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 15 Sep 2025 11:11:40 +0200 Subject: [PATCH] Add ticket filtering "byTicketId" --- .../PersonIdentifierApiController.php | 14 ++ .../ChillTicketBundle/chill.api.specs.yaml | 12 ++ .../Controller/TicketListApiController.php | 8 + .../Repository/TicketACLAwareRepository.php | 7 + .../TicketACLAwareRepositoryInterface.php | 2 +- .../TicketListApiControllerByTicketIdTest.php | 147 ++++++++++++++++++ .../TicketACLAwareRepositoryTest.php | 7 + 7 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 src/Bundle/ChillPersonBundle/Controller/PersonIdentifierApiController.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByTicketIdTest.php diff --git a/src/Bundle/ChillPersonBundle/Controller/PersonIdentifierApiController.php b/src/Bundle/ChillPersonBundle/Controller/PersonIdentifierApiController.php new file mode 100644 index 000000000..200d5c224 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Controller/PersonIdentifierApiController.php @@ -0,0 +1,14 @@ + + The id of the ticket. + + When the id of the ticket is set, the other parameters are ignored. + required: false + style: form + explode: false + schema: + type: integer + minimum: 0 - name: byPerson in: query description: the id of the person diff --git a/src/Bundle/ChillTicketBundle/src/Controller/TicketListApiController.php b/src/Bundle/ChillTicketBundle/src/Controller/TicketListApiController.php index 74d52ee01..fd1db6e22 100644 --- a/src/Bundle/ChillTicketBundle/src/Controller/TicketListApiController.php +++ b/src/Bundle/ChillTicketBundle/src/Controller/TicketListApiController.php @@ -59,6 +59,14 @@ final readonly class TicketListApiController $params = []; + if ($request->query->has('byTicketId')) { + $params['byTicketId'] = $request->query->getInt('byTicketId', -1); + + if (-1 === $params['byTicketId']) { + throw new BadRequestHttpException("The parameter 'byTicketId' is not an integer."); + } + } + if ($request->query->has('byPerson')) { $personIds = explode(',', $request->query->get('byPerson')); foreach ($personIds as $id) { diff --git a/src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepository.php b/src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepository.php index 3167757e6..222199666 100644 --- a/src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepository.php +++ b/src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepository.php @@ -53,6 +53,13 @@ final readonly class TicketACLAwareRepository implements TicketACLAwareRepositor // counter for all the loops $i = 0; + if (array_key_exists('byTicketId', $params)) { + $qb->andWhere($qb->expr()->in('t.id', ':byTicketId')); + $qb->setParameter('byTicketId', $params['byTicketId']); + + return $qb; + } + if (array_key_exists('byPerson', $params)) { $or = $qb->expr()->orX(); diff --git a/src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepositoryInterface.php b/src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepositoryInterface.php index 952c4166c..646ca3403 100644 --- a/src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepositoryInterface.php @@ -22,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, byAddresseeGroup?: list, byCreator?: list} + * @phpstan-type TicketACLAwareRepositoryParam array{byPerson?: list, byCurrentState?: list, byCurrentStateEmergency?: list, byMotives?: list, byCreatedBefore?: \DateTimeImmutable, byCreatedAfter?: \DateTimeImmutable, byAddressee?: list, byAddresseeGroup?: list, byCreator?: list, byTicketId?: int} */ interface TicketACLAwareRepositoryInterface { diff --git a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByTicketIdTest.php b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByTicketIdTest.php new file mode 100644 index 000000000..f3fb1c1eb --- /dev/null +++ b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByTicketIdTest.php @@ -0,0 +1,147 @@ +prophesize(Security::class); + $security->isGranted('ROLE_USER')->willReturn(true); + + $ticketRepository = $this->prophesize(TicketACLAwareRepositoryInterface::class); + $tickets = [new Ticket(), new Ticket()]; + $ticketRepository->countTickets( + Argument::that(fn ($params) => isset($params['byTicketId']) && 5 === $params['byTicketId']) + ) + ->shouldBeCalled() + ->willReturn(2); + $ticketRepository->findTickets( + Argument::that(fn ($params) => isset($params['byTicketId']) && 5 === $params['byTicketId']), + 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); + + // Needed for controller constructor but not used here + $userRepository = $this->prophesize(UserRepositoryInterface::class); + $userGroupRepository = $this->prophesize(UserGroupRepositoryInterface::class); + + // Create controller + $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(), + $userGroupRepository->reveal() + ); + + // Create request with byTicketId filter + $request = new Request( + query: ['byTicketId' => '5'] + ); + + // Call controller method + $response = $controller->listTicket($request); + + // Assert response + $this->assertInstanceOf(JsonResponse::class, $response); + $this->assertEquals('{"items":[{},{}],"pagination":{}}', $response->getContent()); + } + + public function testListTicketWithInvalidByTicketIdThrows(): void + { + $this->expectException(BadRequestHttpException::class); + + // Mock dependencies + $security = $this->prophesize(Security::class); + $security->isGranted('ROLE_USER')->willReturn(true); + + $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); + $userGroupRepository = $this->prophesize(UserGroupRepositoryInterface::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(), + $userGroupRepository->reveal() + ); + + // Use -1 to trigger the controller's validation error + $request = new Request(query: ['byTicketId' => '-1']); + + // This should throw BadRequestHttpException + $controller->listTicket($request); + } +} diff --git a/src/Bundle/ChillTicketBundle/tests/Repository/TicketACLAwareRepositoryTest.php b/src/Bundle/ChillTicketBundle/tests/Repository/TicketACLAwareRepositoryTest.php index 695c5d76d..3635d3dc3 100644 --- a/src/Bundle/ChillTicketBundle/tests/Repository/TicketACLAwareRepositoryTest.php +++ b/src/Bundle/ChillTicketBundle/tests/Repository/TicketACLAwareRepositoryTest.php @@ -185,4 +185,11 @@ class TicketACLAwareRepositoryTest extends KernelTestCase self::assertIsArray($actual); } + + public function testFindByTicketid(): void + { + $actual = $this->repository->findTickets(['byTicketId' => 1]); + + self::assertIsArray($actual); + } }