[CLEANUP] Rework/simplify copyright header and remove @package
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Tests / Unit / Property / PropertyMapperTest.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Tests\Unit\Property;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 /**
18 * Test case
19 */
20 class PropertyMapperTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
21
22 protected $mockConfigurationBuilder;
23
24 protected $mockConfiguration;
25
26 /**
27 * Sets up this test case
28 *
29 * @return void
30 * @author Sebastian Kurfürst <sebastian@typo3.org>
31 */
32 public function setUp() {
33 $this->mockConfigurationBuilder = $this->getMock('TYPO3\\CMS\\Extbase\\Property\\PropertyMappingConfigurationBuilder');
34 $this->mockConfiguration = $this->getMock('TYPO3\\CMS\\Extbase\\Property\\PropertyMappingConfigurationInterface');
35 }
36
37 /**
38 * @return array
39 */
40 public function validSourceTypes() {
41 return array(
42 array('someString', 'string'),
43 array(42, 'integer'),
44 array(3.5, 'float'),
45 array(TRUE, 'boolean'),
46 array(array(), 'array')
47 );
48 }
49
50 /**
51 * @test
52 * @dataProvider validSourceTypes
53 * @author Sebastian Kurfürst <sebastian@typo3.org>
54 * @param mixed $source
55 * @param mixed $sourceType
56 */
57 public function sourceTypeCanBeCorrectlyDetermined($source, $sourceType) {
58 /** @var \TYPO3\CMS\Extbase\Property\PropertyMapper|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface */
59 $propertyMapper = $this->getAccessibleMock('TYPO3\\CMS\\Extbase\\Property\\PropertyMapper', array('dummy'));
60 $this->assertEquals($sourceType, $propertyMapper->_call('determineSourceType', $source));
61 }
62
63 /**
64 * @return array
65 */
66 public function invalidSourceTypes() {
67 return array(
68 array(NULL),
69 array(new \stdClass()),
70 array(new \ArrayObject())
71 );
72 }
73
74 /**
75 * @test
76 * @dataProvider invalidSourceTypes
77 * @expectedException \TYPO3\CMS\Extbase\Property\Exception\InvalidSourceException
78 * @author Sebastian Kurfürst <sebastian@typo3.org>
79 * @param mixed $source
80 */
81 public function sourceWhichIsNoSimpleTypeThrowsException($source) {
82 /** @var \TYPO3\CMS\Extbase\Property\PropertyMapper|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface */
83 $propertyMapper = $this->getAccessibleMock('TYPO3\\CMS\\Extbase\\Property\\PropertyMapper', array('dummy'));
84 $propertyMapper->_call('determineSourceType', $source);
85 }
86
87 /**
88 * @param string $name
89 * @param boolean $canConvertFrom
90 * @param array $properties
91 * @param string $typeOfSubObject
92 * @return \PHPUnit_Framework_MockObject_MockObject
93 */
94 protected function getMockTypeConverter($name = '', $canConvertFrom = TRUE, $properties = array(), $typeOfSubObject = '') {
95 $mockTypeConverter = $this->getMock('TYPO3\\CMS\\Extbase\\Property\\TypeConverterInterface');
96 $mockTypeConverter->_name = $name;
97 $mockTypeConverter->expects($this->any())->method('canConvertFrom')->will($this->returnValue($canConvertFrom));
98 $mockTypeConverter->expects($this->any())->method('convertFrom')->will($this->returnValue($name));
99 $mockTypeConverter->expects($this->any())->method('getSourceChildPropertiesToBeConverted')->will($this->returnValue($properties));
100 $mockTypeConverter->expects($this->any())->method('getTypeOfChildProperty')->will($this->returnValue($typeOfSubObject));
101 return $mockTypeConverter;
102 }
103
104 /**
105 * @test
106 * @author Sebastian Kurfürst <sebastian@typo3.org>
107 */
108 public function findTypeConverterShouldReturnTypeConverterFromConfigurationIfItIsSet() {
109 $mockTypeConverter = $this->getMockTypeConverter();
110 $this->mockConfiguration->expects($this->any())->method('getTypeConverter')->will($this->returnValue($mockTypeConverter));
111 /** @var \TYPO3\CMS\Extbase\Property\PropertyMapper|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface */
112 $propertyMapper = $this->getAccessibleMock('TYPO3\\CMS\\Extbase\\Property\\PropertyMapper', array('dummy'));
113 $this->assertSame($mockTypeConverter, $propertyMapper->_call('findTypeConverter', 'someSource', 'someTargetType', $this->mockConfiguration));
114 }
115
116 /**
117 * Simple type conversion
118 * @return array
119 */
120 public function dataProviderForFindTypeConverter() {
121 return array(
122 array(
123 'someStringSource',
124 'string',
125 array(
126 'string' => array(
127 'string' => array(
128 10 => $this->getMockTypeConverter('string2string,prio10'),
129 1 => $this->getMockTypeConverter('string2string,prio1')
130 )
131 )
132 ),
133 'string2string,prio10'
134 ),
135 array(array('some' => 'array'), 'string', array(
136 'array' => array(
137 'string' => array(
138 10 => $this->getMockTypeConverter('array2string,prio10'),
139 1 => $this->getMockTypeConverter('array2string,prio1')
140 )
141 )
142 ), 'array2string,prio10')
143 );
144 }
145
146 /**
147 * @test
148 * @dataProvider dataProviderForFindTypeConverter
149 * @author Sebastian Kurfürst <sebastian@typo3.org>
150 * @param mixed $source
151 * @param mixed $targetType
152 * @param mixed $typeConverters
153 * @param mixed $expectedTypeConverter
154 */
155 public function findTypeConverterShouldReturnHighestPriorityTypeConverterForSimpleType($source, $targetType, $typeConverters, $expectedTypeConverter) {
156 $propertyMapper = $this->getAccessibleMock('TYPO3\CMS\Extbase\Property\PropertyMapper', array('dummy'));
157 $propertyMapper->_set('typeConverters', $typeConverters);
158 $actualTypeConverter = $propertyMapper->_call('findTypeConverter', $source, $targetType, $this->mockConfiguration);
159 $this->assertSame($expectedTypeConverter, $actualTypeConverter->_name);
160 }
161
162 /**
163 * @return array
164 */
165 public function dataProviderForObjectTypeConverters() {
166 $data = array();
167
168 $className1 = $this->getUniqueId('TYPO3_Flow_Testclass1_', FALSE);
169 $className2 = $this->getUniqueId('TYPO3_Flow_Testclass2_', FALSE);
170 $className3 = $this->getUniqueId('TYPO3_Flow_Testclass3_', FALSE);
171
172 $interfaceName1 = $this->getUniqueId('TYPO3_Flow_TestInterface1_', FALSE);
173 $interfaceName2 = $this->getUniqueId('TYPO3_Flow_TestInterface2_', FALSE);
174 $interfaceName3 = $this->getUniqueId('TYPO3_Flow_TestInterface3_', FALSE);
175
176 eval("
177 interface $interfaceName2 {}
178 interface $interfaceName1 {}
179
180 interface $interfaceName3 extends $interfaceName2 {}
181
182 class $className1 implements $interfaceName1 {}
183 class $className2 extends $className1 {}
184 class $className3 extends $className2 implements $interfaceName3 {}
185 ");
186
187 // The most specific converter should win
188 $data[] = array(
189 'target' => $className3,
190 'expectedConverter' => 'Class3Converter',
191 'typeConverters' => array(
192 $className2 => array(0 => $this->getMockTypeConverter('Class2Converter')),
193 $className3 => array(0 => $this->getMockTypeConverter('Class3Converter')),
194
195 $interfaceName1 => array(0 => $this->getMockTypeConverter('Interface1Converter')),
196 $interfaceName2 => array(0 => $this->getMockTypeConverter('Interface2Converter')),
197 $interfaceName3 => array(0 => $this->getMockTypeConverter('Interface3Converter')),
198 )
199 );
200
201 // In case the most specific converter does not want to handle this conversion, the second one is taken.
202 $data[] = array(
203 'target' => $className3,
204 'expectedConverter' => 'Class2Converter',
205 'typeConverters' => array(
206 $className2 => array(0 => $this->getMockTypeConverter('Class2Converter')),
207 $className3 => array(0 => $this->getMockTypeConverter('Class3Converter', FALSE)),
208
209 $interfaceName1 => array(0 => $this->getMockTypeConverter('Interface1Converter')),
210 $interfaceName2 => array(0 => $this->getMockTypeConverter('Interface2Converter')),
211 $interfaceName3 => array(0 => $this->getMockTypeConverter('Interface3Converter')),
212 )
213 );
214
215 // In case there is no most-specific-converter, we climb ub the type hierarchy
216 $data[] = array(
217 'target' => $className3,
218 'expectedConverter' => 'Class2Converter-HighPriority',
219 'typeConverters' => array(
220 $className2 => array(0 => $this->getMockTypeConverter('Class2Converter'), 10 => $this->getMockTypeConverter('Class2Converter-HighPriority'))
221 )
222 );
223
224 // If no parent class converter wants to handle it, we ask for all interface converters.
225 $data[] = array(
226 'target' => $className3,
227 'expectedConverter' => 'Interface1Converter',
228 'typeConverters' => array(
229 $className2 => array(0 => $this->getMockTypeConverter('Class2Converter', FALSE), 10 => $this->getMockTypeConverter('Class2Converter-HighPriority', FALSE)),
230
231 $interfaceName1 => array(4 => $this->getMockTypeConverter('Interface1Converter')),
232 $interfaceName2 => array(1 => $this->getMockTypeConverter('Interface2Converter')),
233 $interfaceName3 => array(2 => $this->getMockTypeConverter('Interface3Converter')),
234 )
235 );
236
237 // If two interface converters have the same priority, an exception is thrown.
238 $data[] = array(
239 'target' => $className3,
240 'expectedConverter' => 'Interface1Converter',
241 'typeConverters' => array(
242 $className2 => array(0 => $this->getMockTypeConverter('Class2Converter', FALSE), 10 => $this->getMockTypeConverter('Class2Converter-HighPriority', FALSE)),
243
244 $interfaceName1 => array(4 => $this->getMockTypeConverter('Interface1Converter')),
245 $interfaceName2 => array(2 => $this->getMockTypeConverter('Interface2Converter')),
246 $interfaceName3 => array(2 => $this->getMockTypeConverter('Interface3Converter')),
247 ),
248 'shouldFailWithException' => 'TYPO3\\CMS\\Extbase\\Property\\Exception\\DuplicateTypeConverterException'
249 );
250
251 // If no interface converter wants to handle it, a converter for "object" is looked up.
252 $data[] = array(
253 'target' => $className3,
254 'expectedConverter' => 'GenericObjectConverter-HighPriority',
255 'typeConverters' => array(
256 $className2 => array(0 => $this->getMockTypeConverter('Class2Converter', FALSE), 10 => $this->getMockTypeConverter('Class2Converter-HighPriority', FALSE)),
257
258 $interfaceName1 => array(4 => $this->getMockTypeConverter('Interface1Converter', FALSE)),
259 $interfaceName2 => array(3 => $this->getMockTypeConverter('Interface2Converter', FALSE)),
260 $interfaceName3 => array(2 => $this->getMockTypeConverter('Interface3Converter', FALSE)),
261 'object' => array(1 => $this->getMockTypeConverter('GenericObjectConverter'), 10 => $this->getMockTypeConverter('GenericObjectConverter-HighPriority'))
262 ),
263 );
264
265 // If the target is no valid class name and no simple type, an exception is thrown
266 $data[] = array(
267 'target' => 'SomeNotExistingClassName',
268 'expectedConverter' => 'GenericObjectConverter-HighPriority',
269 'typeConverters' => array(),
270 'shouldFailWithException' => 'TYPO3\\CMS\\Extbase\\Property\\Exception\\InvalidTargetException'
271 );
272
273 // if the type converter is not found, we expect an exception
274 $data[] = array(
275 'target' => $className3,
276 'expectedConverter' => 'Class3Converter',
277 'typeConverters' => array(),
278 'shouldFailWithException' => 'TYPO3\\CMS\\Extbase\\Property\\Exception\\TypeConverterException'
279 );
280
281 // If The target type is no string, we expect an exception.
282 $data[] = array(
283 'target' => new \stdClass(),
284 'expectedConverter' => '',
285 'typeConverters' => array(),
286 'shouldFailWithException' => 'TYPO3\\CMS\\Extbase\\Property\\Exception\\InvalidTargetException'
287 );
288 return $data;
289 }
290
291 /**
292 * @test
293 * @dataProvider dataProviderForObjectTypeConverters
294 * @author Sebastian Kurfürst <sebastian@typo3.org>
295 * @param mixed $targetClass
296 * @param mixed $expectedTypeConverter
297 * @param mixed $typeConverters
298 * @param boolean $shouldFailWithException
299 * @throws \Exception
300 * @return void
301 */
302 public function findTypeConverterShouldReturnConverterForTargetObjectIfItExists($targetClass, $expectedTypeConverter, $typeConverters, $shouldFailWithException = FALSE) {
303 $propertyMapper = $this->getAccessibleMock('TYPO3\CMS\Extbase\Property\PropertyMapper', array('dummy'));
304 $propertyMapper->_set('typeConverters', array('string' => $typeConverters));
305 try {
306 $actualTypeConverter = $propertyMapper->_call('findTypeConverter', 'someSourceString', $targetClass, $this->mockConfiguration);
307 if ($shouldFailWithException) {
308 $this->fail('Expected exception ' . $shouldFailWithException . ' which was not thrown.');
309 }
310 $this->assertSame($expectedTypeConverter, $actualTypeConverter->_name);
311 } catch (\Exception $e) {
312 if ($shouldFailWithException === FALSE) {
313 throw $e;
314 }
315 $this->assertInstanceOf($shouldFailWithException, $e);
316 }
317 }
318
319 /**
320 * @test
321 */
322 public function convertShouldAskConfigurationBuilderForDefaultConfiguration() {
323 $propertyMapper = $this->getAccessibleMock('TYPO3\CMS\Extbase\Property\PropertyMapper', array('dummy'));
324 $this->inject($propertyMapper, 'configurationBuilder', $this->mockConfigurationBuilder);
325
326 $this->mockConfigurationBuilder->expects($this->once())->method('build')->will($this->returnValue($this->mockConfiguration));
327
328 $converter = $this->getMockTypeConverter('string2string');
329 $typeConverters = array(
330 'string' => array(
331 'string' => array(10 => $converter)
332 )
333 );
334
335 $propertyMapper->_set('typeConverters', $typeConverters);
336 $this->assertEquals('string2string', $propertyMapper->convert('source', 'string'));
337 }
338
339 /**
340 * @test
341 */
342 public function findFirstEligibleTypeConverterInObjectHierarchyShouldReturnNullIfSourceTypeIsUnknown() {
343 /** @var \TYPO3\CMS\Extbase\Property\PropertyMapper|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface */
344 $propertyMapper = $this->getAccessibleMock('TYPO3\\CMS\\Extbase\\Property\\PropertyMapper', array('dummy'));
345 $this->assertNull($propertyMapper->_call('findFirstEligibleTypeConverterInObjectHierarchy', 'source', 'unknownSourceType', 'TYPO3\\CMS\\Extbase\\Core\\Bootstrap'));
346 }
347
348 /**
349 * @test
350 */
351 public function doMappingReturnsSourceUnchangedIfAlreadyConverted() {
352 $source = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
353 $targetType = 'TYPO3\CMS\Extbase\Persistence\ObjectStorage';
354 $propertyPath = '';
355 $propertyMapper = $this->getAccessibleMock('TYPO3\CMS\Extbase\Property\PropertyMapper', array('dummy'));
356 $this->assertSame($source, $propertyMapper->_callRef('doMapping', $source, $targetType, $this->mockConfiguration, $propertyPath));
357 }
358
359 /**
360 * @test
361 */
362 public function doMappingReturnsSourceUnchangedIfAlreadyConvertedToCompositeType() {
363 $source = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
364 $targetType = 'TYPO3\CMS\Extbase\Persistence\ObjectStorage<SomeEntity>';
365 $propertyPath = '';
366 $propertyMapper = $this->getAccessibleMock('TYPO3\CMS\Extbase\Property\PropertyMapper', array('dummy'));
367 $this->assertSame($source, $propertyMapper->_callRef('doMapping', $source, $targetType, $this->mockConfiguration, $propertyPath));
368 }
369
370 /**
371 * @test
372 */
373 public function convertSkipsPropertiesIfConfiguredTo() {
374 $source = array('firstProperty' => 1, 'secondProperty' => 2);
375 $typeConverters = array(
376 'array' => array(
377 'stdClass' => array(10 => $this->getMockTypeConverter('array2object', TRUE, $source, 'integer'))
378 ),
379 'integer' => array(
380 'integer' => array(10 => $this->getMockTypeConverter('integer2integer'))
381 )
382 );
383 $configuration = new \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration();
384
385 $propertyMapper = $this->getAccessibleMock('TYPO3\CMS\Extbase\Property\PropertyMapper', array('dummy'));
386 $propertyMapper->_set('typeConverters', $typeConverters);
387
388 $propertyMapper->convert($source, 'stdClass', $configuration->allowProperties('firstProperty')->skipProperties('secondProperty'));
389 }
390
391 /**
392 * @test
393 */
394 public function convertSkipsUnknownPropertiesIfConfiguredTo() {
395 $source = array('firstProperty' => 1, 'secondProperty' => 2);
396 $typeConverters = array(
397 'array' => array(
398 'stdClass' => array(10 => $this->getMockTypeConverter('array2object', TRUE, $source, 'integer'))
399 ),
400 'integer' => array(
401 'integer' => array(10 => $this->getMockTypeConverter('integer2integer'))
402 )
403 );
404 $configuration = new \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration();
405
406 $propertyMapper = $this->getAccessibleMock('TYPO3\CMS\Extbase\Property\PropertyMapper', array('dummy'));
407 $propertyMapper->_set('typeConverters', $typeConverters);
408
409 $propertyMapper->convert($source, 'stdClass', $configuration->allowProperties('firstProperty')->skipUnknownProperties());
410 }
411 }