Add functionality to add comments to tickets

A new controller, 'AddCommentController', has been added. This controller implements the 'AddCommentCommandHandler', allowing users to add comments to tickets. Additionally, corresponding test cases were implemented. The Ticket entity was also updated to accept and manage comments. API endpoint specs were updated to reflect these changes.
This commit is contained in:
Julien Fastré 2024-04-18 21:57:55 +02:00
parent 56a1a488de
commit 613ee8b186
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
8 changed files with 326 additions and 4 deletions

View File

@ -64,3 +64,32 @@ paths:
description: "ACCEPTED"
422:
description: "UNPROCESSABLE ENTITY"
/1.0/ticket/{id}/comment/add:
post:
tags:
- ticket
summary: Add a comment to an existing ticket
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:
content:
type: string
responses:
201:
description: "ACCEPTED"
422:
description: "UNPROCESSABLE ENTITY"

View File

@ -0,0 +1,25 @@
<?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 Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Serializer\Annotation as Serializer;
final readonly class AddCommentCommand
{
public function __construct(
#[Assert\NotBlank()]
#[Assert\NotNull]
#[Serializer\Groups(['write'])]
public ?string $content = null,
) {}
}

View File

@ -0,0 +1,31 @@
<?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\Handler;
use Chill\TicketBundle\Action\Ticket\AddCommentCommand;
use Chill\TicketBundle\Entity\Comment;
use Chill\TicketBundle\Entity\Ticket;
use Doctrine\ORM\EntityManagerInterface;
final readonly class AddCommentCommandHandler
{
public function __construct(
private EntityManagerInterface $entityManager,
) {}
public function handle(Ticket $ticket, AddCommentCommand $command): void
{
$comment = new Comment($command->content, $ticket);
$this->entityManager->persist($comment);
}
}

View File

@ -0,0 +1,68 @@
<?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\Controller;
use Chill\TicketBundle\Action\Ticket\AddCommentCommand;
use Chill\TicketBundle\Action\Ticket\Handler\AddCommentCommandHandler;
use Chill\TicketBundle\Entity\Ticket;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
final readonly class AddCommentController
{
public function __construct(
private Security $security,
private SerializerInterface $serializer,
private ValidatorInterface $validator,
private AddCommentCommandHandler $addCommentCommandHandler,
private EntityManagerInterface $entityManager,
) {}
#[Route('/api/1.0/ticket/{id}/comment/add', name: 'chill_ticket_comment_add', methods: ['POST'])]
public function __invoke(Ticket $ticket, Request $request): Response
{
if (!$this->security->isGranted('ROLE_USER')) {
throw new AccessDeniedHttpException('Only user can add ticket comments.');
}
$command = $this->serializer->deserialize($request->getContent(), AddCommentCommand::class, 'json', ['groups' => 'write']);
$errors = $this->validator->validate($command);
if (count($errors) > 0) {
return new JsonResponse(
$this->serializer->serialize($errors, 'json'),
Response::HTTP_UNPROCESSABLE_ENTITY,
[],
true
);
}
$this->addCommentCommandHandler->handle($ticket, $command);
$this->entityManager->flush();
return new JsonResponse(
$this->serializer->serialize($ticket, 'json', ['groups' => 'read']),
Response::HTTP_CREATED,
[],
true
);
}
}

View File

@ -31,12 +31,14 @@ class Comment implements TrackCreationInterface, TrackUpdateInterface
private ?int $id = null;
public function __construct(
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: false, options: ['default' => ''])]
private string $content,
#[ORM\ManyToOne(targetEntity: Ticket::class, inversedBy: 'comments')]
#[JoinColumn(nullable: false)]
private Ticket $ticket,
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: false, options: ['default' => ''])]
private string $content = ''
) {}
) {
$ticket->addComment($this);
}
public function getId(): ?int
{

View File

@ -104,16 +104,27 @@ class Ticket implements TrackCreationInterface, TrackUpdateInterface
->getValues();
}
/**
* @internal use @see{Comment::__construct} instead
*/
public function addComment(Comment $comment): void
{
$this->comments->add($comment);
}
/**
* Add a PersonHistory.
*
* This method should not be used, use @see{PersonHistory::__construct()} insted.
* @internal use @see{PersonHistory::__construct} instead
*/
public function addPersonHistory(PersonHistory $personHistory): void
{
$this->personHistories->add($personHistory);
}
/**
* @internal use @see{MotiveHistory::__construct} instead
*/
public function addMotiveHistory(MotiveHistory $motiveHistory): void
{
$this->motiveHistories->add($motiveHistory);

View File

@ -0,0 +1,52 @@
<?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\Tests\Action\Ticket\Handler;
use Chill\TicketBundle\Action\Ticket\AddCommentCommand;
use Chill\TicketBundle\Action\Ticket\Handler\AddCommentCommandHandler;
use Chill\TicketBundle\Entity\Comment;
use Chill\TicketBundle\Entity\Ticket;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
/**
* @internal
*
* @coversNothing
*/
class AddCommentCommandHandlerTest extends TestCase
{
use ProphecyTrait;
public function testAddComment(): void
{
$handler = $this->buildCommand();
$ticket = new Ticket();
$command = new AddCommentCommand(content: 'test');
$handler->handle($ticket, $command);
self::assertCount(1, $ticket->getComments());
self::assertEquals('test', $ticket->getComments()[0]->getContent());
}
private function buildCommand(): AddCommentCommandHandler
{
$entityManager = $this->prophesize(EntityManagerInterface::class);
$entityManager->persist(Argument::type(Comment::class))->shouldBeCalled();
return new AddCommentCommandHandler($entityManager->reveal());
}
}

View File

@ -0,0 +1,104 @@
<?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\Tests\Controller;
use Chill\TicketBundle\Action\Ticket\Handler\AddCommentCommandHandler;
use Chill\TicketBundle\Controller\AddCommentController;
use Chill\TicketBundle\Entity\Comment;
use Chill\TicketBundle\Entity\Ticket;
use Doctrine\ORM\EntityManagerInterface;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @internal
*
* @coversNothing
*/
class AddCommentControllerTest extends KernelTestCase
{
use ProphecyTrait;
private SerializerInterface $serializer;
private ValidatorInterface $validator;
protected function setUp(): void
{
self::bootKernel();
$this->validator = self::getContainer()->get(ValidatorInterface::class);
$this->serializer = self::getContainer()->get(SerializerInterface::class);
}
public function testAddComment(): void
{
$controller = $this->buildController(willFlush: true);
$ticket = new Ticket();
$request = new Request(content: <<<'JSON'
{"content": "test"}
JSON);
$response = $controller->__invoke($ticket, $request);
self::assertEquals(201, $response->getStatusCode());
}
public function testAddCommentWithBlankContent(): void
{
$controller = $this->buildController(willFlush: false);
$ticket = new Ticket();
$request = new Request(content: <<<'JSON'
{"content": ""}
JSON);
$response = $controller->__invoke($ticket, $request);
self::assertEquals(422, $response->getStatusCode());
$request = new Request(content: <<<'JSON'
{"content": null}
JSON);
$response = $controller->__invoke($ticket, $request);
self::assertEquals(422, $response->getStatusCode());
}
private function buildController(bool $willFlush): AddCommentController
{
$security = $this->prophesize(Security::class);
$security->isGranted('ROLE_USER')->willReturn(true);
$entityManager = $this->prophesize(EntityManagerInterface::class);
if ($willFlush) {
$entityManager->persist(Argument::type(Comment::class))->shouldBeCalled();
$entityManager->flush()->shouldBeCalled();
}
$commandHandler = new AddCommentCommandHandler($entityManager->reveal());
return new AddCommentController(
$security->reveal(),
$this->serializer,
$this->validator,
$commandHandler,
$entityManager->reveal(),
);
}
}