From d42a1296c49468aeb65a45582bb521bb83f86f80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 25 Sep 2025 14:51:39 +0200 Subject: [PATCH] Add integration and unit tests for `PersonJsonNormalizer` to verify normalization behavior - Introduce `PersonJsonNormalizerIntegrationTest` to test database-driven normalization scenarios. - Expand `PersonJsonNormalizerTest` with cases covering minimal group normalization and extended keys. - Refactor test setup to use mock objects and improve coverage of normalization logic. --- .../Normalizer/PersonJsonNormalizer.php | 5 - .../PersonJsonNormalizerIntegrationTest.php | 63 ++++++ .../Normalizer/PersonJsonNormalizerTest.php | 189 ++++++++++++++---- 3 files changed, 210 insertions(+), 47 deletions(-) create mode 100644 src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonJsonNormalizerIntegrationTest.php diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonJsonNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonJsonNormalizer.php index be8c693cd..1867b16ad 100644 --- a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonJsonNormalizer.php +++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonJsonNormalizer.php @@ -78,11 +78,6 @@ class PersonJsonNormalizer implements NormalizerAwareInterface, NormalizerInterf null]; } - public function supportsDenormalization($data, $type, $format = null) - { - return Person::class === $type && 'person' === ($data['type'] ?? null); - } - public function supportsNormalization($data, $format = null): bool { return $data instanceof Person && 'json' === $format; diff --git a/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonJsonNormalizerIntegrationTest.php b/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonJsonNormalizerIntegrationTest.php new file mode 100644 index 000000000..3fff8aa97 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonJsonNormalizerIntegrationTest.php @@ -0,0 +1,63 @@ +get(PersonRepository::class); + $person = $repo->findOneBy([]); + + if (!$person instanceof Person) { + self::markTestSkipped('No person found in test database. Load fixtures to enable this test.'); + } + + /** @var SerializerInterface $serializer */ + $serializer = $container->get(SerializerInterface::class); + + // Should not throw + $data = $serializer->normalize($person, 'json'); + Assert::assertIsArray($data); + + // Spot check some expected keys exist + foreach ([ + 'type', 'id', 'text', 'textAge', 'firstName', 'lastName', 'birthdate', 'age', 'gender', 'civility', + ] as $key) { + Assert::assertArrayHasKey($key, $data, sprintf('Expected key %s in normalized payload', $key)); + } + + // Minimal group should also work + $minimal = $serializer->normalize($person, 'json', ['groups' => 'minimal']); + Assert::assertIsArray($minimal); + foreach ([ + 'type', 'id', 'text', 'textAge', 'firstName', 'lastName', + ] as $key) { + Assert::assertArrayHasKey($key, $minimal, sprintf('Expected key %s in minimal normalized payload', $key)); + } + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonJsonNormalizerTest.php b/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonJsonNormalizerTest.php index af78e2475..501b42507 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonJsonNormalizerTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonJsonNormalizerTest.php @@ -11,74 +11,179 @@ declare(strict_types=1); namespace Serializer\Normalizer; +use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Phonenumber\PhoneNumberHelperInterface; use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface; use Chill\MainBundle\Templating\Entity\ChillEntityRenderExtension; use Chill\PersonBundle\Entity\Person; -use Chill\PersonBundle\Repository\PersonRepository; +use Chill\PersonBundle\Entity\PersonAltName; use Chill\PersonBundle\Repository\ResidentialAddressRepository; use Chill\PersonBundle\Serializer\Normalizer\PersonJsonNormalizer; +use Doctrine\Common\Collections\ArrayCollection; +use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; -use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; /** * @internal * - * @coversNothing + * @covers \Chill\PersonBundle\Serializer\Normalizer\PersonJsonNormalizer */ -final class PersonJsonNormalizerTest extends KernelTestCase +final class PersonJsonNormalizerTest extends TestCase { use ProphecyTrait; - private PersonJsonNormalizer $normalizer; - - protected function setUp(): void + public function testSupportsNormalization(): void { - self::bootKernel(); + $normalizer = $this->createNormalizer(); - $residentialAddressRepository = $this->prophesize(ResidentialAddressRepository::class); - $residentialAddressRepository - ->findCurrentResidentialAddressByPerson(Argument::type(Person::class), Argument::any()) - ->willReturn([]); - - $this->normalizer = $this->buildPersonJsonNormalizer( - self::getContainer()->get(ChillEntityRenderExtension::class), - self::getContainer()->get(PersonRepository::class), - self::getContainer()->get(CenterResolverManagerInterface::class), - $residentialAddressRepository->reveal(), - self::getContainer()->get(PhoneNumberHelperInterface::class), - self::getContainer()->get(NormalizerInterface::class) - ); + self::assertTrue($normalizer->supportsNormalization(new Person(), 'json')); + self::assertFalse($normalizer->supportsNormalization(new \stdClass(), 'json')); + self::assertFalse($normalizer->supportsNormalization(new Person(), 'xml')); } - public function testNormalization() + public function testNormalizeWithMinimalGroupReturnsOnlyBaseKeys(): void { - $person = new Person(); - $result = $this->normalizer->normalize($person, 'json', [AbstractNormalizer::GROUPS => ['read']]); + $person = $this->createSamplePerson(); - $this->assertIsArray($result); + $normalizer = $this->createNormalizer(); + $data = $normalizer->normalize($person, 'json', [AbstractNormalizer::GROUPS => 'minimal']); + + // Expected base keys + $expectedKeys = [ + 'type', + 'id', + 'text', + 'textAge', + 'firstName', + 'lastName', + 'current_household_address', + 'birthdate', + 'deathdate', + 'age', + 'phonenumber', + 'mobilenumber', + 'email', + 'gender', + 'civility', + ]; + + foreach ($expectedKeys as $key) { + self::assertArrayHasKey($key, $data, sprintf('Key %s should be present', $key)); + } + + // Ensure extended keys are not present in minimal mode + foreach (['centers', 'altNames', 'current_household_id', 'current_residential_addresses'] as $key) { + self::assertArrayNotHasKey($key, $data, sprintf('Key %s should NOT be present in minimal group', $key)); + } } - private function buildPersonJsonNormalizer( - ChillEntityRenderExtension $render, - PersonRepository $repository, - CenterResolverManagerInterface $centerResolverManager, - ResidentialAddressRepository $residentialAddressRepository, - PhoneNumberHelperInterface $phoneNumberHelper, - NormalizerInterface $normalizer, - ): PersonJsonNormalizer { - $personJsonNormalizer = new PersonJsonNormalizer( - $render, - $repository, - $centerResolverManager, - $residentialAddressRepository, - $phoneNumberHelper - ); - $personJsonNormalizer->setNormalizer($normalizer); + public function testNormalizeWithoutGroupsIncludesExtendedKeys(): void + { + $person = $this->createSamplePerson(withAltNames: true); - return $personJsonNormalizer; + $center1 = (new Center())->setName('c1'); + $center2 = (new Center())->setName('c2'); + + + $normalizer = $this->createNormalizer( + centers: [$center1, $center2], + currentResidentialAddresses: [['addr' => 1]], + ); + + $data = $normalizer->normalize($person, 'json'); + + // Base keys + $baseKeys = [ + 'type', 'id', 'text', 'textAge', 'firstName', 'lastName', 'current_household_address', 'birthdate', 'deathdate', 'age', 'phonenumber', 'mobilenumber', 'email', 'gender', 'civility', + ]; + foreach ($baseKeys as $key) { + self::assertArrayHasKey($key, $data, sprintf('Key %s should be present', $key)); + } + + // Extended keys + foreach (['centers', 'altNames', 'current_household_id', 'current_residential_addresses'] as $key) { + self::assertArrayHasKey($key, $data, sprintf('Key %s should be present', $key)); + } + + self::assertSame(['c1', 'c2'], $data['centers']); + self::assertIsArray($data['altNames']); + self::assertSame([['key' => 'aka', 'label' => 'Johnny']], $data['altNames']); + self::assertNull($data['current_household_id'], 'No household set so id should be null'); + self::assertSame([['addr' => 1]], $data['current_residential_addresses']); + } + + private function createNormalizer(array $centers = [], array $currentResidentialAddresses = []): PersonJsonNormalizer + { + $render = $this->prophesize(ChillEntityRenderExtension::class); + $render->renderString(Argument::type(Person::class), ['addAge' => false])->willReturn('John Doe'); + $render->renderString(Argument::type(Person::class), ['addAge' => true])->willReturn('John Doe (25)'); + + $centerResolver = $this->prophesize(CenterResolverManagerInterface::class); + $centerResolver->resolveCenters(Argument::type(Person::class))->willReturn($centers); + + $raRepo = $this->prophesize(ResidentialAddressRepository::class); + $raRepo->findCurrentResidentialAddressByPerson(Argument::type(Person::class))->willReturn($currentResidentialAddresses); + + $phoneHelper = $this->prophesize(PhoneNumberHelperInterface::class); + + $normalizer = new PersonJsonNormalizer( + $render->reveal(), + $centerResolver->reveal(), + $raRepo->reveal(), + $phoneHelper->reveal(), + ); + + // Inner normalizer that echoes values or simple conversions + $inner = new class () implements NormalizerInterface { + public function supportsNormalization($data, $format = null): bool + { + return true; + } + + public function normalize($object, $format = null, array $context = []) + { + // For scalars and arrays, return as-is; for objects, return string or id when possible + if (\is_scalar($object) || null === $object) { + return $object; + } + if ($object instanceof \DateTimeInterface) { + return $object->format('Y-m-d'); + } + if ($object instanceof Center) { + return $object->getName(); + } + if (is_array($object)) { + return array_map(fn ($o) => $this->normalize($o, $format, $context), $object); + } + + // default stub + return (string) (method_exists($object, 'getId') ? $object->getId() : 'normalized'); + } + }; + + $normalizer->setNormalizer($inner); + + return $normalizer; + } + + private function createSamplePerson(bool $withAltNames = false): Person + { + $p = new Person(); + $p->setFirstName('John'); + $p->setLastName('Doe'); + $p->setBirthdate(new \DateTime('2000-01-01')); + $p->setEmail('john@example.test'); + + if ($withAltNames) { + $alt = new PersonAltName(); + $alt->setKey('aka'); + $alt->setLabel('Johnny'); + $p->setAltNames(new ArrayCollection([$alt])); + } + + return $p; } }