08b81170343c0dcf518056906c1997b77dc7e2cc
[yaffs-website] / web / core / tests / Drupal / Tests / Core / TypedData / RecursiveContextualValidatorTest.php
1 <?php
2
3 /**
4  * @file
5  * Contains \Drupal\Tests\Core\TypedData\RecursiveContextualValidatorTest.
6  */
7
8 namespace Drupal\Tests\Core\TypedData;
9
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;
21
22 /**
23  * @coversDefaultClass \Drupal\Core\TypedData\Validation\RecursiveContextualValidator
24  * @group typedData
25  */
26 class RecursiveContextualValidatorTest extends UnitTestCase {
27
28   /**
29    * The type data manager.
30    *
31    * @var \Drupal\Core\TypedData\TypedDataManager
32    */
33   protected $typedDataManager;
34
35   /**
36    * The recursive validator.
37    *
38    * @var \Drupal\Core\TypedData\Validation\RecursiveValidator
39    */
40   protected $recursiveValidator;
41
42   /**
43    * The validator factory.
44    *
45    * @var \Symfony\Component\Validator\ConstraintValidatorFactoryInterface
46    */
47   protected $validatorFactory;
48
49   /**
50    * The execution context factory.
51    *
52    * @var \Drupal\Core\TypedData\Validation\ExecutionContextFactory
53    */
54   protected $contextFactory;
55
56   /**
57    * {@inheritdoc}
58    */
59   protected function setUp() {
60     parent::setUp();
61
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',
66     ]);
67     $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandlerInterface')
68       ->disableOriginalConstructor()
69       ->getMock();
70     $class_resolver = $this->getMockBuilder('Drupal\Core\DependencyInjection\ClassResolverInterface')
71       ->disableOriginalConstructor()
72       ->getMock();
73
74     $this->typedDataManager = new TypedDataManager($namespaces, $cache_backend, $module_handler, $class_resolver);
75     $this->typedDataManager->setValidationConstraintManager(
76       new ConstraintManager($namespaces, $cache_backend, $module_handler)
77     );
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);
82
83     $translator = $this->getMock('Drupal\Core\Validation\TranslatorInterface');
84     $translator->expects($this->any())
85       ->method('trans')
86       ->willReturnCallback(function($id) {
87         return $id;
88       });
89     $this->contextFactory = new ExecutionContextFactory($translator);
90     $this->validatorFactory = new ConstraintValidatorFactory();
91     $this->recursiveValidator = new RecursiveValidator($this->contextFactory, $this->validatorFactory, $this->typedDataManager);
92   }
93
94   /**
95    * Ensures that passing an explicit group is not supported.
96    *
97    * @covers ::validate
98    */
99   public function testValidateWithGroups() {
100     $this->setExpectedException(\LogicException::class);
101     $this->recursiveValidator->validate('test', NULL, 'test group');
102   }
103
104   /**
105    * Ensures that passing a non typed data value is not supported.
106    *
107    * @covers ::validate
108    */
109   public function testValidateWithoutTypedData() {
110     $this->setExpectedException(\InvalidArgumentException::class);
111     $this->recursiveValidator->validate('test');
112   }
113
114   /**
115    * @covers ::validate
116    */
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);
121   }
122
123   /**
124    * @covers ::validate
125    */
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);
132           }
133         ])
134     );
135     $typed_data->setValue('foo');
136
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());
141   }
142
143   /**
144    * @covers ::validate
145    */
146   public function testBasicValidateWithMultipleConstraints() {
147     $options = [
148       'callback' => function ($value, ExecutionContextInterface $context) {
149         $context->addViolation('test violation');
150       }
151     ];
152     $typed_data = $this->typedDataManager->create(
153       DataDefinition::create('string')
154         ->addConstraint('Callback', $options)
155         ->addConstraint('NotNull')
156     );
157     $violations = $this->recursiveValidator->validate($typed_data);
158     $this->assertCount(2, $violations);
159   }
160
161   /**
162    * @covers ::validate
163    */
164   public function testPropertiesValidateWithMultipleLevels() {
165
166     $typed_data = $this->buildExampleTypedDataWithProperties();
167
168     $violations = $this->recursiveValidator->validate($typed_data);
169     $this->assertCount(6, $violations);
170
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());
177
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());
184   }
185
186   /**
187    * Setups a typed data object used for test purposes.
188    *
189    * @param array $tree
190    *   An array of value, constraints and properties.
191    *
192    * @return \Drupal\Core\TypedData\TypedDataInterface|\PHPUnit_Framework_MockObject_MockObject
193    */
194   protected function setupTypedData(array $tree, $name = '') {
195     $callback = function ($value, ExecutionContextInterface $context) {
196       $context->addViolation('violation: ' . (is_array($value) ? count($value) : $value));
197     };
198
199     $tree += ['constraints' => []];
200
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());
207       }
208       $typed_data = $this->typedDataManager->create(
209         $map_data_definition,
210         $tree['value'],
211         $name
212       );
213     }
214     else {
215       /** @var \Drupal\Core\TypedData\TypedDataInterface $typed_data */
216       $typed_data = $this->typedDataManager->create(
217         DataDefinition::create('string')
218           ->addConstraint('Callback', ['callback' => $callback]),
219         $tree['value'],
220         $name
221       );
222     }
223
224     return $typed_data;
225   }
226
227   /**
228    * @covers ::validateProperty
229    */
230   public function testValidatePropertyWithCustomGroup() {
231     $tree = [
232       'value' => [],
233       'properties' => [
234         'key1' => ['value' => 'value1'],
235       ],
236     ];
237     $typed_data = $this->setupTypedData($tree, 'test_name');
238     $this->setExpectedException(\LogicException::class);
239     $this->recursiveValidator->validateProperty($typed_data, 'key1', 'test group');
240   }
241
242   /**
243    * @covers ::validateProperty
244    *
245    * @dataProvider providerTestValidatePropertyWithInvalidObjects
246    */
247   public function testValidatePropertyWithInvalidObjects($object) {
248     $this->setExpectedException(\InvalidArgumentException::class);
249     $this->recursiveValidator->validateProperty($object, 'key1', NULL);
250   }
251
252   /**
253    * Provides data for testValidatePropertyWithInvalidObjects.
254    * @return array
255    */
256   public function providerTestValidatePropertyWithInvalidObjects() {
257     $data = [];
258     $data[] = [new \stdClass()];
259     $data[] = [new TestClass()];
260
261     $data[] = [$this->getMock('Drupal\Core\TypedData\TypedDataInterface')];
262
263     return $data;
264   }
265
266   /**
267    * @covers ::validateProperty
268    */
269   public function testValidateProperty() {
270     $typed_data = $this->buildExampleTypedDataWithProperties();
271
272     $violations = $this->recursiveValidator->validateProperty($typed_data, 'key_with_properties');
273     $this->assertCount(3, $violations);
274
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());
278
279     $this->assertEquals('', $violations->get(0)->getPropertyPath());
280     $this->assertEquals('subkey1', $violations->get(1)->getPropertyPath());
281     $this->assertEquals('subkey2', $violations->get(2)->getPropertyPath());
282   }
283
284   /**
285    * @covers ::validatePropertyValue
286    *
287    * @dataProvider providerTestValidatePropertyWithInvalidObjects
288    */
289   public function testValidatePropertyValueWithInvalidObjects($object) {
290     $this->setExpectedException(\InvalidArgumentException::class);
291     $this->recursiveValidator->validatePropertyValue($object, 'key1', [], NULL);
292   }
293
294   /**
295    * @covers ::validatePropertyValue
296    */
297   public function testValidatePropertyValue() {
298     $typed_data = $this->buildExampleTypedDataWithProperties(['subkey1' => 'subvalue11', 'subkey2' => 'subvalue22']);
299
300     $violations = $this->recursiveValidator->validatePropertyValue($typed_data, 'key_with_properties', $typed_data->get('key_with_properties'));
301     $this->assertCount(3, $violations);
302
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());
306
307     $this->assertEquals('', $violations->get(0)->getPropertyPath());
308     $this->assertEquals('subkey1', $violations->get(1)->getPropertyPath());
309     $this->assertEquals('subkey2', $violations->get(2)->getPropertyPath());
310   }
311
312   /**
313    * Builds some example type data object.
314    *
315    * @return \Drupal\Core\TypedData\TypedDataInterface|\PHPUnit_Framework_MockObject_MockObject
316    */
317   protected function buildExampleTypedDataWithProperties($subkey_value = NULL) {
318     $subkey_value = $subkey_value ?: ['subkey1' => 'subvalue1', 'subkey2' => 'subvalue2'];
319     $tree = [
320       'value' => [
321         'key1' => 'value1',
322         'key2' => 'value2',
323         'key_with_properties' => $subkey_value
324       ],
325     ];
326     $tree['properties'] = [
327       'key1' => [
328         'value' => 'value1',
329       ],
330       'key2' => [
331         'value' => 'value2',
332       ],
333       'key_with_properties' => [
334         'value' => $subkey_value ?: ['subkey1' => 'subvalue1', 'subkey2' => 'subvalue2'],
335         ],
336     ];
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']];
339
340     return $this->setupTypedData($tree, 'test_name');
341   }
342
343 }
344
345 class TestClass {
346
347 }