[!!!][TASK] Extract testing framework for TYPO3
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Tests / Unit / Persistence / Generic / Mapper / DataMapperTest.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Tests\Unit\Persistence\Generic\Mapper;
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 use TYPO3\CMS\Components\TestingFramework\Core\AccessibleObjectInterface;
18 use TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException;
19 use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap;
20 use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper;
21
22 /**
23 * Test case
24 */
25 class DataMapperTest extends \TYPO3\CMS\Components\TestingFramework\Core\UnitTestCase
26 {
27 /**
28 * @test
29 */
30 public function mapMapsArrayToObjectByCallingmapToObject()
31 {
32 $rows = [['uid' => '1234']];
33 $object = new \stdClass();
34 /** @var DataMapper|AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject $dataMapper */
35 $dataMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class, ['mapSingleRow', 'getTargetType']);
36 $dataMapper->expects($this->any())->method('getTargetType')->will($this->returnArgument(1));
37 $dataMapFactory = $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory::class);
38 $dataMapper->_set('dataMapFactory', $dataMapFactory);
39 $dataMapper->expects($this->once())->method('mapSingleRow')->with($rows[0])->will($this->returnValue($object));
40 $dataMapper->map(get_class($object), $rows);
41 }
42
43 /**
44 * @test
45 */
46 public function mapSingleRowReturnsObjectFromPersistenceSessionIfAvailable()
47 {
48 $row = ['uid' => '1234'];
49 $object = new \stdClass();
50 $persistenceSession = $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\Session::class);
51 $persistenceSession->expects($this->once())->method('hasIdentifier')->with('1234')->will($this->returnValue(true));
52 $persistenceSession->expects($this->once())->method('getObjectByIdentifier')->with('1234')->will($this->returnValue($object));
53 $dataMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class, ['dummy']);
54 $dataMapper->_set('persistenceSession', $persistenceSession);
55 $dataMapper->_call('mapSingleRow', get_class($object), $row);
56 }
57
58 /**
59 * @test
60 */
61 public function thawPropertiesSetsPropertyValues()
62 {
63 $className = $this->getUniqueId('Class');
64 $classNameWithNS = __NAMESPACE__ . '\\' . $className;
65 eval('namespace ' . __NAMESPACE__ . '; class ' . $className . ' extends \\' . \TYPO3\CMS\Extbase\DomainObject\AbstractEntity::class . ' {
66 public $firstProperty; public $secondProperty; public $thirdProperty; public $fourthProperty;
67 }'
68 );
69 $object = new $classNameWithNS();
70 $row = [
71 'uid' => '1234',
72 'firstProperty' => 'firstValue',
73 'secondProperty' => 1234,
74 'thirdProperty' => 1.234,
75 'fourthProperty' => false
76 ];
77 $columnMaps = [
78 'uid' => new ColumnMap('uid', 'uid'),
79 'pid' => new ColumnMap('pid', 'pid'),
80 'firstProperty' => new ColumnMap('firstProperty', 'firstProperty'),
81 'secondProperty' => new ColumnMap('secondProperty', 'secondProperty'),
82 'thirdProperty' => new ColumnMap('thirdProperty', 'thirdProperty')
83 ];
84 $dataMap = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap::class, ['dummy'], [$className, $className]);
85 $dataMap->_set('columnMaps', $columnMaps);
86 $dataMaps = [
87 $classNameWithNS => $dataMap
88 ];
89 /** @var AccessibleObjectInterface|\TYPO3\CMS\Extbase\Reflection\ClassSchema $classSchema */
90 $classSchema = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Reflection\ClassSchema::class, ['dummy'], [$classNameWithNS]);
91 $classSchema->addProperty('pid', 'integer');
92 $classSchema->addProperty('uid', 'integer');
93 $classSchema->addProperty('firstProperty', 'string');
94 $classSchema->addProperty('secondProperty', 'integer');
95 $classSchema->addProperty('thirdProperty', 'float');
96 $classSchema->addProperty('fourthProperty', 'boolean');
97 $mockReflectionService = $this->getMockBuilder(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class)
98 ->setMethods(['getClassSchema'])
99 ->getMock();
100 $mockReflectionService->expects($this->any())->method('getClassSchema')->will($this->returnValue($classSchema));
101 $dataMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class, ['dummy']);
102 $dataMapper->_set('dataMaps', $dataMaps);
103 $dataMapper->_set('reflectionService', $mockReflectionService);
104 $dataMapper->_call('thawProperties', $object, $row);
105 $this->assertAttributeEquals('firstValue', 'firstProperty', $object);
106 $this->assertAttributeEquals(1234, 'secondProperty', $object);
107 $this->assertAttributeEquals(1.234, 'thirdProperty', $object);
108 $this->assertAttributeEquals(false, 'fourthProperty', $object);
109 }
110
111 /**
112 * Test if fetchRelatedEager method returns NULL when $fieldValue = '' and relation type == RELATION_HAS_ONE
113 *
114 * @test
115 */
116 public function fetchRelatedEagerReturnsNullForEmptyRelationHasOne()
117 {
118 $columnMap = new ColumnMap('columnName', 'propertyName');
119 $columnMap->setTypeOfRelation(ColumnMap::RELATION_HAS_ONE);
120 $dataMap = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap::class)
121 ->setMethods(['getColumnMap'])
122 ->disableOriginalConstructor()
123 ->getMock();
124 $dataMap->expects($this->any())->method('getColumnMap')->will($this->returnValue($columnMap));
125 $dataMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class, ['getDataMap']);
126 $dataMapper->expects($this->any())->method('getDataMap')->will($this->returnValue($dataMap));
127 $result = $dataMapper->_call('fetchRelatedEager', $this->createMock(\TYPO3\CMS\Extbase\DomainObject\AbstractEntity::class), 'SomeName', '');
128 $this->assertEquals(null, $result);
129 }
130
131 /**
132 * Test if fetchRelatedEager method returns empty array when $fieldValue = '' and relation type != RELATION_HAS_ONE
133 *
134 * @test
135 */
136 public function fetchRelatedEagerReturnsEmptyArrayForEmptyRelationNotHasOne()
137 {
138 $columnMap = new ColumnMap('columnName', 'propertyName');
139 $columnMap->setTypeOfRelation(ColumnMap::RELATION_BELONGS_TO_MANY);
140 $dataMap = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap::class)
141 ->setMethods(['getColumnMap'])
142 ->disableOriginalConstructor()
143 ->getMock();
144 $dataMap->expects($this->any())->method('getColumnMap')->will($this->returnValue($columnMap));
145 $dataMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class, ['getDataMap']);
146 $dataMapper->expects($this->any())->method('getDataMap')->will($this->returnValue($dataMap));
147 $result = $dataMapper->_call('fetchRelatedEager', $this->createMock(\TYPO3\CMS\Extbase\DomainObject\AbstractEntity::class), 'SomeName', '');
148 $this->assertEquals([], $result);
149 }
150
151 /**
152 * Test if fetchRelatedEager method returns NULL when $fieldValue = ''
153 * and relation type == RELATION_HAS_ONE without calling fetchRelated
154 *
155 * @test
156 */
157 public function mapObjectToClassPropertyReturnsNullForEmptyRelationHasOne()
158 {
159 $columnMap = new ColumnMap('columnName', 'propertyName');
160 $columnMap->setTypeOfRelation(ColumnMap::RELATION_HAS_ONE);
161 $dataMap = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap::class)
162 ->setMethods(['getColumnMap'])
163 ->disableOriginalConstructor()
164 ->getMock();
165 $dataMap->expects($this->any())->method('getColumnMap')->will($this->returnValue($columnMap));
166 $dataMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class, ['getDataMap', 'fetchRelated']);
167 $dataMapper->expects($this->any())->method('getDataMap')->will($this->returnValue($dataMap));
168 $dataMapper->expects($this->never())->method('fetchRelated');
169 $result = $dataMapper->_call('mapObjectToClassProperty', $this->createMock(\TYPO3\CMS\Extbase\DomainObject\AbstractEntity::class), 'SomeName', '');
170 $this->assertEquals(null, $result);
171 }
172
173 /**
174 * Test if mapObjectToClassProperty method returns objects
175 * that are already registered in the persistence session
176 * without query it from the persistence layer
177 *
178 * @test
179 */
180 public function mapObjectToClassPropertyReturnsExistingObjectWithoutCallingFetchRelated()
181 {
182 $columnMap = new ColumnMap('columnName', 'propertyName');
183 $columnMap->setTypeOfRelation(ColumnMap::RELATION_HAS_ONE);
184 $dataMap = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap::class)
185 ->setMethods(['getColumnMap'])
186 ->disableOriginalConstructor()
187 ->getMock();
188
189 $className = $this->getUniqueId('Class1');
190 $classNameWithNS = __NAMESPACE__ . '\\' . $className;
191 eval('namespace ' . __NAMESPACE__ . '; class ' . $className . ' extends \\' . \TYPO3\CMS\Extbase\DomainObject\AbstractEntity::class . ' { public $relationProperty; }');
192 $object = new $classNameWithNS();
193
194 $className2 = $this->getUniqueId('Class2');
195 $className2WithNS = __NAMESPACE__ . '\\' . $className2;
196 eval('namespace ' . __NAMESPACE__ . '; class ' . $className2 . ' extends \\' . \TYPO3\CMS\Extbase\DomainObject\AbstractEntity::class . ' { }');
197 $child = new $className2WithNS();
198
199 /** @var \TYPO3\CMS\Extbase\Reflection\ClassSchema|AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject $classSchema1 */
200 $classSchema1 = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Reflection\ClassSchema::class, ['dummy'], [$classNameWithNS]);
201 $classSchema1->addProperty('relationProperty', $className2WithNS);
202 $identifier = 1;
203
204 $session = new \TYPO3\CMS\Extbase\Persistence\Generic\Session();
205 $session->registerObject($child, $identifier);
206
207 $mockReflectionService = $this->getMockBuilder(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class)
208 ->setMethods(['getClassSchema'])
209 ->disableOriginalConstructor()
210 ->getMock();
211 $mockReflectionService->expects($this->any())->method('getClassSchema')->will($this->returnValue($classSchema1));
212
213 $dataMap->expects($this->any())->method('getColumnMap')->will($this->returnValue($columnMap));
214 $dataMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class, ['getDataMap', 'getNonEmptyRelationValue']);
215 $dataMapper->_set('reflectionService', $mockReflectionService);
216 $dataMapper->_set('persistenceSession', $session);
217 $dataMapper->expects($this->any())->method('getDataMap')->will($this->returnValue($dataMap));
218 $dataMapper->expects($this->never())->method('getNonEmptyRelationValue');
219 $result = $dataMapper->_call('mapObjectToClassProperty', $object, 'relationProperty', $identifier);
220 $this->assertEquals($child, $result);
221 }
222
223 /**
224 * Data provider for date checks. Date will be stored based on UTC in
225 * the database. That's why it's not possible to check for explicit date
226 * strings but using the date('c') conversion instead, which considers the
227 * current local timezone setting.
228 *
229 * @return array
230 */
231 public function mapDateTimeHandlesDifferentFieldEvaluationsDataProvider()
232 {
233 return [
234 'nothing' => [null, null, null],
235 'timestamp' => [1, null, date('c', 1)],
236 'empty date' => ['0000-00-00', 'date', null],
237 'valid date' => ['2013-01-01', 'date', date('c', strtotime('2013-01-01T00:00:00+00:00'))],
238 'empty datetime' => ['0000-00-00 00:00:00', 'datetime', null],
239 'valid datetime' => ['2013-01-01 01:02:03', 'datetime', date('c', strtotime('2013-01-01T01:02:03+00:00'))],
240 ];
241 }
242
243 /**
244 * @param NULL|string|int $value
245 * @param NULL|string $storageFormat
246 * @param NULL|string $expectedValue
247 * @test
248 * @dataProvider mapDateTimeHandlesDifferentFieldEvaluationsDataProvider
249 */
250 public function mapDateTimeHandlesDifferentFieldEvaluations($value, $storageFormat, $expectedValue)
251 {
252 $accessibleClassName = $this->buildAccessibleProxy(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class);
253
254 /** @var DataMapper|AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject $accessibleDataMapFactory */
255 $accessibleDataMapFactory = new $accessibleClassName();
256
257 /** @var $dateTime NULL|\DateTime */
258 $dateTime = $accessibleDataMapFactory->_callRef('mapDateTime', $value, $storageFormat);
259
260 if ($expectedValue === null) {
261 $this->assertNull($dateTime);
262 } else {
263 $this->assertEquals($expectedValue, $dateTime->format('c'));
264 }
265 }
266
267 /**
268 * @test
269 */
270 public function mapDateTimeHandlesSubclassesOfDateTime()
271 {
272 $accessibleClassName = $this->buildAccessibleProxy(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class);
273
274 /** @var DataMapper|AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject $accessibleDataMapFactory */
275 $accessibleDataMapFactory = new $accessibleClassName();
276 $targetType = 'TYPO3\CMS\Extbase\Tests\Unit\Persistence\Fixture\Model\CustomDateTime';
277 $date = '2013-01-01 01:02:03';
278 $storageFormat = 'datetime';
279
280 /** @var $dateTime NULL|\DateTime */
281 $dateTime = $accessibleDataMapFactory->_callRef('mapDateTime', $date, $storageFormat, $targetType);
282
283 $this->assertInstanceOf($targetType, $dateTime);
284 }
285
286 /**
287 * @test
288 */
289 public function getPlainValueReturnsCorrectDateTimeFormat()
290 {
291 $subject = new DataMapper();
292 $columnMap = new ColumnMap('column_name', 'propertyName');
293 $columnMap->setDateTimeStorageFormat('datetime');
294 $datetimeAsString = '2013-04-15 09:30:00';
295 $input = new \DateTime($datetimeAsString, new \DateTimeZone('UTC'));
296 $this->assertEquals('2013-04-15 09:30:00', $subject->getPlainValue($input, $columnMap));
297 $columnMap->setDateTimeStorageFormat('date');
298 $this->assertEquals('2013-04-15', $subject->getPlainValue($input, $columnMap));
299 }
300
301 /**
302 * @test
303 * @dataProvider getPlainValueReturnsExpectedValuesDataProvider
304 */
305 public function getPlainValueReturnsExpectedValues($expectedValue, $input)
306 {
307 $dataMapper = new DataMapper();
308 $this->assertSame($expectedValue, $dataMapper->getPlainValue($input));
309 }
310
311 /**
312 * @return array
313 */
314 public function getPlainValueReturnsExpectedValuesDataProvider()
315 {
316 return [
317 'datetime to timestamp' => ['1365866253', new \DateTime('@1365866253')],
318 'boolean true to 1' => [1, true],
319 'boolean false to 0' => [0, false],
320 'NULL is handled' => ['NULL', null],
321 'plain value is returned unchanged' => ['RANDOM string', 'RANDOM string'],
322 'array is flattened' => ['a,b,c', ['a', 'b', 'c']],
323 'deep array is flattened' => ['a,b,c', [['a', 'b'], 'c']],
324 ];
325 }
326
327 /**
328 * @test
329 */
330 public function getPlainValueCallsGetRealInstanceOnInputIfInputIsInstanceOfLazyLoadingProxy()
331 {
332 $this->expectException(UnexpectedTypeException::class);
333 $this->expectExceptionCode(1274799934);
334 $dataMapper = new DataMapper();
335 $input = $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy::class);
336 $input->expects($this->once())->method('_loadRealInstance')->will($this->returnValue($dataMapper));
337 $dataMapper->getPlainValue($input);
338 }
339
340 /**
341 * @test
342 */
343 public function getPlainValueCallsGetUidOnDomainObjectInterfaceInput()
344 {
345 $dataMapper = new DataMapper();
346 $input = $this->createMock(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface::class);
347
348 $input->expects($this->once())->method('getUid')->will($this->returnValue(23));
349 $this->assertSame(23, $dataMapper->getPlainValue($input));
350 }
351 }