[BUGFIX] Reuse entities of overridden classes in persistence session
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Tests / Unit / Persistence / Generic / PersistenceManagerTest.php
1 <?php
2
3 namespace TYPO3\CMS\Extbase\Tests\Unit\Persistence\Generic;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
19 use TYPO3\CMS\Extbase\Object\Container\Container;
20 use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
21 use TYPO3\CMS\Extbase\Persistence\Generic\Backend;
22 use TYPO3\CMS\Extbase\Persistence\Generic\BackendInterface;
23 use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
24 use TYPO3\CMS\Extbase\Persistence\Generic\Session;
25 use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
26 use TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface;
27 use TYPO3\CMS\Extbase\Persistence\RepositoryInterface;
28 use TYPO3\CMS\Extbase\Tests\Unit\Persistence\Fixture\Model\Entity2;
29 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
30
31 /**
32 * Test case
33 */
34 class PersistenceManagerTest extends UnitTestCase
35 {
36 /**
37 * @var ObjectManagerInterface
38 */
39 protected $mockObjectManager;
40
41 /**
42 *
43 */
44 protected function setUp(): void
45 {
46 $this->mockObjectManager = $this->createMock(ObjectManagerInterface::class);
47 }
48
49 /**
50 * @test
51 */
52 public function persistAllPassesAddedObjectsToBackend(): void
53 {
54 $entity2 = new Entity2();
55 $objectStorage = new ObjectStorage();
56 $objectStorage->attach($entity2);
57 $mockBackend = $this->createMock(BackendInterface::class);
58 $mockBackend->expects($this->once())->method('setAggregateRootObjects')->with($objectStorage);
59
60 /** @var PersistenceManager|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $manager */
61 $manager = $this->getAccessibleMock(
62 PersistenceManager::class,
63 ['dummy']
64 );
65 $manager->_set('backend', $mockBackend);
66 $manager->add($entity2);
67
68 $manager->persistAll();
69 }
70
71 /**
72 * @test
73 */
74 public function persistAllPassesRemovedObjectsToBackend(): void
75 {
76 $entity2 = new Entity2();
77 $objectStorage = new ObjectStorage();
78 $objectStorage->attach($entity2);
79 $mockBackend = $this->createMock(BackendInterface::class);
80 $mockBackend->expects($this->once())->method('setDeletedEntities')->with($objectStorage);
81
82 /** @var PersistenceManager|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $manager */
83 $manager = $this->getAccessibleMock(
84 PersistenceManager::class,
85 ['dummy']
86 );
87 $manager->_set('backend', $mockBackend);
88 $manager->remove($entity2);
89
90 $manager->persistAll();
91 }
92
93 /**
94 * @test
95 */
96 public function getIdentifierByObjectReturnsIdentifierFromBackend(): void
97 {
98 $fakeUuid = 'fakeUuid';
99 $object = new \stdClass();
100
101 $mockBackend = $this->createMock(BackendInterface::class);
102 $mockBackend->expects($this->once())->method('getIdentifierByObject')->with($object)->will($this->returnValue($fakeUuid));
103
104 /** @var PersistenceManager|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $manager */
105 $manager = $this->getAccessibleMock(
106 PersistenceManager::class,
107 ['dummy']
108 );
109 $manager->_set('backend', $mockBackend);
110
111 $this->assertEquals($manager->getIdentifierByObject($object), $fakeUuid);
112 }
113
114 /**
115 * @test
116 */
117 public function getObjectByIdentifierReturnsObjectFromSessionIfAvailable(): void
118 {
119 $fakeUuid = 'fakeUuid';
120 $object = new \stdClass();
121
122 $mockSession = $this->createMock(Session::class);
123 $mockSession->expects($this->once())->method('hasIdentifier')->with($fakeUuid)->will($this->returnValue(true));
124 $mockSession->expects($this->once())->method('getObjectByIdentifier')->with($fakeUuid)->will($this->returnValue($object));
125
126 /** @var PersistenceManager|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $manager */
127 $manager = $this->getAccessibleMock(
128 PersistenceManager::class,
129 ['dummy']
130 );
131 $manager->_set('persistenceSession', $mockSession);
132
133 $this->assertEquals($manager->getObjectByIdentifier($fakeUuid), $object);
134 }
135
136 /**
137 * @test
138 */
139 public function getObjectByIdentifierReturnsObjectFromPersistenceIfAvailable(): void
140 {
141 $fakeUuid = '42';
142 $object = new \stdClass();
143 $fakeEntityType = get_class($object);
144
145 $mockSession = $this->createMock(Session::class);
146 $mockSession->expects($this->once())->method('hasIdentifier')->with($fakeUuid)->will($this->returnValue(false));
147
148 $mockBackend = $this->createMock(BackendInterface::class);
149 $mockBackend->expects($this->once())->method('getObjectByIdentifier')->with(
150 $fakeUuid,
151 $fakeEntityType
152 )->will($this->returnValue($object));
153
154 /** @var PersistenceManager|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $manager */
155 $manager = $this->getAccessibleMock(
156 PersistenceManager::class,
157 ['dummy']
158 );
159 $manager->_set('persistenceSession', $mockSession);
160 $manager->_set('backend', $mockBackend);
161
162 $this->assertEquals($manager->getObjectByIdentifier($fakeUuid, $fakeEntityType), $object);
163 }
164
165 /**
166 * @test
167 */
168 public function getObjectByIdentifierReturnsNullForUnknownObject(): void
169 {
170 $fakeUuid = '42';
171 $fakeEntityType = 'foobar';
172
173 $mockSession = $this->createMock(Session::class);
174 $mockSession->expects($this->once())->method('hasIdentifier')->with(
175 $fakeUuid,
176 $fakeEntityType
177 )->will($this->returnValue(false));
178
179 $mockBackend = $this->createMock(BackendInterface::class);
180 $mockBackend->expects($this->once())->method('getObjectByIdentifier')->with(
181 $fakeUuid,
182 $fakeEntityType
183 )->will($this->returnValue(null));
184
185 /** @var PersistenceManager|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $manager */
186 $manager = $this->getAccessibleMock(
187 PersistenceManager::class,
188 ['dummy']
189 );
190 $manager->_set('persistenceSession', $mockSession);
191 $manager->_set('backend', $mockBackend);
192
193 $this->assertNull($manager->getObjectByIdentifier($fakeUuid, $fakeEntityType));
194 }
195
196 /**
197 * @test
198 */
199 public function addActuallyAddsAnObjectToTheInternalObjectsArray(): void
200 {
201 $someObject = new \stdClass();
202 $persistenceManager = new PersistenceManager();
203 $persistenceManager->add($someObject);
204
205 $this->assertAttributeContains($someObject, 'addedObjects', $persistenceManager);
206 }
207
208 /**
209 * @test
210 */
211 public function removeActuallyRemovesAnObjectFromTheInternalObjectsArray(): void
212 {
213 $object1 = new \stdClass();
214 $object2 = new \stdClass();
215 $object3 = new \stdClass();
216
217 $persistenceManager = new PersistenceManager();
218 $persistenceManager->add($object1);
219 $persistenceManager->add($object2);
220 $persistenceManager->add($object3);
221
222 $persistenceManager->remove($object2);
223
224 $this->assertAttributeContains($object1, 'addedObjects', $persistenceManager);
225 $this->assertAttributeNotContains($object2, 'addedObjects', $persistenceManager);
226 $this->assertAttributeContains($object3, 'addedObjects', $persistenceManager);
227 }
228
229 /**
230 * @test
231 */
232 public function removeRemovesTheRightObjectEvenIfItHasBeenModifiedSinceItsAddition(): void
233 {
234 $object1 = new \ArrayObject(['val' => '1']);
235 $object2 = new \ArrayObject(['val' => '2']);
236 $object3 = new \ArrayObject(['val' => '3']);
237
238 $persistenceManager = new PersistenceManager();
239 $persistenceManager->add($object1);
240 $persistenceManager->add($object2);
241 $persistenceManager->add($object3);
242
243 $object2['foo'] = 'bar';
244 $object3['val'] = '2';
245
246 $persistenceManager->remove($object2);
247
248 $this->assertAttributeContains($object1, 'addedObjects', $persistenceManager);
249 $this->assertAttributeNotContains($object2, 'addedObjects', $persistenceManager);
250 $this->assertAttributeContains($object3, 'addedObjects', $persistenceManager);
251 }
252
253 /**
254 * Make sure we remember the objects that are not currently add()ed
255 * but might be in persistent storage.
256 *
257 * @test
258 */
259 public function removeRetainsObjectForObjectsNotInCurrentSession(): void
260 {
261 $object = new \ArrayObject(['val' => '1']);
262 $persistenceManager = new PersistenceManager();
263 $persistenceManager->remove($object);
264
265 $this->assertAttributeContains($object, 'removedObjects', $persistenceManager);
266 }
267
268 /**
269 * @test
270 */
271 public function updateSchedulesAnObjectForPersistence(): void
272 {
273 $className = $this->getUniqueId('BazFixture');
274 eval('
275 namespace ' . __NAMESPACE__ . '\\Domain\\Model;
276 class ' . $className . ' extends \\' . AbstractEntity::class . ' {
277 protected $uid = 42;
278 }
279 ');
280 eval('
281 namespace ' . __NAMESPACE__ . '\\Domain\\Repository;
282 class ' . $className . 'Repository extends \\TYPO3\\CMS\\Extbase\\Persistence\\Repository {}
283 ');
284 $classNameWithNamespace = __NAMESPACE__ . '\\Domain\\Model\\' . $className;
285 $repositorClassNameWithNamespace = __NAMESPACE__ . '\\Domain\\Repository\\' . $className . 'Repository';
286
287 /** @var PersistenceManager|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $persistenceManager */
288 $persistenceManager = $this->getAccessibleMock(
289 PersistenceManager::class,
290 ['dummy']
291 );
292 $session = new Session(new Container());
293 $changedEntities = new ObjectStorage();
294 $entity1 = new $classNameWithNamespace();
295 /** @var RepositoryInterface|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $repository */
296 $repository = $this->getAccessibleMock($repositorClassNameWithNamespace, ['dummy'], [$this->mockObjectManager]);
297 $repository->_set('objectType', get_class($entity1));
298 $mockBackend = $this->getMockBuilder($this->buildAccessibleProxy(Backend::class))
299 ->setMethods(['commit', 'setChangedEntities'])
300 ->disableOriginalConstructor()
301 ->getMock();
302 $mockBackend->expects($this->once())
303 ->method('setChangedEntities')
304 ->with($this->equalTo($changedEntities));
305
306 $persistenceManager->_set('backend', $mockBackend);
307 $persistenceManager->_set('persistenceSession', $session);
308
309 $repository->_set('persistenceManager', $persistenceManager);
310 $session->registerObject($entity1, 42);
311 $changedEntities->attach($entity1);
312 $repository->update($entity1);
313 $persistenceManager->persistAll();
314 }
315
316 /**
317 * @test
318 */
319 public function clearStateForgetsAboutNewObjects(): void
320 {
321 /** @var PersistenceManagerInterface|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $mockObject */
322 $mockObject = $this->createMock(PersistenceManagerInterface::class);
323 $mockObject->Persistence_Object_Identifier = 'abcdefg';
324
325 $mockSession = $this->createMock(Session::class);
326 $mockSession->expects($this->any())->method('hasIdentifier')->will($this->returnValue(false));
327 $mockBackend = $this->createMock(BackendInterface::class);
328
329 /** @var PersistenceManager|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $persistenceManager */
330 $persistenceManager = $this->getAccessibleMock(
331 PersistenceManager::class,
332 ['dummy']
333 );
334 $persistenceManager->_set('persistenceSession', $mockSession);
335 $persistenceManager->_set('backend', $mockBackend);
336
337 $persistenceManager->registerNewObject($mockObject);
338 $persistenceManager->clearState();
339
340 $object = $persistenceManager->getObjectByIdentifier('abcdefg');
341 $this->assertNull($object);
342 }
343
344 /**
345 * @test
346 */
347 public function tearDownWithBackendSupportingTearDownDelegatesCallToBackend(): void
348 {
349 $methods = array_merge(
350 get_class_methods(BackendInterface::class),
351 ['tearDown']
352 );
353 $mockBackend = $this->getMockBuilder(BackendInterface::class)
354 ->setMethods($methods)
355 ->getMock();
356 $mockBackend->expects($this->once())->method('tearDown');
357
358 /** @var PersistenceManager|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $persistenceManager */
359 $persistenceManager = $this->getAccessibleMock(
360 PersistenceManager::class,
361 ['dummy']
362 );
363 $persistenceManager->_set('backend', $mockBackend);
364
365 $persistenceManager->tearDown();
366 }
367
368 /**
369 * @test
370 *
371 * This test and the related Fixtures TxDomainModelTestEntity and
372 * TxDomainRepositoryTestEntityRepository can be removed if we do not need to support
373 * underscore class names instead of namespaced class names
374 */
375 public function persistAllAddsReconstitutedObjectFromSessionToBackendsAggregateRootObjects(): void
376 {
377 $className = $this->getUniqueId('BazFixture');
378 eval('
379 class Foo_Bar_Domain_Model_' . $className . ' extends \\' . AbstractEntity::class . ' {}
380 ');
381 eval('
382 class Foo_Bar_Domain_Repository_' . $className . 'Repository {}
383 ');
384 /** @var PersistenceManager|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $persistenceManager */
385 $persistenceManager = $this->getAccessibleMock(
386 PersistenceManager::class,
387 ['dummy']
388 );
389 $aggregateRootObjects = new ObjectStorage();
390 $fullClassName = 'Foo_Bar_Domain_Model_' . $className;
391 $entity1 = new $fullClassName();
392 $aggregateRootObjects->attach($entity1);
393 $mockBackend = $this->getMockBuilder($this->buildAccessibleProxy(Backend::class))
394 ->setMethods(['commit', 'setAggregateRootObjects', 'setDeletedEntities'])
395 ->disableOriginalConstructor()
396 ->getMock();
397 $mockBackend->expects($this->once())
398 ->method('setAggregateRootObjects')
399 ->with($this->equalTo($aggregateRootObjects));
400 $persistenceManager->_set('backend', $mockBackend);
401 $persistenceManager->add($entity1);
402 $persistenceManager->persistAll();
403 }
404
405 /**
406 * @test
407 */
408 public function persistAllAddsNamespacedReconstitutedObjectFromSessionToBackendsAggregateRootObjects(): void
409 {
410 $className = $this->getUniqueId('BazFixture');
411 eval('
412 namespace ' . __NAMESPACE__ . '\\Domain\\Model;
413 class ' . $className . ' extends \\' . AbstractEntity::class . ' {}
414 ');
415 eval('
416 namespace ' . __NAMESPACE__ . '\\Domain\\Repository;
417 class ' . $className . 'Repository {}
418 ');
419 /** @var PersistenceManager|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $persistenceManager */
420 $persistenceManager = $this->getAccessibleMock(
421 PersistenceManager::class,
422 ['dummy']
423 );
424 $aggregateRootObjects = new ObjectStorage();
425 $classNameWithNamespace = __NAMESPACE__ . '\\Domain\\Model\\' . $className;
426 $entity1 = new $classNameWithNamespace();
427 $aggregateRootObjects->attach($entity1);
428 $mockBackend = $this->getMockBuilder($this->buildAccessibleProxy(Backend::class))
429 ->setMethods(['commit', 'setAggregateRootObjects', 'setDeletedEntities'])
430 ->disableOriginalConstructor()
431 ->getMock();
432 $mockBackend->expects($this->once())
433 ->method('setAggregateRootObjects')
434 ->with($this->equalTo($aggregateRootObjects));
435 $persistenceManager->_set('backend', $mockBackend);
436 $persistenceManager->add($entity1);
437 $persistenceManager->persistAll();
438 }
439 }