mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-26 00:24:59 +00:00
303 lines
11 KiB
PHP
303 lines
11 KiB
PHP
<?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 Chill\PersonBundle\Tests\Serializer\Normalizer;
|
|
|
|
use Chill\MainBundle\Entity\Center;
|
|
use Chill\MainBundle\Entity\Civility;
|
|
use Chill\MainBundle\Entity\Gender;
|
|
use Chill\PersonBundle\Entity\Identifier\PersonIdentifier;
|
|
use Chill\PersonBundle\Entity\Identifier\PersonIdentifierDefinition;
|
|
use Chill\PersonBundle\Entity\Person;
|
|
use Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface;
|
|
use Chill\PersonBundle\PersonIdentifier\PersonIdentifierManagerInterface;
|
|
use Chill\PersonBundle\PersonIdentifier\PersonIdentifierWorker;
|
|
use Chill\PersonBundle\Serializer\Normalizer\PersonJsonDenormalizer;
|
|
use libphonenumber\PhoneNumber;
|
|
use PHPUnit\Framework\TestCase;
|
|
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
|
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
|
|
|
/**
|
|
* @internal
|
|
*
|
|
* @covers \Chill\PersonBundle\Serializer\Normalizer\PersonJsonDenormalizer
|
|
*/
|
|
final class PersonJsonDenormalizerTest extends TestCase
|
|
{
|
|
private function createIdentifierManager(): PersonIdentifierManagerInterface
|
|
{
|
|
return new class () implements PersonIdentifierManagerInterface {
|
|
public function getWorkers(): array
|
|
{
|
|
return [];
|
|
}
|
|
|
|
public function buildWorkerByPersonIdentifierDefinition(int|PersonIdentifierDefinition $personIdentifierDefinition): PersonIdentifierWorker
|
|
{
|
|
if (is_int($personIdentifierDefinition)) {
|
|
$definition = new PersonIdentifierDefinition(['en' => 'Test'], 'dummy');
|
|
// Force the id for testing purposes
|
|
$r = new \ReflectionProperty(PersonIdentifierDefinition::class, 'id');
|
|
$r->setAccessible(true);
|
|
$r->setValue($definition, $personIdentifierDefinition);
|
|
} else {
|
|
$definition = $personIdentifierDefinition;
|
|
}
|
|
|
|
$engine = new class () implements PersonIdentifierEngineInterface {
|
|
public static function getName(): string
|
|
{
|
|
return 'dummy';
|
|
}
|
|
|
|
public function canonicalizeValue(array $value, PersonIdentifierDefinition $definition): ?string
|
|
{
|
|
// trivial canonicalization for tests
|
|
return isset($value['content']) ? (string) $value['content'] : null;
|
|
}
|
|
|
|
public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder, PersonIdentifierDefinition $personIdentifierDefinition): void {}
|
|
|
|
public function renderAsString(?PersonIdentifier $identifier, PersonIdentifierDefinition $definition): string
|
|
{
|
|
return '';
|
|
}
|
|
|
|
public function isEmpty(PersonIdentifier $identifier): bool
|
|
{
|
|
$value = $identifier->getValue();
|
|
$content = isset($value['content']) ? trim((string) $value['content']) : '';
|
|
|
|
return '' === $content;
|
|
}
|
|
};
|
|
|
|
return new PersonIdentifierWorker($engine, $definition);
|
|
}
|
|
};
|
|
}
|
|
|
|
public function testSupportsDenormalizationReturnsTrueForValidData(): void
|
|
{
|
|
$denormalizer = new PersonJsonDenormalizer($this->createIdentifierManager());
|
|
|
|
$data = [
|
|
'type' => 'person',
|
|
// important: new Person (creation) must not contain an id
|
|
];
|
|
|
|
self::assertTrue($denormalizer->supportsDenormalization($data, Person::class));
|
|
}
|
|
|
|
public function testSupportsDenormalizationReturnsFalseForInvalidData(): void
|
|
{
|
|
$denormalizer = new PersonJsonDenormalizer($this->createIdentifierManager());
|
|
|
|
// not an array
|
|
self::assertFalse($denormalizer->supportsDenormalization('not-an-array', Person::class));
|
|
|
|
// missing type
|
|
self::assertFalse($denormalizer->supportsDenormalization([], Person::class));
|
|
|
|
// wrong type value
|
|
self::assertFalse($denormalizer->supportsDenormalization(['type' => 'not-person'], Person::class));
|
|
|
|
// id present means it's not a create payload for this denormalizer
|
|
self::assertFalse($denormalizer->supportsDenormalization(['type' => 'person', 'id' => 123], Person::class));
|
|
|
|
// wrong target class
|
|
self::assertFalse($denormalizer->supportsDenormalization(['type' => 'person'], \stdClass::class));
|
|
}
|
|
|
|
public function testDenormalizeMapsPayloadToPersonProperties(): void
|
|
{
|
|
$json = <<<'JSON'
|
|
{
|
|
"type": "person",
|
|
"firstName": "Jérome",
|
|
"lastName": "diallo",
|
|
"altNames": [
|
|
{
|
|
"key": "jeune_fille",
|
|
"value": "FJ"
|
|
}
|
|
],
|
|
"birthdate": null,
|
|
"deathdate": null,
|
|
"phonenumber": "",
|
|
"mobilenumber": "",
|
|
"email": "",
|
|
"gender": {
|
|
"id": 5,
|
|
"type": "chill_main_gender"
|
|
},
|
|
"center": {
|
|
"id": 1,
|
|
"type": "center"
|
|
},
|
|
"civility": null,
|
|
"identifiers": [
|
|
{
|
|
"type": "person_identifier",
|
|
"value": {
|
|
"content": "789456"
|
|
},
|
|
"definition_id": 5
|
|
}
|
|
]
|
|
}
|
|
JSON;
|
|
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
|
|
|
|
$inner = new class () implements DenormalizerInterface {
|
|
public ?Gender $gender = null;
|
|
public ?Center $center = null;
|
|
|
|
public function denormalize($data, $type, $format = null, array $context = [])
|
|
{
|
|
if (PhoneNumber::class === $type) {
|
|
return '' === $data ? null : new PhoneNumber();
|
|
}
|
|
if (\DateTime::class === $type || \DateTimeImmutable::class === $type) {
|
|
return null === $data ? null : new \DateTimeImmutable((string) $data);
|
|
}
|
|
if (Gender::class === $type) {
|
|
return $this->gender ??= new Gender();
|
|
}
|
|
if (Center::class === $type) {
|
|
return $this->center ??= new Center();
|
|
}
|
|
if (Civility::class === $type) {
|
|
return null; // input is null in our payload
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function supportsDenormalization($data, $type, $format = null)
|
|
{
|
|
return true;
|
|
}
|
|
};
|
|
|
|
$denormalizer = new PersonJsonDenormalizer($this->createIdentifierManager());
|
|
$denormalizer->setDenormalizer($inner);
|
|
|
|
$person = $denormalizer->denormalize($data, Person::class);
|
|
|
|
self::assertInstanceOf(Person::class, $person);
|
|
self::assertSame('Jérome', $person->getFirstName());
|
|
self::assertSame('diallo', $person->getLastName());
|
|
|
|
// phone numbers: empty strings map to null via the inner denormalizer stub
|
|
self::assertNull($person->getPhonenumber());
|
|
self::assertNull($person->getMobilenumber());
|
|
|
|
// email passes through as is
|
|
self::assertSame('', $person->getEmail());
|
|
|
|
// nested objects are provided by our inner denormalizer and must be set back on the Person
|
|
self::assertSame($inner->gender, $person->getGender());
|
|
self::assertSame($inner->center, $person->getCenter());
|
|
|
|
// dates are null in the provided payload
|
|
self::assertNull($person->getBirthdate());
|
|
self::assertNull($person->getDeathdate());
|
|
|
|
// civility is null as provided
|
|
self::assertNull($person->getCivility());
|
|
|
|
// altNames: make sure the alt name with key jeune_fille has label FJ
|
|
$found = false;
|
|
foreach ($person->getAltNames() as $altName) {
|
|
if ('jeune_fille' === $altName->getKey()) {
|
|
$found = true;
|
|
self::assertSame('FJ', $altName->getLabel());
|
|
}
|
|
}
|
|
self::assertTrue($found, 'Expected altNames to contain key "jeune_fille"');
|
|
|
|
$found = false;
|
|
foreach ($person->getIdentifiers() as $identifier) {
|
|
if (5 === $identifier->getDefinition()->getId()) {
|
|
$found = true;
|
|
self::assertSame(['content' => '789456'], $identifier->getValue());
|
|
}
|
|
}
|
|
self::assertTrue($found, 'Expected identifiers with definition id 5');
|
|
}
|
|
|
|
public function testDenormalizeRemovesEmptyIdentifier(): void
|
|
{
|
|
$data = [
|
|
'type' => 'person',
|
|
'firstName' => 'Alice',
|
|
'lastName' => 'Smith',
|
|
'identifiers' => [
|
|
[
|
|
'type' => 'person_identifier',
|
|
'value' => ['content' => ''],
|
|
'definition_id' => 7,
|
|
],
|
|
],
|
|
];
|
|
|
|
$denormalizer = new PersonJsonDenormalizer($this->createIdentifierManager());
|
|
|
|
$person = $denormalizer->denormalize($data, Person::class);
|
|
|
|
// The identifier with empty content must be considered empty and removed
|
|
self::assertSame(0, $person->getIdentifiers()->count(), 'Expected no identifiers to remain on the person');
|
|
}
|
|
|
|
public function testDenormalizeRemovesPreviouslyExistingIdentifierWhenIncomingValueIsEmpty(): void
|
|
{
|
|
// Prepare an existing Person with a pre-existing identifier (definition id = 9)
|
|
$definition = new PersonIdentifierDefinition(['en' => 'Test'], 'dummy');
|
|
$ref = new \ReflectionProperty(PersonIdentifierDefinition::class, 'id');
|
|
$ref->setValue($definition, 9);
|
|
|
|
$existingIdentifier = new PersonIdentifier($definition);
|
|
$existingIdentifier->setValue(['content' => 'ABC']);
|
|
|
|
$person = new Person();
|
|
$person->addIdentifier($existingIdentifier);
|
|
|
|
// Also set the identifier's own id = 9 so that the denormalizer logic matches it
|
|
// (the current denormalizer matches by PersonIdentifier->getId() === definition_id)
|
|
$refId = new \ReflectionProperty(PersonIdentifier::class, 'id');
|
|
$refId->setValue($existingIdentifier, 9);
|
|
|
|
// Incoming payload sets the same definition id with an empty value
|
|
$data = [
|
|
'type' => 'person',
|
|
'identifiers' => [
|
|
[
|
|
'type' => 'person_identifier',
|
|
'value' => ['content' => ''],
|
|
'definition_id' => 9,
|
|
],
|
|
],
|
|
];
|
|
|
|
$denormalizer = new PersonJsonDenormalizer($this->createIdentifierManager());
|
|
|
|
// Use AbstractNormalizer::OBJECT_TO_POPULATE to update the existing person
|
|
$result = $denormalizer->denormalize($data, Person::class, null, [
|
|
AbstractNormalizer::OBJECT_TO_POPULATE => $person,
|
|
]);
|
|
|
|
self::assertSame($person, $result, 'Denormalizer should update and return the provided Person instance');
|
|
self::assertSame(0, $person->getIdentifiers()->count(), 'The previously existing identifier should be removed when incoming value is empty');
|
|
}
|
|
}
|