From faed443a9649a470505fce9b36263d1efcad843e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 16 Jul 2025 13:39:27 +0000 Subject: [PATCH] =?UTF-8?q?Ticket:=20ajout=20de=20param=C3=A8tres=20=C3=A0?= =?UTF-8?q?=20la=20requ=C3=AAte=20de=20liste=20de=20tickets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/packages/chill_ticket.yaml | 1 + .../ChillTicketBundle/chill.api.specs.yaml | 50 ++++ .../Controller/TicketListApiController.php | 68 ++++- .../src/DependencyInjection/Configuration.php | 18 ++ .../src/Entity/EmergencyStatusEnum.php | 9 + .../Repository/TicketACLAwareRepository.php | 61 ++++- .../TicketACLAwareRepositoryInterface.php | 8 +- ...ketListApiControllerByCreatedAfterTest.php | 219 ++++++++++++++++ ...etListApiControllerByCreatedBeforeTest.php | 219 ++++++++++++++++ ...piControllerByResponseTimeExceededTest.php | 207 +++++++++++++++ ...ApiControllerCurrentStateEmergencyTest.php | 137 ++++++++++ ...icketListApiControllerCurrentStateTest.php | 137 ++++++++++ .../TicketListApiControllerMotivesTest.php | 243 ++++++++++++++++++ .../TicketListApiControllerTest.php | 116 ++------- .../TicketACLAwareRepositoryTest.php | 47 +++- 15 files changed, 1441 insertions(+), 99 deletions(-) create mode 100644 src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByCreatedAfterTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByCreatedBeforeTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByResponseTimeExceededTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerCurrentStateEmergencyTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerCurrentStateTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerMotivesTest.php diff --git a/config/packages/chill_ticket.yaml b/config/packages/chill_ticket.yaml index acb5571dc..3706d961d 100644 --- a/config/packages/chill_ticket.yaml +++ b/config/packages/chill_ticket.yaml @@ -1,4 +1,5 @@ chill_ticket: ticket: person_per_ticket: one # One of "one"; "many" + response_time_exceeded_delay: PT12H diff --git a/src/Bundle/ChillTicketBundle/chill.api.specs.yaml b/src/Bundle/ChillTicketBundle/chill.api.specs.yaml index da3de8a88..199b056a2 100644 --- a/src/Bundle/ChillTicketBundle/chill.api.specs.yaml +++ b/src/Bundle/ChillTicketBundle/chill.api.specs.yaml @@ -85,6 +85,56 @@ paths: enum: - open - closed + - name: byCurrentStateEmergency + in: query + description: the current state emergency of the ticket + required: false + style: form + explode: false + schema: + type: array + items: + type: string + enum: + - yes + - no + - name: byMotives + in: query + description: the motives of the ticket + required: false + style: form + explode: false + schema: + type: array + items: + type: integer + format: integer + minimum: 1 + - name: byCreatedBefore + in: query + description: "Filter by the creation date for the ticket: only tickets created before the given date." + required: false + schema: + type: string + format: date-time + - name: byCreatedAfter + in: query + description: "Filter by the creation date for the ticket: only tickets crated after the given date." + required: false + schema: + type: string + format: date-time + - name: byResponseTimeExceeded + in: query + allowEmptyValue: true + description: | + Filter tickets that are not closed and have a response time exceeded (configuration parameter). + + The value of this parameter is ignored. + + **Warning**: This silently remove the filters "byCurrentState" and "byCreatedBefore". + schema: + type: string responses: 200: description: OK diff --git a/src/Bundle/ChillTicketBundle/src/Controller/TicketListApiController.php b/src/Bundle/ChillTicketBundle/src/Controller/TicketListApiController.php index 5e1250b2f..606967cbc 100644 --- a/src/Bundle/ChillTicketBundle/src/Controller/TicketListApiController.php +++ b/src/Bundle/ChillTicketBundle/src/Controller/TicketListApiController.php @@ -15,8 +15,12 @@ use Chill\MainBundle\Pagination\PaginatorFactoryInterface; use Chill\MainBundle\Serializer\Model\Collection; use Chill\PersonBundle\Repository\PersonRepository; use Chill\PersonBundle\Security\Authorization\PersonVoter; +use Chill\TicketBundle\Entity\EmergencyStatusEnum; use Chill\TicketBundle\Entity\StateEnum; +use Chill\TicketBundle\Repository\MotiveRepository; use Chill\TicketBundle\Repository\TicketACLAwareRepositoryInterface; +use Symfony\Component\Clock\ClockInterface; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; @@ -27,13 +31,20 @@ use Symfony\Component\Serializer\SerializerInterface; final readonly class TicketListApiController { + private \DateInterval $expectedTimeResponseDelay; + public function __construct( private Security $security, private TicketACLAwareRepositoryInterface $ticketRepository, private PaginatorFactoryInterface $paginatorFactory, private SerializerInterface $serializer, private PersonRepository $personRepository, - ) {} + private MotiveRepository $motiveRepository, + private ClockInterface $clock, + ParameterBagInterface $parameterBag, + ) { + $this->expectedTimeResponseDelay = new \DateInterval($parameterBag->get('chill_ticket')['ticket']['response_time_exceeded_delay']); + } #[Route('/api/1.0/ticket/ticket/list', name: 'chill_ticket_list_api', methods: ['GET'])] public function listTicket(Request $request): JsonResponse @@ -66,6 +77,61 @@ final readonly class TicketListApiController } } + if ($request->query->has('byCurrentStateEmergency')) { + try { + $params['byCurrentStateEmergency'] = array_map( + fn (string $state): EmergencyStatusEnum => EmergencyStatusEnum::fromValue($state), + explode(',', $request->query->get('byCurrentStateEmergency')) + ); + } catch (\InvalidArgumentException $e) { + throw new BadRequestHttpException($e->getMessage(), $e); + } + } + + if ($request->query->has('byMotives')) { + $params = explode(',', $request->query->get('byMotives')); + foreach ($params as $id) { + if (!is_numeric($id) || 0 === ((int) $id)) { + throw new BadRequestHttpException('Only numbers are allowed in by motives parameter'); + } + $params['byMotives'][] = $motive = $this->motiveRepository->find($id); + + if (null === $motive) { + throw new BadRequestHttpException('Motive not found'); + } + } + } + + if ($request->query->has('byCreatedBefore')) { + $params['byCreatedBefore'] = + \DateTimeImmutable::createFromFormat(\DateTimeImmutable::RFC3339, $request->query->get('byCreatedBefore')); + + if (false === $params['byCreatedBefore']) { + $params['byCreatedBefore'] = \DateTimeImmutable::createFromFormat(\DateTimeImmutable::RFC3339_EXTENDED, $request->query->get('byCreatedBefore')); + } + if (false === $params['byCreatedBefore']) { + throw new BadRequestHttpException('Invalid date for byCreatedBefore'); + } + } + + if ($request->query->has('byCreatedAfter')) { + $params['byCreatedAfter'] = + \DateTimeImmutable::createFromFormat(\DateTimeImmutable::RFC3339, $request->query->get('byCreatedAfter')); + if (false === $params['byCreatedAfter']) { + $params['byCreatedAfter'] = + \DateTimeImmutable::createFromFormat(\DateTimeImmutable::RFC3339_EXTENDED, $request->query->get('byCreatedAfter')); + } + if (false === $params['byCreatedAfter']) { + throw new BadRequestHttpException('Invalid date for byCreatedAfter'); + } + } + + if ($request->query->has('byResponseTimeExceeded')) { + $params['byCurrentState'] = [StateEnum::OPEN]; + $params['byCreatedBefore'] = $this->clock->now()->sub($this->expectedTimeResponseDelay); + unset($params['byCreatedAfter']); + } + $nb = $this->ticketRepository->countTickets($params); $paginator = $this->paginatorFactory->create($nb); diff --git a/src/Bundle/ChillTicketBundle/src/DependencyInjection/Configuration.php b/src/Bundle/ChillTicketBundle/src/DependencyInjection/Configuration.php index 19c107a09..fbea3d280 100644 --- a/src/Bundle/ChillTicketBundle/src/DependencyInjection/Configuration.php +++ b/src/Bundle/ChillTicketBundle/src/DependencyInjection/Configuration.php @@ -37,6 +37,24 @@ class Configuration implements ConfigurationInterface ->defaultValue('many') ->end(); + $ticketArray + ->children() + ->scalarNode('response_time_exceeded_delay') + ->info('The delay to declare a ticket as having an exceeded response time. Must be expressed as a valid parameter for instantiating a \DateInterval') + ->defaultValue('PT12H') + ->validate() + ->ifTrue(function (string $value) { + try { + new \DateInterval($value); + } catch (\Throwable) { + return true; + } + + return false; + }) + ->thenInvalid('The date interval provided for response_time_exceeded_delay (%s) is invalid') + ; + return $treeBuilder; } } diff --git a/src/Bundle/ChillTicketBundle/src/Entity/EmergencyStatusEnum.php b/src/Bundle/ChillTicketBundle/src/Entity/EmergencyStatusEnum.php index aa070fbf5..c9fbe6c63 100644 --- a/src/Bundle/ChillTicketBundle/src/Entity/EmergencyStatusEnum.php +++ b/src/Bundle/ChillTicketBundle/src/Entity/EmergencyStatusEnum.php @@ -18,4 +18,13 @@ enum EmergencyStatusEnum: string { case YES = 'yes'; case NO = 'no'; + + public static function fromValue(string $value): EmergencyStatusEnum + { + return match ($value) { + self::YES->value => self::YES, + self::NO->value => self::NO, + default => throw new \InvalidArgumentException(sprintf('Value "%s" is not valid', $value)), + }; + } } diff --git a/src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepository.php b/src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepository.php index 0592ea0c3..02122b9fe 100644 --- a/src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepository.php +++ b/src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepository.php @@ -11,6 +11,8 @@ declare(strict_types=1); namespace Chill\TicketBundle\Repository; +use Chill\TicketBundle\Entity\EmergencyStatusHistory; +use Chill\TicketBundle\Entity\MotiveHistory; use Chill\TicketBundle\Entity\PersonHistory; use Chill\TicketBundle\Entity\StateHistory; use Chill\TicketBundle\Entity\Ticket; @@ -23,8 +25,19 @@ final readonly class TicketACLAwareRepository implements TicketACLAwareRepositor public function findTickets(array $params, int $start = 0, int $limit = 100): array { - return $this->buildQuery($params)->select('t')->getQuery()->setFirstResult($start) - ->setMaxResults($limit)->getResult(); + $query = $this->buildQuery($params)->select('t'); + + // order by emergency. As the value can be 'yes' or 'no', we simply order by 'yes' first + $query->addSelect(sprintf("'SELECT u.emergencyStatus FROM %s u WHERE u.ticket = t AND u.endDate IS NULL' AS HIDDEN emergencyStatus", EmergencyStatusHistory::class)); + $query->addOrderBy('emergencyStatus', 'DESC'); + + // most recent tickets first + $query->addOrderBy('t.createdAt', 'DESC'); + + return $query->getQuery() + ->setFirstResult($start) + ->setMaxResults($limit) + ->getResult(); } public function countTickets(array $params): int @@ -74,6 +87,50 @@ final readonly class TicketACLAwareRepository implements TicketACLAwareRepositor $qb->setParameter('currentState', $params['byCurrentState']); } + if (array_key_exists('byCurrentStateEmergency', $params)) { + $qb->andWhere( + $qb->expr()->exists(sprintf( + 'SELECT 1 FROM %s tp_state_emergency_%d WHERE tp_state_emergency_%d.ticket = t + AND tp_state_emergency_%d.emergencyStatus IN (:currentStateEmergency) AND tp_state_emergency_%d.endDate IS NULL', + EmergencyStatusHistory::class, + ++$i, + $i, + $i, + $i, + )) + ); + $qb->setParameter('currentStateEmergency', $params['byCurrentStateEmergency']); + } + + if (array_key_exists('byMotives', $params)) { + foreach ($params['byMotives'] as $motive) { + $qb->andWhere( + $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 + ', + MotiveHistory::class, + ++$i, + $i, + $i, + $i, + $i, + )) + ); + $qb->setParameter(sprintf('motive_%d', $i), $motive); + } + } + + if (array_key_exists('byCreatedAfter', $params)) { + $qb->andWhere('t.createdAt >= :opening_after'); + $qb->setParameter('opening_after', $params['byCreatedAfter']); + } + + if (array_key_exists('byCreatedBefore', $params)) { + $qb->andWhere('t.createdAt <= :opening_after'); + $qb->setParameter('opening_after', $params['byCreatedBefore']); + } + return $qb; } } diff --git a/src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepositoryInterface.php b/src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepositoryInterface.php index 6fce13086..474bd7a8d 100644 --- a/src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepositoryInterface.php @@ -12,18 +12,22 @@ declare(strict_types=1); namespace Chill\TicketBundle\Repository; use Chill\PersonBundle\Entity\Person; +use Chill\TicketBundle\Entity\EmergencyStatusEnum; +use Chill\TicketBundle\Entity\Motive; use Chill\TicketBundle\Entity\StateEnum; 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} */ interface TicketACLAwareRepositoryInterface { /** * Find tickets. * - * @param array{byPerson?: list, byCurrentState: list} $params + * @param TicketACLAwareRepositoryParam $params * * @return list */ @@ -32,7 +36,7 @@ interface TicketACLAwareRepositoryInterface /** * Find tickets. * - * @param array{byPerson?: list} $params + * @param TicketACLAwareRepositoryParam $params */ public function countTickets(array $params): int; } diff --git a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByCreatedAfterTest.php b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByCreatedAfterTest.php new file mode 100644 index 000000000..2b03910e4 --- /dev/null +++ b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByCreatedAfterTest.php @@ -0,0 +1,219 @@ +prophesize(Security::class); + $security->isGranted('ROLE_USER')->willReturn(true); + + $ticketRepository = $this->prophesize(TicketACLAwareRepositoryInterface::class); + $tickets = [new Ticket(), new Ticket()]; + + // The date in RFC3339 format + $dateString = '2025-05-15T15:05:00Z'; + $date = \DateTimeImmutable::createFromFormat(\DateTimeImmutable::RFC3339, $dateString); + + $ticketRepository->countTickets( + Argument::that( + fn ($params) => isset($params['byCreatedAfter']) + && $params['byCreatedAfter'] == $date + ) + )->willReturn(2); + + $ticketRepository->findTickets( + Argument::that( + fn ($params) => isset($params['byCreatedAfter']) + && $params['byCreatedAfter'] == $date + ), + 0, + 10 + )->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); + + // 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']]]) + ); + + // Create request with byCreatedAfter filter in RFC3339 format + $request = new Request( + query: ['byCreatedAfter' => $dateString] + ); + + // Call controller method + $response = $controller->listTicket($request); + + // Assert response + $this->assertInstanceOf(JsonResponse::class, $response); + $this->assertEquals('{"items":[{},{}],"pagination":{}}', $response->getContent()); + } + + public function testListTicketWithByCreatedAfterRFC3339ExtendedFormat(): void + { + // Mock dependencies + $security = $this->prophesize(Security::class); + $security->isGranted('ROLE_USER')->willReturn(true); + + $ticketRepository = $this->prophesize(TicketACLAwareRepositoryInterface::class); + $tickets = [new Ticket(), new Ticket()]; + + // The date in RFC3339_EXTENDED format + $dateString = '2025-05-15T15:05:00.000+02:00'; + $date = \DateTimeImmutable::createFromFormat(\DateTimeImmutable::RFC3339_EXTENDED, $dateString); + + $ticketRepository->countTickets( + Argument::that( + fn ($params) => isset($params['byCreatedAfter']) + && $params['byCreatedAfter'] == $date + ) + )->willReturn(2); + + $ticketRepository->findTickets( + Argument::that( + fn ($params) => isset($params['byCreatedAfter']) + && $params['byCreatedAfter'] == $date + ), + 0, + 10 + )->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); + + // 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']]]) + ); + + // Create request with byCreatedAfter filter in RFC3339_EXTENDED format + $request = new Request( + query: ['byCreatedAfter' => $dateString] + ); + + // Call controller method + $response = $controller->listTicket($request); + + // Assert response + $this->assertInstanceOf(JsonResponse::class, $response); + $this->assertEquals('{"items":[{},{}],"pagination":{}}', $response->getContent()); + } + + public function testListTicketWithByCreatedAfterInvalidFormat(): void + { + self::expectException(BadRequestHttpException::class); + self::expectExceptionMessage('Invalid date for byCreatedAfter'); + + // 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); + + // 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']]]) + ); + + // Create request with byCreatedAfter filter in invalid format + $request = new Request( + query: ['byCreatedAfter' => '2025-05-15'] + ); + + // Call controller method + $controller->listTicket($request); + } +} diff --git a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByCreatedBeforeTest.php b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByCreatedBeforeTest.php new file mode 100644 index 000000000..c4fb42ddd --- /dev/null +++ b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByCreatedBeforeTest.php @@ -0,0 +1,219 @@ +prophesize(Security::class); + $security->isGranted('ROLE_USER')->willReturn(true); + + $ticketRepository = $this->prophesize(TicketACLAwareRepositoryInterface::class); + $tickets = [new Ticket(), new Ticket()]; + + // The date in RFC3339 format + $dateString = '2025-05-15T15:05:00Z'; + $date = \DateTimeImmutable::createFromFormat(\DateTimeImmutable::RFC3339, $dateString); + + $ticketRepository->countTickets( + Argument::that( + fn ($params) => isset($params['byCreatedBefore']) + && $params['byCreatedBefore'] == $date + ) + )->willReturn(2); + + $ticketRepository->findTickets( + Argument::that( + fn ($params) => isset($params['byCreatedBefore']) + && $params['byCreatedBefore'] == $date + ), + 0, + 10 + )->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); + + // 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']]]) + ); + + // Create request with byCreatedAfter filter in RFC3339 format + $request = new Request( + query: ['byCreatedBefore' => $dateString] + ); + + // Call controller method + $response = $controller->listTicket($request); + + // Assert response + $this->assertInstanceOf(JsonResponse::class, $response); + $this->assertEquals('{"items":[{},{}],"pagination":{}}', $response->getContent()); + } + + public function testListTicketWithByCreatedAfterRFC3339ExtendedFormat(): void + { + // Mock dependencies + $security = $this->prophesize(Security::class); + $security->isGranted('ROLE_USER')->willReturn(true); + + $ticketRepository = $this->prophesize(TicketACLAwareRepositoryInterface::class); + $tickets = [new Ticket(), new Ticket()]; + + // The date in RFC3339_EXTENDED format + $dateString = '2025-05-15T15:05:00.000+02:00'; + $date = \DateTimeImmutable::createFromFormat(\DateTimeImmutable::RFC3339_EXTENDED, $dateString); + + $ticketRepository->countTickets( + Argument::that( + fn ($params) => isset($params['byCreatedBefore']) + && $params['byCreatedBefore'] == $date + ) + )->willReturn(2); + + $ticketRepository->findTickets( + Argument::that( + fn ($params) => isset($params['byCreatedBefore']) + && $params['byCreatedBefore'] == $date + ), + 0, + 10 + )->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); + + // 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']]]) + ); + + // Create request with byCreatedAfter filter in RFC3339_EXTENDED format + $request = new Request( + query: ['byCreatedBefore' => $dateString] + ); + + // Call controller method + $response = $controller->listTicket($request); + + // Assert response + $this->assertInstanceOf(JsonResponse::class, $response); + $this->assertEquals('{"items":[{},{}],"pagination":{}}', $response->getContent()); + } + + public function testListTicketWithByCreatedBeforeInvalidFormat(): void + { + self::expectException(BadRequestHttpException::class); + self::expectExceptionMessage('Invalid date for byCreatedBefore'); + + // 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); + + // 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']]]) + ); + + // Create request with byCreatedAfter filter in invalid format + $request = new Request( + query: ['byCreatedBefore' => '2025-05-15'] + ); + + // Call controller method + $controller->listTicket($request); + } +} diff --git a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByResponseTimeExceededTest.php b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByResponseTimeExceededTest.php new file mode 100644 index 000000000..7768591ea --- /dev/null +++ b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerByResponseTimeExceededTest.php @@ -0,0 +1,207 @@ +prophesize(Security::class); + $security->isGranted('ROLE_USER')->willReturn(true); + + $ticketRepository = $this->prophesize(TicketACLAwareRepositoryInterface::class); + $tickets = [new Ticket(), new Ticket()]; + + // Create a mock clock with a fixed time + $mockClock = new MockClock(); + $now = new \DateTimeImmutable('2025-05-15T15:05:00Z'); + $mockClock->modify($now->format(\DateTimeImmutable::RFC3339)); + + // Calculate the expected byCreatedBefore value (now - PT12H) + $expectedCreatedBefore = $now->sub(new \DateInterval('PT12H')); + + $ticketRepository->countTickets( + Argument::that( + fn ($params) => isset($params['byCurrentState']) + && $params['byCurrentState'] === [StateEnum::OPEN] + && isset($params['byCreatedBefore']) + && $params['byCreatedBefore'] == $expectedCreatedBefore + && !isset($params['byCreatedAfter']) + ) + )->willReturn(2); + + $ticketRepository->findTickets( + Argument::that( + fn ($params) => isset($params['byCurrentState']) + && $params['byCurrentState'] === [StateEnum::OPEN] + && isset($params['byCreatedBefore']) + && $params['byCreatedBefore'] == $expectedCreatedBefore + && !isset($params['byCreatedAfter']) + ), + 0, + 10 + )->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); + + // Create controller + $controller = new TicketListApiController( + $security->reveal(), + $ticketRepository->reveal(), + $paginatorFactory->reveal(), + $serializer->reveal(), + $personRepository->reveal(), + $motiveRepository->reveal(), + $mockClock, + new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]) + ); + + // Create request with byResponseTimeExceeded parameter only + $request = new Request( + query: ['byResponseTimeExceeded' => ''] + ); + + // Call controller method + $response = $controller->listTicket($request); + + // Assert response + $this->assertInstanceOf(JsonResponse::class, $response); + $this->assertEquals('{"items":[{},{}],"pagination":{}}', $response->getContent()); + } + + public function testListTicketWithByResponseTimeExceededAndByCreatedAfter(): void + { + // Mock dependencies + $security = $this->prophesize(Security::class); + $security->isGranted('ROLE_USER')->willReturn(true); + + $ticketRepository = $this->prophesize(TicketACLAwareRepositoryInterface::class); + $tickets = [new Ticket(), new Ticket()]; + + // Create a mock clock with a fixed time + $mockClock = new MockClock(); + $now = new \DateTimeImmutable('2025-05-15T15:05:00Z'); + $mockClock->modify($now->format(\DateTimeImmutable::RFC3339)); + + // Calculate the expected byCreatedBefore value (now - PT12H) + $expectedCreatedBefore = $now->sub(new \DateInterval('PT12H')); + + // The byCreatedAfter parameter should be ignored when byResponseTimeExceeded is present + $ticketRepository->countTickets( + Argument::that( + fn ($params) => isset($params['byCurrentState']) + && $params['byCurrentState'] === [StateEnum::OPEN] + && isset($params['byCreatedBefore']) + && $params['byCreatedBefore'] == $expectedCreatedBefore + && !isset($params['byCreatedAfter']) + ) + )->willReturn(2); + + $ticketRepository->findTickets( + Argument::that( + fn ($params) => isset($params['byCurrentState']) + && $params['byCurrentState'] === [StateEnum::OPEN] + && isset($params['byCreatedBefore']) + && $params['byCreatedBefore'] == $expectedCreatedBefore + && !isset($params['byCreatedAfter']) + ), + 0, + 10 + )->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); + + // Create controller + $controller = new TicketListApiController( + $security->reveal(), + $ticketRepository->reveal(), + $paginatorFactory->reveal(), + $serializer->reveal(), + $personRepository->reveal(), + $motiveRepository->reveal(), + $mockClock, + new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]) + ); + + // Create request with both byCreatedAfter and byResponseTimeExceeded parameters + $request = new Request( + query: [ + 'byCreatedAfter' => '2025-05-15T12:00:00+00:00', + 'byResponseTimeExceeded' => '', + ] + ); + + // Call controller method + $response = $controller->listTicket($request); + + // Assert response + $this->assertInstanceOf(JsonResponse::class, $response); + $this->assertEquals('{"items":[{},{}],"pagination":{}}', $response->getContent()); + } +} diff --git a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerCurrentStateEmergencyTest.php b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerCurrentStateEmergencyTest.php new file mode 100644 index 000000000..79388a4a0 --- /dev/null +++ b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerCurrentStateEmergencyTest.php @@ -0,0 +1,137 @@ +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['byCurrentStateEmergency']) && in_array(EmergencyStatusEnum::YES, $params['byCurrentStateEmergency'])) + ) + ->willReturn(2); + $ticketRepository->findTickets( + Argument::that(fn ($params) => isset($params['byCurrentStateEmergency']) && in_array(EmergencyStatusEnum::YES, $params['byCurrentStateEmergency'])), + 0, + 10 + )->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); + + // 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']]]) + ); + + // Create request with emergency filter + $request = new Request( + query: ['byCurrentStateEmergency' => 'yes,no'] + ); + + // Call controller method + $response = $controller->listTicket($request); + + // Assert response + $this->assertInstanceOf(JsonResponse::class, $response); + $this->assertEquals('{"items":[{},{}],"pagination":{}}', $response->getContent()); + } + + public function testListTicketWithCurrentStateEmergencyWithInvalidFilter(): void + { + self::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); + + // 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']]]) + ); + + // Create request with invalid emergency filter + $request = new Request( + query: ['byCurrentStateEmergency' => 'foo'] + ); + + // Call controller method + $response = $controller->listTicket($request); + } +} diff --git a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerCurrentStateTest.php b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerCurrentStateTest.php new file mode 100644 index 000000000..0a8653094 --- /dev/null +++ b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerCurrentStateTest.php @@ -0,0 +1,137 @@ +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['byCurrentState']) && in_array(StateEnum::OPEN, $params['byCurrentState'])) + ) + ->willReturn(2); + $ticketRepository->findTickets( + Argument::that(fn ($params) => isset($params['byCurrentState']) && in_array(StateEnum::OPEN, $params['byCurrentState'])), + 0, + 10 + )->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); + + // 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']]]) + ); + + // Create request with person filter + $request = new Request( + query: ['byCurrentState' => 'open,closed'] + ); + + // Call controller method + $response = $controller->listTicket($request); + + // Assert response + $this->assertInstanceOf(JsonResponse::class, $response); + $this->assertEquals('{"items":[{},{}],"pagination":{}}', $response->getContent()); + } + + public function testListTicketWithCurrentStateWithInvalidFilter(): void + { + self::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); + + // 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']]]) + ); + + // Create request with person filter + $request = new Request( + query: ['byCurrentState' => 'foo'] + ); + + // Call controller method + $response = $controller->listTicket($request); + } +} diff --git a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerMotivesTest.php b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerMotivesTest.php new file mode 100644 index 000000000..b77f7d4f5 --- /dev/null +++ b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerMotivesTest.php @@ -0,0 +1,243 @@ +prophesize(Security::class); + $security->isGranted('ROLE_USER')->willReturn(true); + + $motive1 = new Motive(); + $motive2 = new Motive(); + + $ticketRepository = $this->prophesize(TicketACLAwareRepositoryInterface::class); + $tickets = [new Ticket(), new Ticket()]; + $ticketRepository->countTickets( + Argument::that(fn ($params) => isset($params['byMotives']) && in_array($motive1, $params['byMotives']) && in_array($motive2, $params['byMotives'])) + ) + ->willReturn(2); + $ticketRepository->findTickets( + Argument::that(fn ($params) => isset($params['byMotives']) && in_array($motive1, $params['byMotives']) && in_array($motive2, $params['byMotives'])), + 0, + 10 + )->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); + $motiveRepository->find(1)->willReturn($motive1); + $motiveRepository->find(2)->willReturn($motive2); + + // 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']]]) + ); + + // Create request with multiple motives filter + $request = new Request( + query: ['byMotives' => '1,2'] + ); + + // Call controller method + $response = $controller->listTicket($request); + + // Assert response + $this->assertInstanceOf(JsonResponse::class, $response); + $this->assertEquals('{"items":[{},{}],"pagination":{}}', $response->getContent()); + } + + public function testListTicketWithSingleMotiveFilter(): void + { + // Mock dependencies + $security = $this->prophesize(Security::class); + $security->isGranted('ROLE_USER')->willReturn(true); + + $motive = new Motive(); + + $ticketRepository = $this->prophesize(TicketACLAwareRepositoryInterface::class); + $tickets = [new Ticket(), new Ticket()]; + $ticketRepository->countTickets( + Argument::that(fn ($params) => isset($params['byMotives']) && in_array($motive, $params['byMotives'])) + ) + ->willReturn(2); + $ticketRepository->findTickets( + Argument::that(fn ($params) => isset($params['byMotives']) && in_array($motive, $params['byMotives'])), + 0, + 10 + )->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); + $motiveRepository->find(1)->willReturn($motive); + + // 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']]]) + ); + + // Create request with single motive filter + $request = new Request( + query: ['byMotives' => '1'] + ); + + // Call controller method + $response = $controller->listTicket($request); + + // Assert response + $this->assertInstanceOf(JsonResponse::class, $response); + $this->assertEquals('{"items":[{},{}],"pagination":{}}', $response->getContent()); + } + + public function testListTicketWithMotiveNotFound(): void + { + self::expectException(BadRequestHttpException::class); + self::expectExceptionMessage('Motive not found'); + + // 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); + $motiveRepository->find(999)->willReturn(null); + + // 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']]]) + ); + + // Create request with non-existent motive + $request = new Request( + query: ['byMotives' => '999'] + ); + + // Call controller method + $controller->listTicket($request); + } + + public function testListTicketWithNonIntegerMotiveParameter(): void + { + self::expectException(BadRequestHttpException::class); + self::expectExceptionMessage('Only numbers are allowed in by motives parameter'); + + // 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); + + // 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']]]) + ); + + // Create request with non-integer motive parameter + $request = new Request( + query: ['byMotives' => 'abc'] + ); + + // Call controller method + $controller->listTicket($request); + } +} diff --git a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerTest.php b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerTest.php index 9582164fb..5a9ec3b3d 100644 --- a/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerTest.php +++ b/src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerTest.php @@ -18,8 +18,8 @@ use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Repository\PersonRepository; use Chill\PersonBundle\Security\Authorization\PersonVoter; use Chill\TicketBundle\Controller\TicketListApiController; -use Chill\TicketBundle\Entity\StateEnum; use Chill\TicketBundle\Entity\Ticket; +use Chill\TicketBundle\Repository\MotiveRepository; use Chill\TicketBundle\Repository\TicketACLAwareRepositoryInterface; use PHPUnit\Framework\TestCase; use Prophecy\Argument; @@ -27,9 +27,10 @@ use Prophecy\PhpUnit\ProphecyTrait; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; -use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\Security\Core\Security; use Symfony\Component\Serializer\SerializerInterface; +use Symfony\Component\Clock\MockClock; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; /** * @internal @@ -66,6 +67,7 @@ final class TicketListApiControllerTest extends TestCase )->willReturn('{"items":[{},{}],"pagination":{}}'); $personRepository = $this->prophesize(PersonRepository::class); + $motiveRepository = $this->prophesize(MotiveRepository::class); // Create controller $controller = new TicketListApiController( @@ -73,7 +75,10 @@ final class TicketListApiControllerTest extends TestCase $ticketRepository->reveal(), $paginatorFactory->reveal(), $serializer->reveal(), - $personRepository->reveal() + $personRepository->reveal(), + $motiveRepository->reveal(), + new MockClock(), + new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]) ); // Create request @@ -121,6 +126,7 @@ final class TicketListApiControllerTest extends TestCase $personRepository = $this->prophesize(PersonRepository::class); $personRepository->find(123)->willReturn($person); + $motiveRepository = $this->prophesize(MotiveRepository::class); // Create controller $controller = new TicketListApiController( @@ -128,7 +134,10 @@ final class TicketListApiControllerTest extends TestCase $ticketRepository->reveal(), $paginatorFactory->reveal(), $serializer->reveal(), - $personRepository->reveal() + $personRepository->reveal(), + $motiveRepository->reveal(), + new MockClock(), + new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]) ); // Create request with person filter @@ -144,93 +153,6 @@ final class TicketListApiControllerTest extends TestCase $this->assertEquals('{"items":[{},{}],"pagination":{}}', $response->getContent()); } - public function testListTicketWithCurrentStateFilter(): void - { - // Mock dependencies - $security = $this->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['byCurrentState']) && in_array(StateEnum::OPEN, $params['byCurrentState'])) - ) - ->willReturn(2); - $ticketRepository->findTickets( - Argument::that(fn ($params) => isset($params['byCurrentState']) && in_array(StateEnum::OPEN, $params['byCurrentState'])), - 0, - 10 - )->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); - - // Create controller - $controller = new TicketListApiController( - $security->reveal(), - $ticketRepository->reveal(), - $paginatorFactory->reveal(), - $serializer->reveal(), - $personRepository->reveal() - ); - - // Create request with person filter - $request = new Request( - query: ['byCurrentState' => 'open,closed'] - ); - - // Call controller method - $response = $controller->listTicket($request); - - // Assert response - $this->assertInstanceOf(JsonResponse::class, $response); - $this->assertEquals('{"items":[{},{}],"pagination":{}}', $response->getContent()); - } - - public function testListTicketWithCurrentStateWithInvalidFilter(): void - { - self::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); - - // Create controller - $controller = new TicketListApiController( - $security->reveal(), - $ticketRepository->reveal(), - $paginatorFactory->reveal(), - $serializer->reveal(), - $personRepository->reveal() - ); - - // Create request with person filter - $request = new Request( - query: ['byCurrentState' => 'foo'] - ); - - // Call controller method - $response = $controller->listTicket($request); - } - public function testListTicketWithoutUserRole(): void { // Mock dependencies @@ -241,6 +163,7 @@ final class TicketListApiControllerTest extends TestCase $paginatorFactory = $this->prophesize(PaginatorFactoryInterface::class); $serializer = $this->prophesize(SerializerInterface::class); $personRepository = $this->prophesize(PersonRepository::class); + $motiveRepository = $this->prophesize(MotiveRepository::class); // Create controller $controller = new TicketListApiController( @@ -248,7 +171,10 @@ final class TicketListApiControllerTest extends TestCase $ticketRepository->reveal(), $paginatorFactory->reveal(), $serializer->reveal(), - $personRepository->reveal() + $personRepository->reveal(), + $motiveRepository->reveal(), + new MockClock(), + new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]) ); // Create request @@ -277,6 +203,7 @@ final class TicketListApiControllerTest extends TestCase $personRepository = $this->prophesize(PersonRepository::class); $personRepository->find(123)->willReturn($person); + $motiveRepository = $this->prophesize(MotiveRepository::class); // Create controller $controller = new TicketListApiController( @@ -284,7 +211,10 @@ final class TicketListApiControllerTest extends TestCase $ticketRepository->reveal(), $paginatorFactory->reveal(), $serializer->reveal(), - $personRepository->reveal() + $personRepository->reveal(), + $motiveRepository->reveal(), + new MockClock(), + new ParameterBag(['chill_ticket' => ['ticket' => ['response_time_exceeded_delay' => 'PT12H']]]) ); // Create request with person filter diff --git a/src/Bundle/ChillTicketBundle/tests/Repository/TicketACLAwareRepositoryTest.php b/src/Bundle/ChillTicketBundle/tests/Repository/TicketACLAwareRepositoryTest.php index 8a0035032..5156f7de5 100644 --- a/src/Bundle/ChillTicketBundle/tests/Repository/TicketACLAwareRepositoryTest.php +++ b/src/Bundle/ChillTicketBundle/tests/Repository/TicketACLAwareRepositoryTest.php @@ -12,6 +12,8 @@ declare(strict_types=1); namespace Chill\TicketBundle\Tests\Repository; use Chill\PersonBundle\DataFixtures\Helper\RandomPersonHelperTrait; +use Chill\TicketBundle\Entity\EmergencyStatusEnum; +use Chill\TicketBundle\Entity\Motive; use Chill\TicketBundle\Entity\StateEnum; use Chill\TicketBundle\Repository\TicketACLAwareRepository; use Doctrine\ORM\EntityManagerInterface; @@ -74,8 +76,51 @@ class TicketACLAwareRepositoryTest extends KernelTestCase public function testFindTicketByCurrentStateMultipleState(): void { - $result = $this->repository->countTickets(['byCurrentState' => [StateEnum::OPEN, StateEnum::CLOSED]]); + $result = $this->repository->findTickets(['byCurrentState' => [StateEnum::OPEN, StateEnum::CLOSED]]); + + self::assertIsArray($result); + } + + public function testCountTicketByCurrentStateEmergencySingleState(): void + { + $result = $this->repository->countTickets(['byCurrentStateEmergency' => [EmergencyStatusEnum::YES]]); self::assertIsInt($result); } + + public function testFindTicketByCurrentStateEmergencyMultipleState(): void + { + $result = $this->repository->findTickets(['byCurrentStateEmergency' => [EmergencyStatusEnum::YES, EmergencyStatusEnum::NO]]); + + self::assertIsArray($result); + } + + public function testFindTicketByMotives(): void + { + $motives = $this->entityManager->createQuery(sprintf('SELECT m FROM %s m', Motive::class)) + ->setMaxResults(2) + ->getResult(); + + if ([] === $motives) { + throw new \UnexpectedValueException('No motives found'); + } + + $results = $this->repository->findTickets(['byMotives' => $motives]); + + self::assertIsArray($results); + } + + public function testFindTicketByCreatedBefore(): void + { + $actual = $this->repository->findTickets(['byCreatedBefore' => (new \DateTimeImmutable('now'))->add(new \DateInterval('P1D'))]); + + self::assertIsArray($actual); + } + + public function testFindTicketByCreatedAfter(): void + { + $actual = $this->repository->findTickets(['byCreatedAfter' => (new \DateTimeImmutable('now'))->sub(new \DateInterval('P1M'))]); + + self::assertIsArray($actual); + } }