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.
This commit is contained in:
Julien Fastré 2024-04-23 23:00:12 +02:00
parent b434d38091
commit fa67835690
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
5 changed files with 157 additions and 5 deletions

View File

@ -115,7 +115,7 @@ paths:
schema: schema:
type: object type: object
properties: properties:
addresses: addressees:
type: array type: array
items: items:
oneOf: oneOf:
@ -129,3 +129,35 @@ paths:
422: 422:
description: "UNPROCESSABLE ENTITY" 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"

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\TicketBundle\Action\Ticket;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserGroup;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* Add a single addressee to the ticket.
*
* This command is converted into an "SetAddresseesCommand" for handling
*/
final readonly class AddAddresseeCommand
{
public function __construct(
#[Groups(['read'])]
public User|UserGroup $addressee
) {}
}

View File

@ -14,6 +14,7 @@ namespace Chill\TicketBundle\Action\Ticket;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserGroup; use Chill\MainBundle\Entity\UserGroup;
use Chill\MainBundle\Validation\Constraint\UserGroupDoNotExclude; use Chill\MainBundle\Validation\Constraint\UserGroupDoNotExclude;
use Chill\TicketBundle\Entity\Ticket;
use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints\GreaterThan; use Symfony\Component\Validator\Constraints\GreaterThan;
@ -28,4 +29,12 @@ final readonly class SetAddresseesCommand
#[Groups(['read'])] #[Groups(['read'])]
public array $addressees public array $addressees
) {} ) {}
public static function fromAddAddresseeCommand(AddAddresseeCommand $command, Ticket $ticket): self
{
return new self([
$command->addressee,
...$ticket->getCurrentAddressee(),
]);
}
} }

View File

@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\TicketBundle\Controller; namespace Chill\TicketBundle\Controller;
use Chill\TicketBundle\Action\Ticket\AddAddresseeCommand;
use Chill\TicketBundle\Action\Ticket\Handler\SetAddresseesCommandHandler; use Chill\TicketBundle\Action\Ticket\Handler\SetAddresseesCommandHandler;
use Chill\TicketBundle\Action\Ticket\SetAddresseesCommand; use Chill\TicketBundle\Action\Ticket\SetAddresseesCommand;
use Chill\TicketBundle\Entity\Ticket; use Chill\TicketBundle\Entity\Ticket;
@ -28,11 +29,11 @@ use Symfony\Component\Validator\Validator\ValidatorInterface;
final readonly class SetAddresseesController final readonly class SetAddresseesController
{ {
public function __construct( public function __construct(
private Security $security, private Security $security,
private EntityManagerInterface $entityManager, private EntityManagerInterface $entityManager,
private SerializerInterface $serializer, private SerializerInterface $serializer,
private SetAddresseesCommandHandler $addressesCommandHandler, private SetAddresseesCommandHandler $addressesCommandHandler,
private ValidatorInterface $validator, private ValidatorInterface $validator,
) {} ) {}
#[Route('/api/1.0/ticket/{id}/addressees/set', methods: ['POST'])] #[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']]); $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))) { if (0 < count($errors = $this->validator->validate($command))) {
return new JsonResponse( return new JsonResponse(
$this->serializer->serialize($errors, 'json'), $this->serializer->serialize($errors, 'json'),

View File

@ -85,6 +85,70 @@ class SetAddresseesControllerTest extends KernelTestCase
self::assertGreaterThan(0, count($asArray['violations'])); 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 public static function getContentData(): iterable
{ {
self::bootKernel(); self::bootKernel();