From fa6783569045615b3a0487972adf08c1d4caf095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 23 Apr 2024 23:00:12 +0200 Subject: [PATCH] Add functionality to add single addressee to tickets This update introduces a new feature allowing end-users to add a single addressee to a ticket without removing the existing ones. This was achieved by adding a new API endpoint and updating the SetAddresseesController to handle the addition of a single addressee. Accompanying tests have also been provided to ensure the new feature works as expected. --- .../ChillTicketBundle/chill.api.specs.yaml | 34 +++++++++- .../src/Action/Ticket/AddAddresseeCommand.php | 29 +++++++++ .../Action/Ticket/SetAddresseesCommand.php | 9 +++ .../Controller/SetAddresseesController.php | 26 ++++++-- .../SetAddresseesControllerTest.php | 64 +++++++++++++++++++ 5 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Ticket/AddAddresseeCommand.php diff --git a/src/Bundle/ChillTicketBundle/chill.api.specs.yaml b/src/Bundle/ChillTicketBundle/chill.api.specs.yaml index 232982f01..5be9ac0c0 100644 --- a/src/Bundle/ChillTicketBundle/chill.api.specs.yaml +++ b/src/Bundle/ChillTicketBundle/chill.api.specs.yaml @@ -115,7 +115,7 @@ paths: schema: type: object properties: - addresses: + addressees: type: array items: oneOf: @@ -129,3 +129,35 @@ paths: 422: description: "UNPROCESSABLE ENTITY" + /1.0/ticket/{id}/addressee/add: + post: + tags: + - ticket + summary: Add an addressee to a ticket, without removing existing ones. + parameters: + - name: id + in: path + required: true + description: The ticket id + schema: + type: integer + format: integer + minimum: 1 + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + addressee: + oneOf: + - $ref: '#/components/schemas/UserGroupById' + - $ref: '#/components/schemas/UserById' + + + responses: + 201: + description: "ACCEPTED" + 422: + description: "UNPROCESSABLE ENTITY" diff --git a/src/Bundle/ChillTicketBundle/src/Action/Ticket/AddAddresseeCommand.php b/src/Bundle/ChillTicketBundle/src/Action/Ticket/AddAddresseeCommand.php new file mode 100644 index 000000000..d7f9c545f --- /dev/null +++ b/src/Bundle/ChillTicketBundle/src/Action/Ticket/AddAddresseeCommand.php @@ -0,0 +1,29 @@ +addressee, + ...$ticket->getCurrentAddressee(), + ]); + } } diff --git a/src/Bundle/ChillTicketBundle/src/Controller/SetAddresseesController.php b/src/Bundle/ChillTicketBundle/src/Controller/SetAddresseesController.php index add8d480a..9fa6dc366 100644 --- a/src/Bundle/ChillTicketBundle/src/Controller/SetAddresseesController.php +++ b/src/Bundle/ChillTicketBundle/src/Controller/SetAddresseesController.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\TicketBundle\Controller; +use Chill\TicketBundle\Action\Ticket\AddAddresseeCommand; use Chill\TicketBundle\Action\Ticket\Handler\SetAddresseesCommandHandler; use Chill\TicketBundle\Action\Ticket\SetAddresseesCommand; use Chill\TicketBundle\Entity\Ticket; @@ -28,11 +29,11 @@ use Symfony\Component\Validator\Validator\ValidatorInterface; final readonly class SetAddresseesController { public function __construct( - private Security $security, - private EntityManagerInterface $entityManager, - private SerializerInterface $serializer, + private Security $security, + private EntityManagerInterface $entityManager, + private SerializerInterface $serializer, private SetAddresseesCommandHandler $addressesCommandHandler, - private ValidatorInterface $validator, + private ValidatorInterface $validator, ) {} #[Route('/api/1.0/ticket/{id}/addressees/set', methods: ['POST'])] @@ -44,6 +45,23 @@ final readonly class SetAddresseesController $command = $this->serializer->deserialize($request->getContent(), SetAddresseesCommand::class, 'json', [AbstractNormalizer::GROUPS => ['read']]); + return $this->registerSetAddressees($command, $ticket); + } + + #[Route('/api/1.0/ticket/{id}/addressee/add', methods: ['POST'])] + public function addAddressee(Ticket $ticket, Request $request): Response + { + if (!$this->security->isGranted('ROLE_USER')) { + throw new AccessDeniedHttpException('Only users can add addressees.'); + } + + $command = $this->serializer->deserialize($request->getContent(), AddAddresseeCommand::class, 'json', [AbstractNormalizer::GROUPS => ['read']]); + + return $this->registerSetAddressees(SetAddresseesCommand::fromAddAddresseeCommand($command, $ticket), $ticket); + } + + private function registerSetAddressees(SetAddresseesCommand $command, Ticket $ticket): Response + { if (0 < count($errors = $this->validator->validate($command))) { return new JsonResponse( $this->serializer->serialize($errors, 'json'), diff --git a/src/Bundle/ChillTicketBundle/tests/Controller/SetAddresseesControllerTest.php b/src/Bundle/ChillTicketBundle/tests/Controller/SetAddresseesControllerTest.php index ed842f742..a7ca214c1 100644 --- a/src/Bundle/ChillTicketBundle/tests/Controller/SetAddresseesControllerTest.php +++ b/src/Bundle/ChillTicketBundle/tests/Controller/SetAddresseesControllerTest.php @@ -85,6 +85,70 @@ class SetAddresseesControllerTest extends KernelTestCase self::assertGreaterThan(0, count($asArray['violations'])); } + /** + * @dataProvider getContentDataUnique + */ + public function testAddAddresseeWithValidData(array $bodyAsArray): void + { + $controller = $this->buildController(true, true); + $request = new Request(content: json_encode(['addressee' => $bodyAsArray], JSON_THROW_ON_ERROR, 512)); + $ticket = new Ticket(); + + $response = $controller->addAddressee($ticket, $request); + + self::assertEquals(200, $response->getStatusCode()); + + $asArray = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR); + self::assertIsArray($asArray); + self::assertArrayHasKey('type', $asArray); + self::assertEquals('ticket_ticket', $asArray['type']); + } + + /** + * @throws \JsonException + * + * @dataProvider getContentDataUnique + */ + public function testAddAddresseeWithInvalidData(array $bodyAsArray): void + { + $controller = $this->buildController(false, false); + $request = new Request(content: json_encode(['addressee' => $bodyAsArray], JSON_THROW_ON_ERROR, 512)); + $ticket = new Ticket(); + + $response = $controller->addAddressee($ticket, $request); + + self::assertEquals(422, $response->getStatusCode()); + + $asArray = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR); + self::assertIsArray($asArray); + self::arrayHasKey('violations', $asArray); + self::assertGreaterThan(0, count($asArray['violations'])); + } + + public static function getContentDataUnique(): iterable + { + $entityManager = self::getContainer()->get(EntityManagerInterface::class); + + $userGroup = $entityManager->createQuery('SELECT ug FROM '.UserGroup::class.' ug ') + ->setMaxResults(1)->getOneOrNullResult(); + + if (null === $userGroup) { + throw new \RuntimeException('User group not existing in database'); + } + + $user = $entityManager->createQuery('SELECT u FROM '.User::class.' u') + ->setMaxResults(1)->getOneOrNullResult(); + + if (null === $user) { + throw new \RuntimeException('User not existing in database'); + } + + self::ensureKernelShutdown(); + + yield [['type' => 'user', 'id' => $user->getId()]]; + yield [['type' => 'user_group', 'id' => $userGroup->getId()]]; + } + public static function getContentData(): iterable { self::bootKernel();