From a126f2f06d3fbb56bc0c7a924dd2fec51e219030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 15 Aug 2025 01:19:49 +0200 Subject: [PATCH] [WIP] Refactor AddressReferenceRepository to use interface and add tests for AddressReferenceAggregatedApiController --- ...ddressReferenceAggregatedApiController.php | 4 +- .../Repository/AddressReferenceRepository.php | 5 +- .../AddressReferenceRepositoryInterface.php | 20 +++ ...ssReferenceAggregatedApiControllerTest.php | 118 ++++++++++++++++++ 4 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Repository/AddressReferenceRepositoryInterface.php create mode 100644 src/Bundle/ChillMainBundle/Tests/Controller/AddressReferenceAggregatedApiControllerTest.php diff --git a/src/Bundle/ChillMainBundle/Controller/AddressReferenceAggregatedApiController.php b/src/Bundle/ChillMainBundle/Controller/AddressReferenceAggregatedApiController.php index 9976d939e..2533dfe81 100644 --- a/src/Bundle/ChillMainBundle/Controller/AddressReferenceAggregatedApiController.php +++ b/src/Bundle/ChillMainBundle/Controller/AddressReferenceAggregatedApiController.php @@ -11,7 +11,7 @@ declare(strict_types=1); namespace Chill\MainBundle\Controller; -use Chill\MainBundle\Repository\AddressReferenceRepository; +use Chill\MainBundle\Repository\AddressReferenceRepositoryInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; @@ -23,7 +23,7 @@ final readonly class AddressReferenceAggregatedApiController { public function __construct( private Security $security, - private AddressReferenceRepository $addressReferenceRepository, + private AddressReferenceRepositoryInterface $addressReferenceRepository, ) {} #[Route(path: '/api/1.0/main/address-reference/aggregated/search')] diff --git a/src/Bundle/ChillMainBundle/Repository/AddressReferenceRepository.php b/src/Bundle/ChillMainBundle/Repository/AddressReferenceRepository.php index 08e662e37..6877b8032 100644 --- a/src/Bundle/ChillMainBundle/Repository/AddressReferenceRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/AddressReferenceRepository.php @@ -20,9 +20,8 @@ use Doctrine\ORM\EntityRepository; use Doctrine\ORM\NativeQuery; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\ORM\Query\ResultSetMappingBuilder; -use Doctrine\Persistence\ObjectRepository; -final readonly class AddressReferenceRepository implements ObjectRepository +final readonly class AddressReferenceRepository implements AddressReferenceRepositoryInterface { private EntityManagerInterface $entityManager; @@ -77,7 +76,7 @@ final readonly class AddressReferenceRepository implements ObjectRepository $connection = $this->entityManager->getConnection(); $qb = $connection->createQueryBuilder(); - $qb->select('row_number() OVER () AS row_number','var.street AS street', 'cmpc.id AS postcode_id', 'cmpc.code AS code', 'cmpc.label AS label', 'jsonb_object_agg(var.address_id, var.streetnumber ORDER BY var.row_number) AS positions') + $qb->select('row_number() OVER () AS row_number', 'var.street AS street', 'cmpc.id AS postcode_id', 'cmpc.code AS code', 'cmpc.label AS label', 'jsonb_object_agg(var.address_id, var.streetnumber ORDER BY var.row_number) AS positions') ->from('view_chill_main_address_reference', 'var') ->innerJoin('var', 'chill_main_postal_code', 'cmpc', 'cmpc.id = var.postcode_id') ->groupBy('cmpc.id', 'var.street') diff --git a/src/Bundle/ChillMainBundle/Repository/AddressReferenceRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/AddressReferenceRepositoryInterface.php new file mode 100644 index 000000000..1843dc79b --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/AddressReferenceRepositoryInterface.php @@ -0,0 +1,20 @@ +prophesize(Security::class); + $security->isGranted('IS_AUTHENTICATED')->willReturn(false); + + $repo = $this->prophesize(AddressReferenceRepositoryInterface::class); + + $controller = new AddressReferenceAggregatedApiController($security->reveal(), $repo->reveal()); + + $request = new Request(query: ['q' => 'anything']); + + $this->expectException(AccessDeniedHttpException::class); + + $controller->search($request); + } + + public function testBadRequestWhenQueryIsMissing(): void + { + $security = $this->prophesize(Security::class); + $security->isGranted('IS_AUTHENTICATED')->willReturn(true); + + $repo = $this->prophesize(AddressReferenceRepositoryInterface::class); + + $controller = new AddressReferenceAggregatedApiController($security->reveal(), $repo->reveal()); + + $request = new Request(); + + $this->expectException(BadRequestHttpException::class); + $this->expectExceptionMessage('Parameter "q" is required.'); + + $controller->search($request); + } + + public function testBadRequestWhenQueryIsEmpty(): void + { + $security = $this->prophesize(Security::class); + $security->isGranted('IS_AUTHENTICATED')->willReturn(true); + + $repo = $this->prophesize(AddressReferenceRepositoryInterface::class); + + $controller = new AddressReferenceAggregatedApiController($security->reveal(), $repo->reveal()); + + $request = new Request(query: ['q' => ' ']); + + $this->expectException(BadRequestHttpException::class); + $this->expectExceptionMessage('Parameter "q" is required and cannot be empty.'); + + $controller->search($request); + } + + public function testSuccessfulSearchReturnsJsonAndCallsRepositoryWithTrimmedQuery(): void + { + $security = $this->prophesize(Security::class); + $security->isGranted('IS_AUTHENTICATED')->willReturn(true); + + $expectedQuery = 'foo'; + $iterableResult = new \ArrayIterator([ + [ + 'street' => 'Main Street', + 'postcode_id' => 123, + 'code' => '1000', + 'label' => 'Brussels', + 'positions' => ['1' => '12', '2' => '14'], + 'row_number' => 1, + ], + ]); + + $repo = $this->prophesize(AddressReferenceRepositoryInterface::class); + $repo->findAggregatedBySearchString($expectedQuery)->willReturn($iterableResult)->shouldBeCalledOnce(); + + $controller = new AddressReferenceAggregatedApiController($security->reveal(), $repo->reveal()); + + // Provide spaces around to ensure trimming is applied + $request = new Request(query: ['q' => " {$expectedQuery} "]); + + $response = $controller->search($request); + + self::assertSame(200, $response->getStatusCode()); + $data = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR); + self::assertIsArray($data); + self::assertCount(1, $data); + self::assertSame('Main Street', $data[0]['street']); + self::assertSame(123, $data[0]['postcode_id']); + self::assertSame('1000', $data[0]['code']); + self::assertSame('Brussels', $data[0]['label']); + } +}