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 (#154)
by joseph
19:00
created

AbstractEntityTest::getDtoFactory()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

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