GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#152)
by joseph
17:58
created

AbstractEntityTest::assertCorrectMapping()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 50
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 39
nc 2
nop 3
dl 0
loc 50
ccs 0
cts 48
cp 0
crap 6
rs 9.296
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace EdmondsCommerce\DoctrineStaticMeta\Entity\Testing;
4
5
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
6
use Doctrine\ORM\EntityManagerInterface;
7
use Doctrine\ORM\Mapping\ClassMetadata;
8
use Doctrine\ORM\Tools\SchemaValidator;
9
use Doctrine\ORM\Utility\PersisterHelper;
10
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\CodeHelper;
11
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\RelationsGenerator;
12
use EdmondsCommerce\DoctrineStaticMeta\Config;
13
use EdmondsCommerce\DoctrineStaticMeta\ConfigInterface;
14
use EdmondsCommerce\DoctrineStaticMeta\Entity\DataTransferObjects\DtoFactory;
15
use EdmondsCommerce\DoctrineStaticMeta\Entity\Embeddable\Objects\AbstractEmbeddableObject;
16
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Interfaces\PrimaryKey\IdFieldInterface;
17
use EdmondsCommerce\DoctrineStaticMeta\Entity\Interfaces\EntityInterface;
18
use EdmondsCommerce\DoctrineStaticMeta\Entity\Repositories\RepositoryFactory;
19
use EdmondsCommerce\DoctrineStaticMeta\Entity\Savers\EntitySaverFactory;
20
use EdmondsCommerce\DoctrineStaticMeta\Entity\Savers\EntitySaverInterface;
21
use EdmondsCommerce\DoctrineStaticMeta\Entity\Testing\EntityGenerator\TestEntityGenerator;
22
use EdmondsCommerce\DoctrineStaticMeta\Entity\Testing\EntityGenerator\TestEntityGeneratorFactory;
23
use EdmondsCommerce\DoctrineStaticMeta\Exception\ConfigException;
24
use EdmondsCommerce\DoctrineStaticMeta\MappingHelper;
25
use EdmondsCommerce\DoctrineStaticMeta\SimpleEnv;
26
use PHPUnit\Framework\TestCase;
27
use Psr\Container\ContainerInterface;
28
29
/**
30
 * Class AbstractEntityTest
31
 *
32
 * This abstract test is designed to give you a good level of test coverage for your entities without any work required.
33
 *
34
 * You should extend the test with methods that test your specific business logic, your validators and anything else.
35
 *
36
 * You can override the methods, properties and constants as you see fit.
37
 *
38
 * @package EdmondsCommerce\DoctrineStaticMeta\Entity
39
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
40
 * @SuppressWarnings(PHPMD.NumberOfChildren)
41
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
42
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
43
 * @SuppressWarnings(PHPMD.StaticAccess)
44
 */
45
abstract class AbstractEntityTest extends TestCase implements EntityTestInterface
46
{
47
    /**
48
     * @var ContainerInterface
49
     */
50
    protected static $container;
51
    /**
52
     * The fully qualified name of the Entity being tested, as calculated by the test class name
53
     *
54
     * @var string
55
     */
56
    protected static $testedEntityFqn;
57
58
    /**
59
     * @var TestEntityGenerator
60
     */
61
    protected static $testEntityGenerator;
62
63
    /**
64
     * @var array
65
     */
66
    protected static $schemaErrors = [];
67
68
    public static function setUpBeforeClass(): void
69
    {
70
        if (null !== static::$container) {
71
            self::tearDownAfterClass();
72
        }
73
        static::initContainer();
74
        static::$testedEntityFqn     = \substr(static::class, 0, -4);
75
        static::$testEntityGenerator = static::$container->get(TestEntityGeneratorFactory::class)
76
                                                         ->createForEntityFqn(static::$testedEntityFqn);
77
    }
78
79
    public static function tearDownAfterClass()
80
    {
81
        $entityManager = static::$container->get(EntityManagerInterface::class);
82
        $entityManager->close();
83
        $entityManager->getConnection()->close();
84
        self::$container   = null;
85
        static::$container = null;
86
    }
87
88
    public static function initContainer(): void
89
    {
90
        $testConfig        = self::getTestContainerConfig();
91
        static::$container = TestContainerFactory::getContainer($testConfig);
92
    }
93
94
    /**
95
     * @throws ConfigException
96
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
97
     * @SuppressWarnings(PHPMD.Superglobals)
98
     * @SuppressWarnings(PHPMD.StaticAccess)
99
     */
100
    protected static function getTestContainerConfig(): array
101
    {
102
        SimpleEnv::setEnv(Config::getProjectRootDirectory() . '/.env');
103
        $testConfig                                 = $_SERVER;
104
        $testConfig[ConfigInterface::PARAM_DB_NAME] = $_SERVER[ConfigInterface::PARAM_DB_NAME] . '_test';
105
106
        return $testConfig;
107
    }
108
109
    /**
110
     * @test
111
     * Use Doctrine's built in schema validation tool to catch issues
112
     */
113
    public function theSchemaIsValidForThisEntity()
114
    {
115
        $errors  = $this->getSchemaErrors();
116
        $message = '';
117
        if (isset($errors[static::$testedEntityFqn])) {
118
            $message = "Failed ORM Validate Schema:\n";
119
            foreach ($errors[static::$testedEntityFqn] as $err) {
120
                $message .= "\n * $err \n";
121
            }
122
        }
123
        self::assertEmpty($message, $message);
124
    }
125
126
    /**
127
     * Use Doctrine's standard schema validation to get errors for the whole schema
128
     *
129
     * We cache this as a class property because the schema only needs validating once as a whole, after that we can
130
     * pull out Entity specific issues as required
131
     *
132
     * @param bool $update
133
     *
134
     * @return array
135
     * @throws \Exception
136
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
137
     */
138
    protected function getSchemaErrors(bool $update = false): array
139
    {
140
        if ([] === static::$schemaErrors || true === $update) {
141
            $validator            = new SchemaValidator($this->getEntityManager());
142
            static::$schemaErrors = $validator->validateMapping();
143
        }
144
145
        return static::$schemaErrors;
146
    }
147
148
    protected function getEntityManager(): EntityManagerInterface
149
    {
150
        return static::$container->get(EntityManagerInterface::class);
151
    }
152
153
    /**
154
     * Loop through Entity fields, call the getter and where possible assert there is a value returned
155
     *
156
     * @param EntityInterface $entity
157
     *
158
     * @return EntityInterface
159
     * @throws \Doctrine\ORM\Query\QueryException
160
     * @throws \ReflectionException
161
     * @SuppressWarnings(PHPMD.StaticAccess)
162
     * @test
163
     * @depends weCanGenerateANewEntityInstance
164
     */
165
    public function theEntityGettersReturnValues(EntityInterface $entity): EntityInterface
166
    {
167
        $meta = $this->getEntityManager()->getClassMetadata(static::$testedEntityFqn);
168
        $dto  = $this->getDtoFactory()->createDtoFromEntity($entity);
169
        foreach ($meta->getFieldNames() as $fieldName) {
170
            if ('id' === $fieldName) {
171
                continue;
172
            }
173
            $type   = PersisterHelper::getTypeOfField($fieldName, $meta, $this->getEntityManager())[0];
174
            $method = $this->getGetterNameForField($fieldName, $type);
175
            if (\ts\stringContains($method, '.')) {
176
                list($getEmbeddableMethod,) = explode('.', $method);
177
                $embeddable = $entity->$getEmbeddableMethod();
178
                self::assertInstanceOf(AbstractEmbeddableObject::class, $embeddable);
179
                continue;
180
            }
181
            $reflectionMethod = new \ReflectionMethod($entity, $method);
182
            if ($reflectionMethod->hasReturnType()) {
183
                $returnType = $reflectionMethod->getReturnType();
184
                $allowsNull = $returnType->allowsNull();
185
                if ($allowsNull) {
186
                    // As we can't assert anything here so simply call
187
                    // the method and allow the type hint to raise any
188
                    // errors.
189
                    $entity->$method();
190
                    continue;
191
                }
192
                self::assertNotNull($dto->$method(), "$fieldName getter returned null");
193
                continue;
194
            }
195
            // If there is no return type then we can't assert anything,
196
            // but again we can just call the getter to check for errors
197
            $dto->$method();
198
        }
199
        if (0 === $this->getCount()) {
200
            self::markTestSkipped('No assertable getters in this Entity');
201
        }
202
203
        return $entity;
204
    }
205
206
    protected function getDtoFactory(): DtoFactory
207
    {
208
        return static::$container->get(DtoFactory::class);
209
    }
210
211
    protected function getGetterNameForField(string $fieldName, string $type): string
212
    {
213
        if ($type === 'boolean') {
214
            return static::$container->get(CodeHelper::class)->getGetterMethodNameForBoolean($fieldName);
215
        }
216
217
        return 'get' . $fieldName;
218
    }
219
220
    /**
221
     * Test that we have correctly generated an instance of our test entity
222
     *
223
     * @return EntityInterface
224
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
225
     * @throws \ErrorException
226
     * @throws \ReflectionException
227
     * @SuppressWarnings(PHPMD.StaticAccess)
228
     * @test
229
     */
230
    public function weCanGenerateANewEntityInstance(): EntityInterface
231
    {
232
        $generated = $this->getTestEntityGenerator()->generateEntity();
233
        self::assertInstanceOf(static::$testedEntityFqn, $generated);
234
235
        return $generated;
236
    }
237
238
    protected function getTestEntityGenerator(): TestEntityGenerator
239
    {
240
        return static::$testEntityGenerator;
241
    }
242
243
    /**
244
     * @param EntityInterface $generated
245
     *
246
     * @return EntityInterface
247
     * @test
248
     * @depends theEntityGettersReturnValues
249
     * @throws \ErrorException
250
     */
251
    public function weCanExtendTheEntityWithUnrequiredAssociationEntities(EntityInterface $generated): EntityInterface
252
    {
253
        if ([] === $this->getTestedEntityClassMetaData()->getAssociationMappings()) {
254
            $this->markTestSkipped('No associations to test');
255
        }
256
        $this->getTestEntityGenerator()->addAssociationEntities($generated);
257
        $this->assertAllAssociationsAreNotEmpty($generated);
258
259
        return $generated;
260
    }
261
262
    protected function getTestedEntityClassMetaData(): ClassMetadata
263
    {
264
        return $this->getEntityManager()->getClassMetadata(static::$testedEntityFqn);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getEntityM...tatic::testedEntityFqn) returns the type Doctrine\Common\Persistence\Mapping\ClassMetadata which includes types incompatible with the type-hinted return Doctrine\ORM\Mapping\ClassMetadata.
Loading history...
265
    }
266
267
    protected function assertAllAssociationsAreNotEmpty(EntityInterface $entity)
268
    {
269
        $meta = $this->getTestedEntityClassMetaData();
270
        foreach ($meta->getAssociationMappings() as $mapping) {
271
            $getter = 'get' . $mapping['fieldName'];
272
            if ($meta->isCollectionValuedAssociation($mapping['fieldName'])) {
273
                $collection = $entity->$getter()->toArray();
274
                $this->assertCorrectMappings(static::$testedEntityFqn, $mapping);
275
                self::assertNotEmpty(
276
                    $collection,
277
                    'Failed to load the collection of the associated entity [' . $mapping['fieldName']
278
                    . '] from the generated ' . static::$testedEntityFqn
279
                    . ', make sure you have reciprocal adding of the association'
280
                );
281
282
                continue;
283
            }
284
            $association = $entity->$getter();
285
            self::assertNotEmpty(
286
                $association,
287
                'Failed to load the associated entity: [' . $mapping['fieldName']
288
                . '] from the generated ' . static::$testedEntityFqn
289
            );
290
            self::assertNotEmpty(
291
                $association->getId(),
292
                'Failed to get the ID of the associated entity: [' . $mapping['fieldName']
293
                . '] from the generated ' . static::$testedEntityFqn
294
            );
295
        }
296
    }
297
298
    /**
299
     * Check the mapping of our class and the associated entity to make sure it's configured properly on both sides.
300
     * Very easy to get wrong. This is in addition to the standard Schema Validation
301
     *
302
     * @param string $classFqn
303
     * @param array  $mapping
304
     */
305
    protected function assertCorrectMappings(string $classFqn, array $mapping)
306
    {
307
        $entityManager                        = $this->getEntityManager();
308
        $pass                                 = false;
309
        $associationFqn                       = $mapping['targetEntity'];
310
        $associationMeta                      = $entityManager->getClassMetadata($associationFqn);
311
        $classTraits                          = $entityManager->getClassMetadata($classFqn)
312
                                                              ->getReflectionClass()
313
                                                              ->getTraits();
314
        $unidirectionalTraitShortNamePrefixes = [
315
            'Has' . $associationFqn::getDoctrineStaticMeta()->getSingular() . RelationsGenerator::PREFIX_UNIDIRECTIONAL,
316
            'Has' . $associationFqn::getDoctrineStaticMeta()->getPlural() . RelationsGenerator::PREFIX_UNIDIRECTIONAL,
317
            'Has' . RelationsGenerator::PREFIX_REQUIRED .
318
            $associationFqn::getDoctrineStaticMeta()->getSingular() . RelationsGenerator::PREFIX_UNIDIRECTIONAL,
319
            'Has' . RelationsGenerator::PREFIX_REQUIRED .
320
            $associationFqn::getDoctrineStaticMeta()->getPlural() . RelationsGenerator::PREFIX_UNIDIRECTIONAL,
321
        ];
322
        foreach ($classTraits as $trait) {
323
            foreach ($unidirectionalTraitShortNamePrefixes as $namePrefix) {
324
                if (0 === \stripos($trait->getShortName(), $namePrefix)) {
325
                    return;
326
                }
327
            }
328
        }
329
        foreach ($associationMeta->getAssociationMappings() as $associationMapping) {
0 ignored issues
show
Bug introduced by
The method getAssociationMappings() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. Did you maybe mean getAssociationNames()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

329
        foreach ($associationMeta->/** @scrutinizer ignore-call */ getAssociationMappings() as $associationMapping) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
330
            if ($classFqn === $associationMapping['targetEntity']) {
331
                $pass = $this->assertCorrectMapping($mapping, $associationMapping, $classFqn);
332
                break;
333
            }
334
        }
335
        self::assertTrue(
336
            $pass,
337
            'Failed finding association mapping to test for ' . "\n" . $mapping['targetEntity']
338
        );
339
    }
340
341
    /**
342
     * @param array  $mapping
343
     * @param array  $associationMapping
344
     * @param string $classFqn
345
     *
346
     * @return bool
347
     */
348
    protected function assertCorrectMapping(array $mapping, array $associationMapping, string $classFqn): bool
349
    {
350
        if (empty($mapping['joinTable'])) {
351
            self::assertArrayNotHasKey(
352
                'joinTable',
353
                $associationMapping,
354
                $classFqn . ' join table is empty,
355
                        but association ' . $mapping['targetEntity'] . ' join table is not empty'
356
            );
357
358
            return true;
359
        }
360
        self::assertNotEmpty(
361
            $associationMapping['joinTable'],
362
            "$classFqn joinTable is set to " . $mapping['joinTable']['name']
363
            . " \n association " . $mapping['targetEntity'] . ' join table is empty'
364
        );
365
        self::assertSame(
366
            $mapping['joinTable']['name'],
367
            $associationMapping['joinTable']['name'],
368
            "join tables not the same: \n * $classFqn = " . $mapping['joinTable']['name']
369
            . " \n * association " . $mapping['targetEntity']
370
            . ' = ' . $associationMapping['joinTable']['name']
371
        );
372
        self::assertArrayHasKey(
373
            'inverseJoinColumns',
374
            $associationMapping['joinTable'],
375
            "join table join columns not the same: \n * $classFqn joinColumn = "
376
            . $mapping['joinTable']['joinColumns'][0]['name']
377
            . " \n * association " . $mapping['targetEntity']
378
            . ' inverseJoinColumn is not set'
379
        );
380
        self::assertSame(
381
            $mapping['joinTable']['joinColumns'][0]['name'],
382
            $associationMapping['joinTable']['inverseJoinColumns'][0]['name'],
383
            "join table join columns not the same: \n * $classFqn joinColumn = "
384
            . $mapping['joinTable']['joinColumns'][0]['name']
385
            . " \n * association " . $mapping['targetEntity']
386
            . ' inverseJoinColumn = ' . $associationMapping['joinTable']['inverseJoinColumns'][0]['name']
387
        );
388
        self::assertSame(
389
            $mapping['joinTable']['inverseJoinColumns'][0]['name'],
390
            $associationMapping['joinTable']['joinColumns'][0]['name'],
391
            "join table join columns  not the same: \n * $classFqn inverseJoinColumn = "
392
            . $mapping['joinTable']['inverseJoinColumns'][0]['name']
393
            . " \n * association " . $mapping['targetEntity'] . ' joinColumn = '
394
            . $associationMapping['joinTable']['joinColumns'][0]['name']
395
        );
396
397
        return true;
398
    }
399
400
    /**
401
     * @param EntityInterface $generated
402
     *
403
     * @return EntityInterface
404
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
405
     * @throws \ReflectionException
406
     * @test
407
     * @depends weCanExtendTheEntityWithUnrequiredAssociationEntities
408
     */
409
    public function theEntityCanBeSavedAndReloadedFromTheDatabase(EntityInterface $generated): EntityInterface
410
    {
411
        $this->getEntitySaver()->save($generated);
412
        $loaded = $this->loadEntity($generated->getId());
413
        self::assertSame((string)$generated->getId(), (string)$loaded->getId());
414
        self::assertInstanceOf(static::$testedEntityFqn, $loaded);
415
416
        return $loaded;
417
    }
418
419
    protected function getEntitySaver(): EntitySaverInterface
420
    {
421
        return static::$container->get(EntitySaverFactory::class)->getSaverForEntityFqn(static::$testedEntityFqn);
422
    }
423
424
    /**
425
     * @param mixed $id
426
     *
427
     * @return EntityInterface|null
428
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
429
     */
430
    protected function loadEntity($id): EntityInterface
431
    {
432
        return static::$container->get(RepositoryFactory::class)
433
                                 ->getRepository(static::$testedEntityFqn)
434
                                 ->get($id);
435
    }
436
437
    /**
438
     * @param EntityInterface $loaded
439
     *
440
     * @return EntityInterface
441
     * @throws ConfigException
442
     * @throws \Doctrine\ORM\Query\QueryException
443
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
444
     * @throws \ErrorException
445
     * @throws \ReflectionException
446
     * @depends theEntityCanBeSavedAndReloadedFromTheDatabase
447
     * @test
448
     */
449
    public function theLoadedEntityCanBeUpdatedAndResaved(EntityInterface $loaded): EntityInterface
450
    {
451
        $this->updateEntityFields($loaded);
452
        $this->assertAllAssociationsAreNotEmpty($loaded);
453
        $this->removeAllAssociations($loaded);
454
        $this->getEntitySaver()->save($loaded);
455
456
        return $loaded;
457
    }
458
459
    /**
460
     * Generate a new entity and then update our Entity with the values from the generated one
461
     *
462
     * @param EntityInterface $entity
463
     *
464
     * @SuppressWarnings(PHPMD.StaticAccess)
465
     */
466
    protected function updateEntityFields(EntityInterface $entity): void
467
    {
468
        $dto = $this->getDtoFactory()->createDtoFromEntity($entity);
469
        $this->getTestEntityGenerator()->fakerUpdateDto($dto);
470
        $entity->update($dto);
471
    }
472
473
    /**
474
     * @param EntityInterface $entity
475
     *
476
     * @throws \ReflectionException
477
     * @SuppressWarnings(PHPMD.StaticAccess)
478
     */
479
    protected function removeAllAssociations(EntityInterface $entity)
480
    {
481
        $required    = $entity::getDoctrineStaticMeta()->getRequiredRelationProperties();
482
        $meta        = $this->getTestedEntityClassMetaData();
483
        $identifiers = array_flip($meta->getIdentifier());
484
        foreach ($meta->getAssociationMappings() as $mapping) {
485
            if (isset($identifiers[$mapping['fieldName']])) {
486
                continue;
487
            }
488
            if (isset($required[$mapping['fieldName']])) {
489
                continue;
490
            }
491
            $remover = 'remove' . MappingHelper::singularize($mapping['fieldName']);
492
            if ($meta->isCollectionValuedAssociation($mapping['fieldName'])) {
493
                $getter    = 'get' . $mapping['fieldName'];
494
                $relations = $entity->$getter();
495
                foreach ($relations as $relation) {
496
                    $entity->$remover($relation);
497
                }
498
                continue;
499
            }
500
            $entity->$remover();
501
        }
502
        $this->assertAllAssociationsAreEmpty($entity);
503
    }
504
505
    protected function assertAllAssociationsAreEmpty(EntityInterface $entity)
506
    {
507
        $required    = $entity::getDoctrineStaticMeta()->getRequiredRelationProperties();
508
        $meta        = $this->getTestedEntityClassMetaData();
509
        $identifiers = array_flip($meta->getIdentifier());
510
        foreach ($meta->getAssociationMappings() as $mapping) {
511
            if (isset($identifiers[$mapping['fieldName']])) {
512
                continue;
513
            }
514
            if (isset($required[$mapping['fieldName']])) {
515
                continue;
516
            }
517
518
            $getter = 'get' . $mapping['fieldName'];
519
            if ($meta->isCollectionValuedAssociation($mapping['fieldName'])) {
520
                $collection = $entity->$getter()->toArray();
521
                self::assertEmpty(
522
                    $collection,
523
                    'Collection of the associated entity [' . $mapping['fieldName']
524
                    . '] is not empty after calling remove'
525
                );
526
                continue;
527
            }
528
            $association = $entity->$getter();
529
            self::assertEmpty(
530
                $association,
531
                'Failed to remove associated entity: [' . $mapping['fieldName']
532
                . '] from the generated ' . static::$testedEntityFqn
533
            );
534
        }
535
    }
536
537
    /**
538
     * @param EntityInterface $entity
539
     *
540
     * @return EntityInterface
541
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
542
     * @depends theLoadedEntityCanBeUpdatedAndResaved
543
     * @test
544
     */
545
    public function theReloadedEntityHasNoAssociatedEntities(EntityInterface $entity): EntityInterface
546
    {
547
        $reLoaded     = $this->loadEntity($entity->getId());
548
        $entityDump   = $this->dump($entity);
549
        $reLoadedDump = $this->dump($reLoaded);
550
        self::assertEquals($entityDump, $reLoadedDump);
551
        $this->assertAllAssociationsAreEmpty($reLoaded);
552
553
        return $reLoaded;
554
    }
555
556
    protected function dump(EntityInterface $entity): string
557
    {
558
        return (new EntityDebugDumper())->dump($entity, $this->getEntityManager());
559
    }
560
561
    /**
562
     * @depends weCanGenerateANewEntityInstance
563
     *
564
     * @param EntityInterface $entity
565
     *
566
     * @test
567
     */
568
    public function checkAllGettersCanBeReturnedFromDoctrineStaticMeta(EntityInterface $entity)
569
    {
570
        $getters = $entity::getDoctrineStaticMeta()->getGetters();
571
        self::assertNotEmpty($getters);
572
        foreach ($getters as $getter) {
573
            self::assertRegExp('%^(get|is|has).+%', $getter);
574
        }
575
    }
576
577
    /**
578
     * @depends weCanGenerateANewEntityInstance
579
     * @test
580
     *
581
     * @param EntityInterface $entity
582
     */
583
    public function checkAllSettersCanBeReturnedFromDoctrineStaticMeta(EntityInterface $entity)
584
    {
585
        $setters = $entity::getDoctrineStaticMeta()->getSetters();
586
        self::assertNotEmpty($setters);
587
        foreach ($setters as $setter) {
588
            self::assertRegExp('%^(set|add).+%', $setter);
589
        }
590
    }
591
592
    /**
593
     * Loop through entity fields and find unique ones
594
     *
595
     * Then ensure that the unique rule is being enforced as expected
596
     *
597
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
598
     * @throws \ErrorException
599
     * @throws \ReflectionException
600
     * @test
601
     * @depends theReloadedEntityHasNoAssociatedEntities
602
     */
603
    public function checkThatWeCanNotSaveEntitiesWithDuplicateUniqueFieldValues(): void
604
    {
605
        $meta         = $this->getTestedEntityClassMetaData();
606
        $uniqueFields = [];
607
        foreach ($meta->getFieldNames() as $fieldName) {
608
            if (IdFieldInterface::PROP_ID === $fieldName) {
609
                continue;
610
            }
611
            if (true === $this->isUniqueField($fieldName)) {
612
                $uniqueFields[] = $fieldName;
613
            }
614
        }
615
        if ([] === $uniqueFields) {
616
            self::markTestSkipped('No unique fields to check');
617
618
            return;
619
        }
620
        foreach ($uniqueFields as $fieldName) {
621
            $primary      = $this->getTestEntityGenerator()->generateEntity();
622
            $secondary    = $this->getTestEntityGenerator()->generateEntity();
623
            $secondaryDto = $this->getDtoFactory()->createDtoFromEntity($secondary);
624
            $getter       = 'get' . $fieldName;
625
            $setter       = 'set' . $fieldName;
626
            $primaryValue = $primary->$getter();
627
            $secondaryDto->$setter($primaryValue);
628
            $secondary->update($secondaryDto);
629
            $saver = $this->getEntitySaver();
630
            $this->expectException(UniqueConstraintViolationException::class);
631
            $saver->saveAll([$primary, $secondary]);
632
        }
633
    }
634
635
    protected function isUniqueField(string $fieldName): bool
636
    {
637
        $fieldMapping = $this->getTestedEntityClassMetaData()->getFieldMapping($fieldName);
638
639
        return array_key_exists('unique', $fieldMapping) && true === $fieldMapping['unique'];
640
    }
641
}
642