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(); } }