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.
This commit is contained in:
2025-09-25 14:51:39 +02:00
parent 4b7e3c1601
commit d42a1296c4
3 changed files with 210 additions and 47 deletions

View File

@@ -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;

View File

@@ -0,0 +1,63 @@
<?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 Serializer\Normalizer;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Repository\PersonRepository;
use PHPUnit\Framework\Assert;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Serializer\SerializerInterface;
/**
* @internal
*
* @coversNothing
*/
final class PersonJsonNormalizerIntegrationTest extends KernelTestCase
{
public function testNormalizeExistingPersonFromDatabase(): void
{
self::bootKernel();
$container = self::getContainer();
/** @var PersonRepository $repo */
$repo = $container->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));
}
}
}

View File

@@ -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;
}
}