From 7c17d4f2cba6719839db99de3b36c0cc13f9c4f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 24 Jun 2025 15:41:56 +0200 Subject: [PATCH] Add SetCallerApiController with tests and API endpoint to manage ticket caller changes --- .../ChillTicketBundle/chill.api.specs.yaml | 62 +++++- .../src/Controller/SetCallerApiController.php | 66 ++++++ .../Controller/SetCallerApiControllerTest.php | 200 ++++++++++++++++++ 3 files changed, 324 insertions(+), 4 deletions(-) create mode 100644 src/Bundle/ChillTicketBundle/src/Controller/SetCallerApiController.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Controller/SetCallerApiControllerTest.php diff --git a/src/Bundle/ChillTicketBundle/chill.api.specs.yaml b/src/Bundle/ChillTicketBundle/chill.api.specs.yaml index 77bf2797d..aaa3b8df7 100644 --- a/src/Bundle/ChillTicketBundle/chill.api.specs.yaml +++ b/src/Bundle/ChillTicketBundle/chill.api.specs.yaml @@ -164,8 +164,8 @@ paths: type: array items: oneOf: - - $ref: '#/components/schemas/UserGroupById' - - $ref: '#/components/schemas/UserById' + - $ref: '#/components/schemas/UserGroupById' + - $ref: '#/components/schemas/UserById' responses: @@ -197,8 +197,8 @@ paths: properties: addressee: oneOf: - - $ref: '#/components/schemas/UserGroupById' - - $ref: '#/components/schemas/UserById' + - $ref: '#/components/schemas/UserGroupById' + - $ref: '#/components/schemas/UserById' responses: @@ -288,3 +288,57 @@ paths: description: "OK" 401: description: "UNAUTHORIZED" + + /1.0/ticket/ticket/{id}/set-caller: + post: + tags: + - ticket + summary: Set a caller for this ticket + description: | + Set a caller to the ticket. + + To remove the caller, set the caller field to null + 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: + caller: + nullable: true + oneOf: + - $ref: '#/components/schemas/PersonById' + - $ref: '#/components/schemas/ThirdPartyById' + examples: + add_user: + value: + caller: + type: person + id: 8 + summary: Set the person with id 8 + add_third_party: + value: + caller: + type: thirdparty + id: 10 + summary: Set the third party with id 10 + remove: + value: + caller: null + summary: Remove the caller (set the caller to null) + + responses: + 200: + description: "OK" + 401: + description: "UNAUTHORIZED" diff --git a/src/Bundle/ChillTicketBundle/src/Controller/SetCallerApiController.php b/src/Bundle/ChillTicketBundle/src/Controller/SetCallerApiController.php new file mode 100644 index 000000000..8393ca4d4 --- /dev/null +++ b/src/Bundle/ChillTicketBundle/src/Controller/SetCallerApiController.php @@ -0,0 +1,66 @@ + '\d+'], methods: ['POST'])] + public function setCaller(Ticket $ticket, Request $request): Response + { + if (!$this->security->isGranted('ROLE_USER')) { + throw new AccessDeniedHttpException('Only users are allowed to set ticket callers.'); + } + + try { + /** @var SetCallerCommand $command */ + $command = $this->serializer->deserialize( + $request->getContent(), + SetCallerCommand::class, + 'json' + ); + } catch (\Throwable $e) { + throw new BadRequestHttpException('Invalid request body: '.$e->getMessage(), $e); + } + + $this->setCallerCommandHandler->__invoke($ticket, $command); + + $this->entityManager->flush(); + + return new JsonResponse( + $this->serializer->serialize($ticket, 'json', ['groups' => ['read']]), + json: true + ); + } +} diff --git a/src/Bundle/ChillTicketBundle/tests/Controller/SetCallerApiControllerTest.php b/src/Bundle/ChillTicketBundle/tests/Controller/SetCallerApiControllerTest.php new file mode 100644 index 000000000..aad6342dd --- /dev/null +++ b/src/Bundle/ChillTicketBundle/tests/Controller/SetCallerApiControllerTest.php @@ -0,0 +1,200 @@ +prophesize(Security::class); + $security->isGranted('ROLE_USER')->willReturn(false); + + $setCallerCommandHandler = $this->prophesize(SetCallerCommandHandler::class); + $entityManager = $this->prophesize(EntityManagerInterface::class); + $serializer = $this->prophesize(SerializerInterface::class); + + $controller = new SetCallerApiController( + $setCallerCommandHandler->reveal(), + $security->reveal(), + $entityManager->reveal(), + $serializer->reveal(), + ); + + $this->expectException(AccessDeniedHttpException::class); + $controller->setCaller($ticket, $request); + } + + public function testSetCallerWithInvalidRequestBody(): void + { + $ticket = new Ticket(); + $request = new Request([], [], [], [], [], [], 'invalid json'); + + $security = $this->prophesize(Security::class); + $security->isGranted('ROLE_USER')->willReturn(true); + + $setCallerCommandHandler = $this->prophesize(SetCallerCommandHandler::class); + $entityManager = $this->prophesize(EntityManagerInterface::class); + + $serializer = $this->prophesize(SerializerInterface::class); + $serializer->deserialize('invalid json', SetCallerCommand::class, 'json') + ->willThrow(new \Exception('Invalid JSON')); + + $controller = new SetCallerApiController( + $setCallerCommandHandler->reveal(), + $security->reveal(), + $entityManager->reveal(), + $serializer->reveal(), + ); + + $this->expectException(BadRequestHttpException::class); + $controller->setCaller($ticket, $request); + } + + public function testSetCallerWithValidRequest(): void + { + $ticket = new Ticket(); + $request = new Request([], [], [], [], [], [], '{"caller": {"id": 123, "type": "person"}}'); + + $person = new Person(); + $command = new SetCallerCommand($person); + + $security = $this->prophesize(Security::class); + $security->isGranted('ROLE_USER')->willReturn(true); + + $serializer = $this->prophesize(SerializerInterface::class); + $serializer->deserialize('{"caller": {"id": 123, "type": "person"}}', SetCallerCommand::class, 'json') + ->willReturn($command); + $serializer->serialize($ticket, 'json', ['groups' => ['read']]) + ->willReturn('{}') + ->shouldBeCalled(); + + $setCallerCommandHandler = $this->prophesize(SetCallerCommandHandler::class); + $setCallerCommandHandler->__invoke($ticket, $command) + ->willReturn($ticket) + ->shouldBeCalled(); + + $entityManager = $this->prophesize(EntityManagerInterface::class); + $entityManager->flush()->shouldBeCalled(); + + $controller = new SetCallerApiController( + $setCallerCommandHandler->reveal(), + $security->reveal(), + $entityManager->reveal(), + $serializer->reveal(), + ); + + $response = $controller->setCaller($ticket, $request); + + $this->assertInstanceOf(JsonResponse::class, $response); + } + + public function testSetCallerWithThirdParty(): void + { + $ticket = new Ticket(); + $request = new Request([], [], [], [], [], [], '{"caller": {"id": 456, "type": "thirdParty"}}'); + + $thirdParty = $this->prophesize(ThirdParty::class)->reveal(); + $command = new SetCallerCommand($thirdParty); + + $security = $this->prophesize(Security::class); + $security->isGranted('ROLE_USER')->willReturn(true); + + $serializer = $this->prophesize(SerializerInterface::class); + $serializer->deserialize('{"caller": {"id": 456, "type": "thirdParty"}}', SetCallerCommand::class, 'json') + ->willReturn($command); + $serializer->serialize($ticket, 'json', ['groups' => ['read']]) + ->willReturn('{}') + ->shouldBeCalled(); + + $setCallerCommandHandler = $this->prophesize(SetCallerCommandHandler::class); + $setCallerCommandHandler->__invoke($ticket, $command) + ->willReturn($ticket) + ->shouldBeCalled(); + + $entityManager = $this->prophesize(EntityManagerInterface::class); + $entityManager->flush()->shouldBeCalled(); + + $controller = new SetCallerApiController( + $setCallerCommandHandler->reveal(), + $security->reveal(), + $entityManager->reveal(), + $serializer->reveal(), + ); + + $response = $controller->setCaller($ticket, $request); + + $this->assertInstanceOf(JsonResponse::class, $response); + } + + public function testSetCallerToNull(): void + { + $ticket = new Ticket(); + $request = new Request([], [], [], [], [], [], '{"caller": null}'); + + $command = new SetCallerCommand(null); + + $security = $this->prophesize(Security::class); + $security->isGranted('ROLE_USER')->willReturn(true); + + $serializer = $this->prophesize(SerializerInterface::class); + $serializer->deserialize('{"caller": null}', SetCallerCommand::class, 'json') + ->willReturn($command); + $serializer->serialize($ticket, 'json', ['groups' => ['read']]) + ->willReturn('{}') + ->shouldBeCalled(); + + $setCallerCommandHandler = $this->prophesize(SetCallerCommandHandler::class); + $setCallerCommandHandler->__invoke($ticket, $command) + ->willReturn($ticket) + ->shouldBeCalled(); + + $entityManager = $this->prophesize(EntityManagerInterface::class); + $entityManager->flush()->shouldBeCalled(); + + $controller = new SetCallerApiController( + $setCallerCommandHandler->reveal(), + $security->reveal(), + $entityManager->reveal(), + $serializer->reveal(), + ); + + $response = $controller->setCaller($ticket, $request); + + $this->assertInstanceOf(JsonResponse::class, $response); + } +}