Passed
Pull Request — master (#8)
by Alex
03:52
created

PersistServiceTest::testBeginTransaction()

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 7
c 2
b 0
f 0
dl 0
loc 12
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ArpTest\DoctrineEntityRepository\Persistence;
6
7
use Arp\DoctrineEntityRepository\Constant\EntityEventName;
8
use Arp\DoctrineEntityRepository\Constant\EntityEventOption;
9
use Arp\DoctrineEntityRepository\Persistence\Event\CollectionEvent;
10
use Arp\DoctrineEntityRepository\Persistence\Event\EntityErrorEvent;
11
use Arp\DoctrineEntityRepository\Persistence\Event\EntityEvent;
12
use Arp\DoctrineEntityRepository\Persistence\Exception\PersistenceException;
13
use Arp\DoctrineEntityRepository\Persistence\PersistService;
14
use Arp\DoctrineEntityRepository\Persistence\PersistServiceInterface;
15
use Arp\Entity\EntityInterface;
16
use Arp\Entity\EntityTrait;
17
use Doctrine\ORM\EntityManagerInterface;
18
use PHPUnit\Framework\MockObject\MockObject;
19
use PHPUnit\Framework\TestCase;
20
use Psr\EventDispatcher\EventDispatcherInterface;
21
use Psr\Log\LoggerInterface;
22
23
/**
24
 * @covers  \Arp\DoctrineEntityRepository\Persistence\PersistService
25
 *
26
 * @author  Alex Patterson <[email protected]>
27
 * @package ArpTest\DoctrineEntityRepository\Persistence
28
 */
29
final class PersistServiceTest extends TestCase
30
{
31
    /**
32
     * @var string
33
     */
34
    private string $entityName;
35
36
    /**
37
     * @var EntityManagerInterface&MockObject
38
     */
39
    private $entityManager;
40
41
    /**
42
     * @var EventDispatcherInterface&MockObject
43
     */
44
    private $eventDispatcher;
45
46
    /**
47
     * @var LoggerInterface&MockObject
48
     */
49
    private $logger;
50
51
    /**
52
     * Set up the test case dependencies.
53
     */
54
    public function setUp(): void
55
    {
56
        $this->entityName = EntityInterface::class;
57
58
        $this->entityManager = $this->createMock(EntityManagerInterface::class);
59
60
        $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class);
61
62
        $this->logger = $this->createMock(LoggerInterface::class);
63
    }
64
65
    /**
66
     * Assert that the class implements PersistServiceInterface
67
     */
68
    public function testImplementsPersistServiceInterface(): void
69
    {
70
        $persistService = new PersistService(
71
            $this->entityName,
72
            $this->entityManager,
73
            $this->eventDispatcher,
74
            $this->logger
75
        );
76
77
        $this->assertInstanceOf(PersistServiceInterface::class, $persistService);
78
    }
79
80
    /**
81
     * Assert that getEntityName() will return the fully qualified class name of the managed entity
82
     */
83
    public function testGetEntityNameWillReturnFQCNOfEntity(): void
84
    {
85
        $persistService = new PersistService(
86
            $this->entityName,
87
            $this->entityManager,
88
            $this->eventDispatcher,
89
            $this->logger
90
        );
91
92
        $this->assertSame($this->entityName, $persistService->getEntityName());
93
    }
94
95
    /**
96
     * If an exception is raised when calling save(), and our entity has an ID value, then the
97
     * EntityEventName::UPDATE_ERROR event should be triggered and a new exception thrown.
98
     *
99
     * @throws PersistenceException
100
     */
101
    public function testSaveExceptionWillBeCaughtLoggedAndZeroReturnedIfTheEntityIdIsNotNull(): void
102
    {
103
        $persistService = new PersistService(
104
            $this->entityName,
105
            $this->entityManager,
106
            $this->eventDispatcher,
107
            $this->logger
108
        );
109
110
        /** @var EntityInterface&MockObject $entity */
111
        $entity = $this->createMock(EntityInterface::class);
112
113
        $entity->expects($this->once())
114
            ->method('hasId')
115
            ->willReturn(true);
116
117
        $exceptionMessage = 'Test exception message for save() and update()';
118
        $exceptionCode = 123;
119
        $exception = new \Exception($exceptionMessage, $exceptionCode);
120
121
        $this->eventDispatcher->expects($this->exactly(2))
122
            ->method('dispatch')
123
            ->withConsecutive(
124
                [$this->isInstanceOf(EntityEvent::class)],
125
                [$this->isInstanceOf(EntityErrorEvent::class)],
126
            )->willReturnOnConsecutiveCalls(
127
                $this->throwException($exception),
128
                $this->returnArgument(0)
129
            );
130
131
        $this->assertSame($entity, $persistService->save($entity));
132
    }
133
134
    /**
135
     * Assert that if we pass an entity with an id to save() that the entity will be updated and returned.
136
     *
137
     * @param array<mixed> $options Optional save options that should be passed to the updated method.
138
     *
139
     * @dataProvider getSaveWillUpdateAndReturnEntityWithIdData
140
     *
141
     * @throws PersistenceException
142
     */
143
    public function testSaveWillUpdateAndReturnEntityWithId(array $options = []): void
144
    {
145
        /** @var PersistService&MockObject $persistService */
146
        $persistService = $this->getMockBuilder(PersistService::class)
147
            ->setConstructorArgs(
148
                [
149
                    $this->entityName,
150
                    $this->entityManager,
151
                    $this->eventDispatcher,
152
                    $this->logger,
153
                ]
154
            )->onlyMethods(['createEvent'])
155
            ->getMock();
156
157
        /** @var EntityInterface&MockObject $entity */
158
        $entity = $this->createMock(EntityInterface::class);
159
160
        $entity->expects($this->once())
161
            ->method('hasId')
162
            ->willReturn(true);
163
164
        /** @var EntityEvent&MockObject $event */
165
        $event = $this->createMock(EntityEvent::class);
166
167
        $persistService->expects($this->once())
168
            ->method('createEvent')
169
            ->with(EntityEventName::UPDATE, $entity, $options)
170
            ->willReturn($event);
171
172
        $this->eventDispatcher->expects($this->once())
173
            ->method('dispatch')
174
            ->with($event)
175
            ->willReturn($event);
176
177
        $event->expects($this->once())
178
            ->method('getEntity')
179
            ->willReturn($entity);
180
181
        $this->assertSame($entity, $persistService->save($entity, $options));
182
    }
183
184
    /**
185
     * @return array<mixed>
186
     */
187
    public function getSaveWillUpdateAndReturnEntityWithIdData(): array
188
    {
189
        return [
190
            [
191
                [
192
                    // empty options
193
                ],
194
            ],
195
        ];
196
    }
197
198
    /**
199
     * If an exception is raised when calling save(), and our entity does NOT have an ID value, then the
200
     * EntityEventName::CREATE_ERROR event should be triggered and a new exception thrown
201
     *
202
     * @throws PersistenceException
203
     */
204
    public function testSaveExceptionWillBeCaughtLoggedAndTheDispatchErrorEventTriggeredWhenEntityIdIsNull(): void
205
    {
206
        $persistService = new PersistService(
207
            $this->entityName,
208
            $this->entityManager,
209
            $this->eventDispatcher,
210
            $this->logger
211
        );
212
213
        /** @var EntityInterface&MockObject $entity */
214
        $entity = $this->createMock(EntityInterface::class);
215
216
        $entity->expects($this->once())
217
            ->method('hasId')
218
            ->willReturn(false);
219
220
        $exceptionMessage = 'Test exception message for ' . __FUNCTION__;
221
        $exceptionCode = 456;
222
        $exception = new \Exception($exceptionMessage, $exceptionCode);
223
224
        $this->eventDispatcher->expects($this->exactly(2))
225
            ->method('dispatch')
226
            ->withConsecutive(
227
                [$this->isInstanceOf(EntityEvent::class)],
228
                [$this->isInstanceOf(EntityErrorEvent::class)],
229
            )->willReturnOnConsecutiveCalls(
230
                $this->throwException($exception),
231
                $this->returnArgument(0)
232
            );
233
234
        $this->assertSame($entity, $persistService->save($entity));
235
    }
236
237
    /**
238
     * Assert that an entity provided to save() that does not have an identity will be proxies to insert()
239
     *
240
     * @param array<mixed> $options
241
     *
242
     * @dataProvider getSaveWillInsertAndReturnEntityWithNoIdData
243
     *
244
     * @throws PersistenceException
245
     */
246
    public function testSaveWillInsertAndReturnEntityWithNoId(array $options = []): void
247
    {
248
        /** @var PersistService&MockObject $persistService */
249
        $persistService = $this->getMockBuilder(PersistService::class)
250
            ->setConstructorArgs(
251
                [
252
                    $this->entityName,
253
                    $this->entityManager,
254
                    $this->eventDispatcher,
255
                    $this->logger,
256
                ]
257
            )->onlyMethods(['createEvent'])
258
            ->getMock();
259
260
        /** @var EntityInterface&MockObject $entity */
261
        $entity = $this->createMock(EntityInterface::class);
262
263
        $entity->expects($this->once())
264
            ->method('hasId')
265
            ->willReturn(false);
266
267
        /** @var EntityEvent&MockObject $event */
268
        $event = $this->createMock(EntityEvent::class);
269
270
        $persistService->expects($this->once())
271
            ->method('createEvent')
272
            ->with(EntityEventName::CREATE, $entity, $options)
273
            ->willReturn($event);
274
275
        $this->eventDispatcher->expects($this->once())
276
            ->method('dispatch')
277
            ->with($event)
278
            ->willReturn($event);
279
280
        $event->expects($this->once())
281
            ->method('getEntity')
282
            ->willReturn($entity);
283
284
        $this->assertSame($entity, $persistService->save($entity, $options));
285
    }
286
287
    /**
288
     * @return array<mixed>
289
     */
290
    public function getSaveWillInsertAndReturnEntityWithNoIdData(): array
291
    {
292
        return [
293
            [
294
295
            ],
296
        ];
297
    }
298
299
    /**
300
     * Assert that if an exception occurs during the dispatch of the delete event, the delete_error event will be
301
     * dispatched with the entity error event containing the caught exception
302
     *
303
     * @throws PersistenceException
304
     */
305
    public function testDeleteWillTriggerDeleteErrorEventOnError(): void
306
    {
307
        $entityName = EntityInterface::class;
308
309
        /** @var PersistService&MockObject $persistService */
310
        $persistService = $this->getMockBuilder(PersistService::class)
311
            ->setConstructorArgs([
312
                $entityName,
313
                $this->entityManager,
314
                $this->eventDispatcher,
315
                $this->logger,
316
            ])->onlyMethods(['createEvent', 'createErrorEvent'])
317
            ->getMock();
318
319
        /** @var EntityInterface&MockObject $entity */
320
        $entity = $this->createMock(EntityInterface::class);
321
        $options = [];
322
323
        /** @var EntityEvent&MockObject $event */
324
        $event = $this->createMock(EntityEvent::class);
325
326
        $persistService->expects($this->once())
327
            ->method('createEvent')
328
            ->with(EntityEventName::DELETE, $entity, $options)
329
            ->willReturn($event);
330
331
        $exceptionMessage = 'This is a test exception message for ' . __FUNCTION__;
332
        $exceptionCode = 123;
333
        $exception = new \Exception($exceptionMessage, $exceptionCode);
334
335
        /** @var EntityErrorEvent&MockObject $errorEvent */
336
        $errorEvent = $this->createMock(EntityErrorEvent::class);
337
338
        $this->eventDispatcher->expects($this->exactly(2))
339
            ->method('dispatch')
340
            ->withConsecutive([$event], [$errorEvent])
341
            ->willReturnOnConsecutiveCalls(
342
                $this->throwException($exception),
343
                $errorEvent
344
            );
345
346
        $persistService->expects($this->once())
347
            ->method('createErrorEvent')
348
            ->with(EntityEventName::DELETE_ERROR, $exception)
349
            ->willReturn($errorEvent);
350
351
        $this->assertFalse($persistService->delete($entity, $options));
352
    }
353
354
    /**
355
     * Assert that the delete event is dispatched when
356
     *
357
     * @throws PersistenceException
358
     */
359
    public function testDeleteWillDispatchDeleteEventAndReturnTrue(): void
360
    {
361
        $entityName = EntityInterface::class;
362
363
        /** @var PersistService&MockObject $persistService */
364
        $persistService = $this->getMockBuilder(PersistService::class)
365
            ->setConstructorArgs([
366
                $entityName,
367
                $this->entityManager,
368
                $this->eventDispatcher,
369
                $this->logger,
370
            ])->onlyMethods(['createEvent'])
371
            ->getMock();
372
373
        /** @var EntityInterface&MockObject $entity */
374
        $entity = $this->createMock(EntityInterface::class);
375
        $options = [];
376
377
        /** @var EntityEvent&MockObject $event */
378
        $event = $this->createMock(EntityEvent::class);
379
380
        $persistService->expects($this->once())
381
            ->method('createEvent')
382
            ->with(EntityEventName::DELETE, $entity, $options)
383
            ->willReturn($event);
384
385
        $this->eventDispatcher->expects($this->once())
386
            ->method('dispatch')
387
            ->with($event)
388
            ->willReturn($event);
389
390
        $this->assertTrue($persistService->delete($entity, $options));
391
    }
392
393
    /**
394
     * Assert that calls to deleteCollection() will dispatch the EntityEventName::DELETE_COLLECTION event using
395
     * the provided $collection
396
     *
397
     * @throws PersistenceException
398
     */
399
    public function testDeleteCollectionWillDispatchDeleteCollectionEvent(): void
400
    {
401
        $entityName = EntityInterface::class;
402
403
        /** @var PersistService&MockObject $persistService */
404
        $persistService = $this->getMockBuilder(PersistService::class)
405
            ->setConstructorArgs([
406
                $entityName,
407
                $this->entityManager,
408
                $this->eventDispatcher,
409
                $this->logger,
410
            ])->onlyMethods(['createCollectionEvent'])
411
            ->getMock();
412
413
        /** @var array<EntityInterface&MockObject> $collection */
414
        $collection = [
415
            $this->createMock(EntityInterface::class),
416
            $this->createMock(EntityInterface::class),
417
            $this->createMock(EntityInterface::class),
418
            $this->createMock(EntityInterface::class),
419
        ];
420
421
        $options = [
422
            EntityEventOption::LOG_ERRORS       => true,
423
            EntityEventOption::THROW_EXCEPTIONS => true,
424
            EntityEventOption::TRANSACTION_MODE => false,
425
        ];
426
427
        /** @var CollectionEvent&MockObject $event */
428
        $event = $this->createMock(CollectionEvent::class);
429
430
        $persistService->expects($this->once())
431
            ->method('createCollectionEvent')
432
            ->with(EntityEventName::DELETE_COLLECTION, $collection, $options)
433
            ->willReturn($event);
434
435
        $this->eventDispatcher->expects($this->once())
436
            ->method('dispatch')
437
            ->with($event)
438
            ->willReturn($event);
439
440
        $event->expects($this->once())
441
            ->method('getParam')
442
            ->with('deleted_count', 0)
443
            ->willReturn(count($collection));
444
445
        $this->assertSame(count($collection), $persistService->deleteCollection($collection, $options));
446
    }
447
448
    /**
449
     * Assert that calls to deleteCollection() will dispatch the EntityEventName::DELETE_COLLECTION_ERROR
450
     * if unable to delete the provided $collection
451
     *
452
     * @throws PersistenceException
453
     */
454
    public function testDeleteCollectionWillDispatchADeleteCollectionErrorEvent(): void
455
    {
456
        $entityName = EntityInterface::class;
457
458
        /** @var PersistService&MockObject $persistService */
459
        $persistService = $this->getMockBuilder(PersistService::class)
460
            ->setConstructorArgs([
461
                $entityName,
462
                $this->entityManager,
463
                $this->eventDispatcher,
464
                $this->logger,
465
            ])->onlyMethods(['createCollectionEvent', 'createErrorEvent'])
466
            ->getMock();
467
468
        /** @var array<EntityInterface&MockObject> $collection */
469
        $collection = [
470
            $this->createMock(EntityInterface::class),
471
            $this->createMock(EntityInterface::class),
472
            $this->createMock(EntityInterface::class),
473
            $this->createMock(EntityInterface::class),
474
        ];
475
476
        $options = [
477
            EntityEventOption::LOG_ERRORS       => true,
478
            EntityEventOption::THROW_EXCEPTIONS => true,
479
            EntityEventOption::TRANSACTION_MODE => false,
480
        ];
481
482
        /** @var CollectionEvent&MockObject $event */
483
        $event = $this->createMock(CollectionEvent::class);
484
485
        $persistService->expects($this->once())
486
            ->method('createCollectionEvent')
487
            ->with(EntityEventName::DELETE_COLLECTION, $collection, $options)
488
            ->willReturn($event);
489
490
        $exceptionMessage = 'This is a test exception message for ' . __FUNCTION__;
491
        $exceptionCode = 999;
492
        $exception = new \Exception($exceptionMessage, $exceptionCode);
493
494
        /** @var EntityErrorEvent&MockObject $errorEvent */
495
        $errorEvent = $this->createMock(EntityErrorEvent::class);
496
497
        $persistService->expects($this->once())
498
            ->method('createErrorEvent')
499
            ->with(EntityEventName::DELETE_COLLECTION_ERROR, $exception)
500
            ->willReturn($errorEvent);
501
502
        $this->eventDispatcher->expects($this->exactly(2))
503
            ->method('dispatch')
504
            ->withConsecutive([$event], [$errorEvent])
505
            ->willReturnOnConsecutiveCalls(
506
                $this->throwException($exception),
507
                $errorEvent
508
            );
509
510
        $event->expects($this->once())
511
            ->method('getParam')
512
            ->with('deleted_count', 0)
513
            ->willReturn(0);
514
515
        $this->assertSame(0, $persistService->deleteCollection($collection, $options));
516
    }
517
518
    /**
519
     * Assert that a PersistenceException will be thrown by persist() if the provided entity instance is not an
520
     * instance of the mapped entity class
521
     */
522
    public function testPersistWillThrowPersistenceExceptionIfProvidedEntityIsAnInvalidType(): void
523
    {
524
        $entityName = \stdClass::class;
525
526
        $persistService = new PersistService(
527
            $entityName, // Invalid entity class name...
528
            $this->entityManager,
529
            $this->eventDispatcher,
530
            $this->logger
531
        );
532
533
        /** @var EntityInterface&MockObject $entity */
534
        $entity = $this->createMock(EntityInterface::class);
535
536
        $errorMessage = sprintf(
537
            'The \'entity\' argument must be an object of type \'%s\'; \'%s\' provided in \'%s::%s\'',
538
            $entityName,
539
            get_class($entity),
540
            PersistService::class,
541
            'persist'
542
        );
543
544
        $this->logger->expects($this->once())
545
            ->method('error')
546
            ->with($errorMessage);
547
548
        $this->expectException(PersistenceException::class);
549
        $this->expectExceptionMessage($errorMessage);
550
551
        $persistService->persist($entity);
552
    }
553
554
    /**
555
     * Assert that if the $entity provided to persist() cannot be persisted any raised exception is caught, logged
556
     * and then rethrown as a PersistenceException
557
     */
558
    public function testPersistWillThrowPersistenceExceptionIfTheEntityCannotBePersisted(): void
559
    {
560
        $persistService = new PersistService(
561
            $this->entityName,
562
            $this->entityManager,
563
            $this->eventDispatcher,
564
            $this->logger
565
        );
566
567
        /** @var EntityInterface&MockObject $entity */
568
        $entity = $this->createMock(EntityInterface::class);
569
570
        $exceptionMessage = 'This is a test error message for persist()';
571
        $exceptionCode = 456;
572
        $exception = new \Exception($exceptionMessage, $exceptionCode);
573
574
        $this->entityManager->expects($this->once())
575
            ->method('persist')
576
            ->with($entity)
577
            ->willThrowException($exception);
578
579
        $errorMessage = sprintf(
580
            'The persist operation failed for entity \'%s\': %s',
581
            $this->entityName,
582
            $exceptionMessage
583
        );
584
585
        $this->logger->expects($this->once())
586
            ->method('error')
587
            ->with($errorMessage, ['exception' => $exception]);
588
589
        $this->expectException(PersistenceException::class);
590
        $this->expectExceptionMessage($errorMessage);
591
        $this->expectExceptionCode($exceptionCode);
592
593
        $persistService->persist($entity);
594
    }
595
596
    /**
597
     * Assert that persist() will successfully proxy the provided $entity to the entity manager persist()
598
     *
599
     * @throws PersistenceException
600
     */
601
    public function testPersist(): void
602
    {
603
        $persistService = new PersistService(
604
            $this->entityName,
605
            $this->entityManager,
606
            $this->eventDispatcher,
607
            $this->logger
608
        );
609
610
        /** @var EntityInterface&MockObject $entity */
611
        $entity = $this->createMock(EntityInterface::class);
612
613
        $this->entityManager->expects($this->once())
614
            ->method('persist')
615
            ->with($entity);
616
617
        $persistService->persist($entity);
618
    }
619
620
    /**
621
     * Assert that exception raised from flush() are logged and rethrown as PersistenceException
622
     *
623
     * @throws PersistenceException
624
     */
625
    public function testFlushExceptionsAreLoggedAndRethrownAsPersistenceException(): void
626
    {
627
        $persistService = new PersistService(
628
            $this->entityName,
629
            $this->entityManager,
630
            $this->eventDispatcher,
631
            $this->logger
632
        );
633
634
        $exceptionMessage = 'This is a test error message for flush()';
635
        $exceptionCode = 999;
636
        $exception = new \Exception($exceptionMessage, $exceptionCode);
637
638
        $this->entityManager->expects($this->once())
639
            ->method('flush')
640
            ->willThrowException($exception);
641
642
        $errorMessage = sprintf(
643
            'The flush operation failed for entity \'%s\': %s',
644
            $this->entityName,
645
            $exceptionMessage
646
        );
647
648
        $this->logger->expects($this->once())
649
            ->method('error')
650
            ->with($errorMessage, ['exception' => $exception, 'entity_name' => $this->entityName]);
651
652
        $this->expectException(PersistenceException::class);
653
        $this->expectExceptionMessage($errorMessage);
654
        $this->expectExceptionCode($exceptionCode);
655
656
        $persistService->flush();
657
    }
658
659
    /**
660
     * Assert that flush() will call the internal entity manager flush()
661
     *
662
     * @throws PersistenceException
663
     */
664
    public function testFlush(): void
665
    {
666
        $persistService = new PersistService(
667
            $this->entityName,
668
            $this->entityManager,
669
            $this->eventDispatcher,
670
            $this->logger
671
        );
672
673
        $this->entityManager->expects($this->once())->method('flush');
674
675
        $persistService->flush();
676
    }
677
678
    /**
679
     * Assert that exceptions raised in clear() are logged and rethrown as PersistenceException
680
     *
681
     * @throw  PersistenceException
682
     */
683
    public function testClearExceptionsAreLoggedAndRethrownAsPersistenceException(): void
684
    {
685
        $persistService = new PersistService(
686
            $this->entityName,
687
            $this->entityManager,
688
            $this->eventDispatcher,
689
            $this->logger
690
        );
691
692
        $exceptionMessage = 'This is a test exception message for clear()';
693
        $exceptionCode = 888;
694
        $exception = new \Exception($exceptionMessage, $exceptionCode);
695
696
        $this->entityManager->expects($this->once())
697
            ->method('clear')
698
            ->willThrowException($exception);
699
700
        $errorMessage = sprintf(
701
            'The clear operation failed for entity \'%s\': %s',
702
            $this->entityName,
703
            $exceptionMessage
704
        );
705
706
        $this->logger->expects($this->once())
707
            ->method('error')
708
            ->with($errorMessage, ['exception' => $exception, 'entity_name' => $this->entityName]);
709
710
        $this->expectException(PersistenceException::class);
711
        $this->expectExceptionMessage($errorMessage);
712
        $this->expectExceptionCode($exceptionCode);
713
714
        $persistService->clear();
715
    }
716
717
    /**
718
     * Assert that calls to clear() will proxy to the internal entity manager clear()
719
     *
720
     * @throws PersistenceException
721
     */
722
    public function testClear(): void
723
    {
724
        $persistService = new PersistService(
725
            $this->entityName,
726
            $this->entityManager,
727
            $this->eventDispatcher,
728
            $this->logger
729
        );
730
731
        $this->entityManager->expects($this->once())->method('clear');
732
733
        $persistService->clear();
734
    }
735
736
    /**
737
     * Assert a PersistenceException is thrown from refresh() if the provided Entity is not managed by this
738
     * repository class
739
     *
740
     * @throws PersistenceException
741
     */
742
    public function testRefreshWillThrowPersistenceExceptionForInvalidEntity(): void
743
    {
744
        $entity = new class() implements EntityInterface {
745
            use EntityTrait;
746
        };
747
748
        $entity2 = new class() implements EntityInterface {
749
            use EntityTrait;
750
        };
751
752
        $this->entityName = get_class($entity2);
753
754
        $persistService = new PersistService(
755
            $this->entityName,
756
            $this->entityManager,
757
            $this->eventDispatcher,
758
            $this->logger
759
        );
760
761
        $this->entityManager->expects($this->never())
762
            ->method('refresh')
763
            ->with($entity);
764
765
        $this->expectException(PersistenceException::class);
766
        $this->expectExceptionMessage(
767
            sprintf(
768
                'The \'entity\' argument must be an object of type \'%s\'; \'%s\' provided in \'%s\'',
769
                $this->entityName,
770
                get_class($entity),
771
                PersistService::class . '::refresh'
772
            )
773
        );
774
775
        $persistService->refresh($entity);
776
    }
777
778
    /**
779
     * Assert a PersistenceException is thrown from refresh() if the refresh() action fails
780
     *
781
     * @throws PersistenceException
782
     */
783
    public function testRefreshError(): void
784
    {
785
        $persistService = new PersistService(
786
            $this->entityName,
787
            $this->entityManager,
788
            $this->eventDispatcher,
789
            $this->logger
790
        );
791
792
        /** @var EntityInterface&MockObject $entity */
793
        $entity = $this->createMock(EntityInterface::class);
794
795
        $exceptionMessage = 'This is a test exception message for ' . __FUNCTION__;
796
        $exceptionCode = 987;
797
        $exception = new \Exception($exceptionMessage, $exceptionCode);
798
799
        $this->entityManager->expects($this->once())
800
            ->method('refresh')
801
            ->with($entity)
802
            ->willThrowException($exception);
803
804
        $this->expectException(PersistenceException::class);
805
        $this->expectExceptionCode($exceptionCode);
806
        $this->expectExceptionMessage(
807
            sprintf(
808
                'The refresh operation failed for entity \'%s\' : %s',
809
                $this->entityName,
810
                $exceptionMessage
811
            )
812
        );
813
814
        $persistService->refresh($entity);
815
    }
816
817
    /**
818
     * Assert calls to refresh() will result in the $entity being passed to EntityManager::refresh()
819
     *
820
     * @throws PersistenceException
821
     */
822
    public function testRefresh(): void
823
    {
824
        $persistService = new PersistService(
825
            $this->entityName,
826
            $this->entityManager,
827
            $this->eventDispatcher,
828
            $this->logger
829
        );
830
831
        /** @var EntityInterface&MockObject $entity */
832
        $entity = $this->createMock(EntityInterface::class);
833
834
        $this->entityManager->expects($this->once())
835
            ->method('refresh')
836
            ->with($entity);
837
838
        $persistService->refresh($entity);
839
    }
840
841
    /**
842
     * Assert calls to beginTransaction() will proxy to the managed EntityManager instance
843
     *
844
     * @throws PersistenceException
845
     */
846
    public function testBeginTransaction(): void
847
    {
848
        $persistService = new PersistService(
849
            $this->entityName,
850
            $this->entityManager,
851
            $this->eventDispatcher,
852
            $this->logger
853
        );
854
855
        $this->entityManager->expects($this->once())->method('beginTransaction');
856
857
        $persistService->beginTransaction();
858
    }
859
860
    /**
861
     * Assert calls to beginTransaction() will throw a PersistenceException on error
862
     *
863
     * @throws PersistenceException
864
     */
865
    public function testBeginTransactionError(): void
866
    {
867
        $persistService = new PersistService(
868
            $this->entityName,
869
            $this->entityManager,
870
            $this->eventDispatcher,
871
            $this->logger
872
        );
873
874
        $exceptionMessage = 'This is a test exception message for ' . __FUNCTION__;
875
        $exceptionCode = 123;
876
        $exception = new \Exception($exceptionMessage, $exceptionCode);
877
878
        $this->entityManager->expects($this->once())
879
            ->method('beginTransaction')
880
            ->willThrowException($exception);
881
882
        $this->expectException(PersistenceException::class);
883
        $this->expectExceptionMessage(sprintf('Failed to start transaction : %s', $exceptionMessage));
884
        $this->expectExceptionCode($exceptionCode);
885
886
        $persistService->beginTransaction();
887
    }
888
889
    /**
890
     * Assert calls to commitTransaction() will proxy to the managed EntityManager instance
891
     *
892
     * @throws PersistenceException
893
     */
894
    public function testCommitTransaction(): void
895
    {
896
        $persistService = new PersistService(
897
            $this->entityName,
898
            $this->entityManager,
899
            $this->eventDispatcher,
900
            $this->logger
901
        );
902
903
        $this->entityManager->expects($this->once())->method('commit');
904
905
        $persistService->commitTransaction();
906
    }
907
908
    /**
909
     * Assert calls to commitTransaction() will throw a PersistenceException on error
910
     *
911
     * @throws PersistenceException
912
     */
913
    public function testCommitTransactionError(): void
914
    {
915
        $persistService = new PersistService(
916
            $this->entityName,
917
            $this->entityManager,
918
            $this->eventDispatcher,
919
            $this->logger
920
        );
921
922
        $exceptionMessage = 'This is a test exception message for ' . __FUNCTION__;
923
        $exceptionCode = 123;
924
        $exception = new \Exception($exceptionMessage, $exceptionCode);
925
926
        $this->entityManager->expects($this->once())
927
            ->method('commit')
928
            ->willThrowException($exception);
929
930
        $this->expectException(PersistenceException::class);
931
        $this->expectExceptionMessage(sprintf('Failed to commit transaction : %s', $exceptionMessage));
932
        $this->expectExceptionCode($exceptionCode);
933
934
        $persistService->commitTransaction();
935
    }
936
937
    /**
938
     * Assert calls to rollbackTransaction() will proxy to the managed EntityManager instance
939
     *
940
     * @throws PersistenceException
941
     */
942
    public function testRollbackTransaction(): void
943
    {
944
        $persistService = new PersistService(
945
            $this->entityName,
946
            $this->entityManager,
947
            $this->eventDispatcher,
948
            $this->logger
949
        );
950
951
        $this->entityManager->expects($this->once())->method('rollback');
952
953
        $persistService->rollbackTransaction();
954
    }
955
956
    /**
957
     * Assert calls to rollbackTransaction() will throw a PersistenceException on error
958
     *
959
     * @throws PersistenceException
960
     */
961
    public function testRollbackTransactionError(): void
962
    {
963
        $persistService = new PersistService(
964
            $this->entityName,
965
            $this->entityManager,
966
            $this->eventDispatcher,
967
            $this->logger
968
        );
969
970
        $exceptionMessage = 'This is a test exception message for ' . __FUNCTION__;
971
        $exceptionCode = 123;
972
        $exception = new \Exception($exceptionMessage, $exceptionCode);
973
974
        $this->entityManager->expects($this->once())
975
            ->method('rollback')
976
            ->willThrowException($exception);
977
978
        $this->expectException(PersistenceException::class);
979
        $this->expectExceptionMessage(sprintf('Failed to rollback transaction : %s', $exceptionMessage));
980
        $this->expectExceptionCode($exceptionCode);
981
982
        $persistService->rollbackTransaction();
983
    }
984
}
985