5 * Contains \Drupal\Tests\Core\TypedData\RecursiveContextualValidatorTest.
8 namespace Drupal\Tests\Core\TypedData;
10 use Drupal\Core\Cache\NullBackend;
11 use Drupal\Core\DependencyInjection\ContainerBuilder;
12 use Drupal\Core\TypedData\DataDefinition;
13 use Drupal\Core\TypedData\MapDataDefinition;
14 use Drupal\Core\TypedData\TypedDataManager;
15 use Drupal\Core\TypedData\Validation\ExecutionContextFactory;
16 use Drupal\Core\TypedData\Validation\RecursiveValidator;
17 use Drupal\Core\Validation\ConstraintManager;
18 use Drupal\Tests\UnitTestCase;
19 use Symfony\Component\Validator\ConstraintValidatorFactory;
20 use Symfony\Component\Validator\Context\ExecutionContextInterface;
23 * @coversDefaultClass \Drupal\Core\TypedData\Validation\RecursiveContextualValidator
26 class RecursiveContextualValidatorTest extends UnitTestCase {
29 * The type data manager.
31 * @var \Drupal\Core\TypedData\TypedDataManager
33 protected $typedDataManager;
36 * The recursive validator.
38 * @var \Drupal\Core\TypedData\Validation\RecursiveValidator
40 protected $recursiveValidator;
43 * The validator factory.
45 * @var \Symfony\Component\Validator\ConstraintValidatorFactoryInterface
47 protected $validatorFactory;
50 * The execution context factory.
52 * @var \Drupal\Core\TypedData\Validation\ExecutionContextFactory
54 protected $contextFactory;
59 protected function setUp() {
62 $cache_backend = new NullBackend('cache');
63 $namespaces = new \ArrayObject([
64 'Drupal\\Core\\TypedData' => $this->root . '/core/lib/Drupal/Core/TypedData',
65 'Drupal\\Core\\Validation' => $this->root . '/core/lib/Drupal/Core/Validation',
67 $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandlerInterface')
68 ->disableOriginalConstructor()
70 $class_resolver = $this->getMockBuilder('Drupal\Core\DependencyInjection\ClassResolverInterface')
71 ->disableOriginalConstructor()
74 $this->typedDataManager = new TypedDataManager($namespaces, $cache_backend, $module_handler, $class_resolver);
75 $this->typedDataManager->setValidationConstraintManager(
76 new ConstraintManager($namespaces, $cache_backend, $module_handler)
78 // Typed data definitions access the manager in the container.
79 $container = new ContainerBuilder();
80 $container->set('typed_data_manager', $this->typedDataManager);
81 \Drupal::setContainer($container);
83 $translator = $this->getMock('Drupal\Core\Validation\TranslatorInterface');
84 $translator->expects($this->any())
86 ->willReturnCallback(function($id) {
89 $this->contextFactory = new ExecutionContextFactory($translator);
90 $this->validatorFactory = new ConstraintValidatorFactory();
91 $this->recursiveValidator = new RecursiveValidator($this->contextFactory, $this->validatorFactory, $this->typedDataManager);
95 * Ensures that passing an explicit group is not supported.
99 public function testValidateWithGroups() {
100 $this->setExpectedException(\LogicException::class);
101 $this->recursiveValidator->validate('test', NULL, 'test group');
105 * Ensures that passing a non typed data value is not supported.
109 public function testValidateWithoutTypedData() {
110 $this->setExpectedException(\InvalidArgumentException::class);
111 $this->recursiveValidator->validate('test');
117 public function testBasicValidateWithoutConstraints() {
118 $typed_data = $this->typedDataManager->create(DataDefinition::create('string'));
119 $violations = $this->recursiveValidator->validate($typed_data);
120 $this->assertCount(0, $violations);
126 public function testBasicValidateWithConstraint() {
127 $typed_data = $this->typedDataManager->create(
128 DataDefinition::create('string')
129 ->addConstraint('Callback', [
130 'callback' => function ($value, ExecutionContextInterface $context) {
131 $context->addViolation('test violation: ' . $value);
135 $typed_data->setValue('foo');
137 $violations = $this->recursiveValidator->validate($typed_data);
138 $this->assertCount(1, $violations);
139 // Ensure that the right value is passed into the validator.
140 $this->assertEquals('test violation: foo', $violations->get(0)->getMessage());
146 public function testBasicValidateWithMultipleConstraints() {
148 'callback' => function ($value, ExecutionContextInterface $context) {
149 $context->addViolation('test violation');
152 $typed_data = $this->typedDataManager->create(
153 DataDefinition::create('string')
154 ->addConstraint('Callback', $options)
155 ->addConstraint('NotNull')
157 $violations = $this->recursiveValidator->validate($typed_data);
158 $this->assertCount(2, $violations);
164 public function testPropertiesValidateWithMultipleLevels() {
166 $typed_data = $this->buildExampleTypedDataWithProperties();
168 $violations = $this->recursiveValidator->validate($typed_data);
169 $this->assertCount(6, $violations);
171 $this->assertEquals('violation: 3', $violations->get(0)->getMessage());
172 $this->assertEquals('violation: value1', $violations->get(1)->getMessage());
173 $this->assertEquals('violation: value2', $violations->get(2)->getMessage());
174 $this->assertEquals('violation: 2', $violations->get(3)->getMessage());
175 $this->assertEquals('violation: subvalue1', $violations->get(4)->getMessage());
176 $this->assertEquals('violation: subvalue2', $violations->get(5)->getMessage());
178 $this->assertEquals('', $violations->get(0)->getPropertyPath());
179 $this->assertEquals('key1', $violations->get(1)->getPropertyPath());
180 $this->assertEquals('key2', $violations->get(2)->getPropertyPath());
181 $this->assertEquals('key_with_properties', $violations->get(3)->getPropertyPath());
182 $this->assertEquals('key_with_properties.subkey1', $violations->get(4)->getPropertyPath());
183 $this->assertEquals('key_with_properties.subkey2', $violations->get(5)->getPropertyPath());
187 * Setups a typed data object used for test purposes.
190 * An array of value, constraints and properties.
192 * @return \Drupal\Core\TypedData\TypedDataInterface|\PHPUnit_Framework_MockObject_MockObject
194 protected function setupTypedData(array $tree, $name = '') {
195 $callback = function ($value, ExecutionContextInterface $context) {
196 $context->addViolation('violation: ' . (is_array($value) ? count($value) : $value));
199 $tree += ['constraints' => []];
201 if (isset($tree['properties'])) {
202 $map_data_definition = MapDataDefinition::create();
203 $map_data_definition->addConstraint('Callback', ['callback' => $callback]);
204 foreach ($tree['properties'] as $property_name => $property) {
205 $sub_typed_data = $this->setupTypedData($property, $property_name);
206 $map_data_definition->setPropertyDefinition($property_name, $sub_typed_data->getDataDefinition());
208 $typed_data = $this->typedDataManager->create(
209 $map_data_definition,
215 /** @var \Drupal\Core\TypedData\TypedDataInterface $typed_data */
216 $typed_data = $this->typedDataManager->create(
217 DataDefinition::create('string')
218 ->addConstraint('Callback', ['callback' => $callback]),
228 * @covers ::validateProperty
230 public function testValidatePropertyWithCustomGroup() {
234 'key1' => ['value' => 'value1'],
237 $typed_data = $this->setupTypedData($tree, 'test_name');
238 $this->setExpectedException(\LogicException::class);
239 $this->recursiveValidator->validateProperty($typed_data, 'key1', 'test group');
243 * @covers ::validateProperty
245 * @dataProvider providerTestValidatePropertyWithInvalidObjects
247 public function testValidatePropertyWithInvalidObjects($object) {
248 $this->setExpectedException(\InvalidArgumentException::class);
249 $this->recursiveValidator->validateProperty($object, 'key1', NULL);
253 * Provides data for testValidatePropertyWithInvalidObjects.
256 public function providerTestValidatePropertyWithInvalidObjects() {
258 $data[] = [new \stdClass()];
259 $data[] = [new TestClass()];
261 $data[] = [$this->getMock('Drupal\Core\TypedData\TypedDataInterface')];
267 * @covers ::validateProperty
269 public function testValidateProperty() {
270 $typed_data = $this->buildExampleTypedDataWithProperties();
272 $violations = $this->recursiveValidator->validateProperty($typed_data, 'key_with_properties');
273 $this->assertCount(3, $violations);
275 $this->assertEquals('violation: 2', $violations->get(0)->getMessage());
276 $this->assertEquals('violation: subvalue1', $violations->get(1)->getMessage());
277 $this->assertEquals('violation: subvalue2', $violations->get(2)->getMessage());
279 $this->assertEquals('', $violations->get(0)->getPropertyPath());
280 $this->assertEquals('subkey1', $violations->get(1)->getPropertyPath());
281 $this->assertEquals('subkey2', $violations->get(2)->getPropertyPath());
285 * @covers ::validatePropertyValue
287 * @dataProvider providerTestValidatePropertyWithInvalidObjects
289 public function testValidatePropertyValueWithInvalidObjects($object) {
290 $this->setExpectedException(\InvalidArgumentException::class);
291 $this->recursiveValidator->validatePropertyValue($object, 'key1', [], NULL);
295 * @covers ::validatePropertyValue
297 public function testValidatePropertyValue() {
298 $typed_data = $this->buildExampleTypedDataWithProperties(['subkey1' => 'subvalue11', 'subkey2' => 'subvalue22']);
300 $violations = $this->recursiveValidator->validatePropertyValue($typed_data, 'key_with_properties', $typed_data->get('key_with_properties'));
301 $this->assertCount(3, $violations);
303 $this->assertEquals('violation: 2', $violations->get(0)->getMessage());
304 $this->assertEquals('violation: subvalue11', $violations->get(1)->getMessage());
305 $this->assertEquals('violation: subvalue22', $violations->get(2)->getMessage());
307 $this->assertEquals('', $violations->get(0)->getPropertyPath());
308 $this->assertEquals('subkey1', $violations->get(1)->getPropertyPath());
309 $this->assertEquals('subkey2', $violations->get(2)->getPropertyPath());
313 * Builds some example type data object.
315 * @return \Drupal\Core\TypedData\TypedDataInterface|\PHPUnit_Framework_MockObject_MockObject
317 protected function buildExampleTypedDataWithProperties($subkey_value = NULL) {
318 $subkey_value = $subkey_value ?: ['subkey1' => 'subvalue1', 'subkey2' => 'subvalue2'];
323 'key_with_properties' => $subkey_value
326 $tree['properties'] = [
333 'key_with_properties' => [
334 'value' => $subkey_value ?: ['subkey1' => 'subvalue1', 'subkey2' => 'subvalue2'],
337 $tree['properties']['key_with_properties']['properties']['subkey1'] = ['value' => $tree['properties']['key_with_properties']['value']['subkey1']];
338 $tree['properties']['key_with_properties']['properties']['subkey2'] = ['value' => $tree['properties']['key_with_properties']['value']['subkey2']];
340 return $this->setupTypedData($tree, 'test_name');