Files
chill-bundles/src/Bundle/ChillPersonBundle/Tests/PersonIdentifier/Validator/UniqueIdentifierConstraintValidatorTest.php
Julien Fastré ad2b6d63ac Handle identifier uniqueness validation for same Person in UniqueIdentifierConstraintValidator
- Add logic to skip validation errors for duplicate identifiers belonging to the same Person.
- Update test case to verify no violation is raised for such duplicates.
- Refactor repository query logic to support the new validation scenario.
2025-09-26 13:55:40 +02:00

159 lines
5.8 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\PersonIdentifier\Validator;
use Chill\PersonBundle\Entity\Identifier\PersonIdentifier;
use Chill\PersonBundle\Entity\Identifier\PersonIdentifierDefinition;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\PersonIdentifier\Validator\UniqueIdentifierConstraint;
use Chill\PersonBundle\PersonIdentifier\Validator\UniqueIdentifierConstraintValidator;
use Chill\PersonBundle\Repository\Identifier\PersonIdentifierRepository;
use Chill\PersonBundle\Templating\Entity\PersonRenderInterface;
use PHPUnit\Framework\Attributes\CoversClass;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
/**
* @internal
*/
#[CoversClass(UniqueIdentifierConstraintValidator::class)]
final class UniqueIdentifierConstraintValidatorTest extends ConstraintValidatorTestCase
{
use ProphecyTrait;
/**
* @var ObjectProphecy|PersonIdentifierRepository
*/
private ObjectProphecy $repository;
/**
* @var ObjectProphecy|PersonRenderInterface
*/
private ObjectProphecy $personRender;
protected function setUp(): void
{
$this->repository = $this->prophesize(PersonIdentifierRepository::class);
$this->personRender = $this->prophesize(PersonRenderInterface::class);
parent::setUp();
}
protected function createValidator(): UniqueIdentifierConstraintValidator
{
return new UniqueIdentifierConstraintValidator($this->repository->reveal(), $this->personRender->reveal());
}
public function testThrowsOnInvalidConstraintType(): void
{
$this->expectException(UnexpectedTypeException::class);
// Provide a valid value so execution reaches the constraint type check
$definition = new PersonIdentifierDefinition(['en' => 'Test'], 'string');
$identifier = new PersonIdentifier($definition);
$identifier->setValue(['value' => 'ABC']);
$this->validator->validate($identifier, new NotBlank());
}
public function testThrowsOnInvalidValueType(): void
{
$this->expectException(UnexpectedValueException::class);
$this->validator->validate(new \stdClass(), new UniqueIdentifierConstraint());
}
public function testNoViolationWhenNoDuplicate(): void
{
$definition = new PersonIdentifierDefinition(['en' => 'Test'], 'string');
$identifier = new PersonIdentifier($definition);
$identifier->setValue(['value' => 'UNIQ']);
// Configure repository mock to return empty array
$this->repository->findByDefinitionAndCanonical($definition, ['value' => 'UNIQ'])->willReturn([]);
$this->validator->validate($identifier, new UniqueIdentifierConstraint());
$this->assertNoViolation();
}
public function testViolationWhenDuplicateFound(): void
{
$definition = new PersonIdentifierDefinition(['en' => 'SSN'], 'string');
$reflectionClass = new \ReflectionClass($definition);
$reflectionId = $reflectionClass->getProperty('id');
$reflectionId->setValue($definition, 1);
$personA = new Person();
$personA->setFirstName('Alice')->setLastName('Anderson');
$personB = new Person();
$personB->setFirstName('Bob')->setLastName('Brown');
$dup1 = new PersonIdentifier($definition);
$dup1->setPerson($personA);
$dup1->setValue(['value' => '123']);
$dup2 = new PersonIdentifier($definition);
$dup2->setPerson($personB);
$dup2->setValue(['value' => '123']);
// Repository returns duplicates
$this->repository->findByDefinitionAndCanonical($definition, ['value' => '123'])->willReturn([$dup1, $dup2]);
// Person renderer returns names
$this->personRender->renderString($personA, Argument::type('array'))->willReturn('Alice Anderson');
$this->personRender->renderString($personB, Argument::type('array'))->willReturn('Bob Brown');
$identifier = new PersonIdentifier($definition);
$identifier->setPerson(new Person());
$identifier->setValue(['value' => '123']);
$constraint = new UniqueIdentifierConstraint();
$this->validator->validate($identifier, $constraint);
$this->buildViolation($constraint->message)
->setParameter('{{ persons }}', 'Alice Anderson, Bob Brown')
->setParameter('definition_id', '1')
->assertRaised();
}
public function testViolationWhenDuplicateFoundButForSamePerson(): void
{
$definition = new PersonIdentifierDefinition(['en' => 'SSN'], 'string');
$reflectionClass = new \ReflectionClass($definition);
$reflectionId = $reflectionClass->getProperty('id');
$reflectionId->setValue($definition, 1);
$personA = new Person();
$personA->setFirstName('Alice')->setLastName('Anderson');
$dup1 = new PersonIdentifier($definition);
$dup1->setPerson($personA);
$dup1->setValue(['value' => '123']);
// Repository returns duplicates
$this->repository->findByDefinitionAndCanonical($definition, ['value' => '123'])->willReturn([$dup1]);
$identifier = new PersonIdentifier($definition);
$identifier->setPerson($personA);
$identifier->setValue(['value' => '123']);
$constraint = new UniqueIdentifierConstraint();
$this->validator->validate($identifier, $constraint);
$this->assertNoViolation();
}
}