From 613ee8b186c871b9268ed1489b260f1f614eb1f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 18 Apr 2024 21:57:55 +0200 Subject: [PATCH] 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. --- .../ChillTicketBundle/chill.api.specs.yaml | 29 +++++ .../src/Action/Ticket/AddCommentCommand.php | 25 +++++ .../Handler/AddCommentCommandHandler.php | 31 ++++++ .../src/Controller/AddCommentController.php | 68 ++++++++++++ .../ChillTicketBundle/src/Entity/Comment.php | 8 +- .../ChillTicketBundle/src/Entity/Ticket.php | 13 ++- .../Handler/AddCommentCommandHandlerTest.php | 52 +++++++++ .../Controller/AddCommentControllerTest.php | 104 ++++++++++++++++++ 8 files changed, 326 insertions(+), 4 deletions(-) create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Ticket/AddCommentCommand.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Ticket/Handler/AddCommentCommandHandler.php create mode 100644 src/Bundle/ChillTicketBundle/src/Controller/AddCommentController.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Action/Ticket/Handler/AddCommentCommandHandlerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Controller/AddCommentControllerTest.php diff --git a/src/Bundle/ChillTicketBundle/chill.api.specs.yaml b/src/Bundle/ChillTicketBundle/chill.api.specs.yaml index ce53732ff..86049cc24 100644 --- a/src/Bundle/ChillTicketBundle/chill.api.specs.yaml +++ b/src/Bundle/ChillTicketBundle/chill.api.specs.yaml @@ -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" diff --git a/src/Bundle/ChillTicketBundle/src/Action/Ticket/AddCommentCommand.php b/src/Bundle/ChillTicketBundle/src/Action/Ticket/AddCommentCommand.php new file mode 100644 index 000000000..1c009cfc8 --- /dev/null +++ b/src/Bundle/ChillTicketBundle/src/Action/Ticket/AddCommentCommand.php @@ -0,0 +1,25 @@ +content, $ticket); + + $this->entityManager->persist($comment); + } +} diff --git a/src/Bundle/ChillTicketBundle/src/Controller/AddCommentController.php b/src/Bundle/ChillTicketBundle/src/Controller/AddCommentController.php new file mode 100644 index 000000000..157e69698 --- /dev/null +++ b/src/Bundle/ChillTicketBundle/src/Controller/AddCommentController.php @@ -0,0 +1,68 @@ +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 + ); + } +} diff --git a/src/Bundle/ChillTicketBundle/src/Entity/Comment.php b/src/Bundle/ChillTicketBundle/src/Entity/Comment.php index d62e6d8b4..de12d5d47 100644 --- a/src/Bundle/ChillTicketBundle/src/Entity/Comment.php +++ b/src/Bundle/ChillTicketBundle/src/Entity/Comment.php @@ -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 { diff --git a/src/Bundle/ChillTicketBundle/src/Entity/Ticket.php b/src/Bundle/ChillTicketBundle/src/Entity/Ticket.php index de369379b..cd49d7377 100644 --- a/src/Bundle/ChillTicketBundle/src/Entity/Ticket.php +++ b/src/Bundle/ChillTicketBundle/src/Entity/Ticket.php @@ -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); diff --git a/src/Bundle/ChillTicketBundle/tests/Action/Ticket/Handler/AddCommentCommandHandlerTest.php b/src/Bundle/ChillTicketBundle/tests/Action/Ticket/Handler/AddCommentCommandHandlerTest.php new file mode 100644 index 000000000..5ff18b436 --- /dev/null +++ b/src/Bundle/ChillTicketBundle/tests/Action/Ticket/Handler/AddCommentCommandHandlerTest.php @@ -0,0 +1,52 @@ +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()); + } +} diff --git a/src/Bundle/ChillTicketBundle/tests/Controller/AddCommentControllerTest.php b/src/Bundle/ChillTicketBundle/tests/Controller/AddCommentControllerTest.php new file mode 100644 index 000000000..2826c360d --- /dev/null +++ b/src/Bundle/ChillTicketBundle/tests/Controller/AddCommentControllerTest.php @@ -0,0 +1,104 @@ +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(), + ); + } +}