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 (#205)
by joseph
36:13 queued 10s
created

AbstractEntityTest::initialiseEntity()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 6
ccs 0
cts 6
cp 0
crap 2
rs 10
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\Entity\Testing\Fixtures\FixturesHelperFactory;
26
use EdmondsCommerce\DoctrineStaticMeta\Exception\ConfigException;
27
use EdmondsCommerce\DoctrineStaticMeta\MappingHelper;
28
use EdmondsCommerce\DoctrineStaticMeta\SimpleEnv;
29
use PHPUnit\Framework\TestCase;
30
use Psr\Container\ContainerInterface;
31
32
/**
33
 * Class AbstractEntityTest
34
 *
35
 * This abstract test is designed to give you a good level of test coverage for your entities without any work required.
36
 *
37
 * You should extend the test with methods that test your specific business logic, your validators and anything else.
38
 *
39
 * You can override the methods, properties and constants as you see fit.
40
 *
41
 * @package EdmondsCommerce\DoctrineStaticMeta\Entity
42
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
43
 * @SuppressWarnings(PHPMD.NumberOfChildren)
44
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
45
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
46
 * @SuppressWarnings(PHPMD.StaticAccess)
47
 */
48
abstract class AbstractEntityTest extends TestCase implements EntityTestInterface
49
{
50
    /**
51
     * @var ContainerInterface
52
     */
53
    protected static $container;
54
    /**
55
     * The fully qualified name of the Entity being tested, as calculated by the test class name
56
     *
57
     * @var string
58
     */
59
    protected static $testedEntityFqn;
60
61
    /**
62
     * @var TestEntityGenerator
63
     */
64
    protected static $testEntityGenerator;
65
66
    /**
67
     * @var TestEntityGeneratorFactory
68
     */
69
    protected static $testEntityGeneratorFactory;
70
71
    /**
72
     * @var array
73
     */
74
    protected static $schemaErrors = [];
75
76
    public static function setUpBeforeClass(): void
77
    {
78
        if (null !== static::$container) {
79
            self::tearDownAfterClass();
80
        }
81
        static::initContainer();
82
        static::$testedEntityFqn            = \substr(static::class, 0, -4);
83
        static::$testEntityGeneratorFactory = static::$container->get(TestEntityGeneratorFactory::class);
84
        static::$testEntityGenerator        =
85
            static::$testEntityGeneratorFactory->createForEntityFqn(static::$testedEntityFqn);
86
    }
87
88
    public static function tearDownAfterClass()
89
    {
90
        $entityManager = static::$container->get(EntityManagerInterface::class);
91
        $entityManager->close();
92
        $entityManager->getConnection()->close();
93
        self::$container   = null;
94
        static::$container = null;
95
    }
96
97
    public static function initContainer(): void
98
    {
99
        $testConfig        = self::getTestContainerConfig();
100
        static::$container = TestContainerFactory::getContainer($testConfig);
101
    }
102
103
    /**
104
     * @throws ConfigException
105
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
106
     * @SuppressWarnings(PHPMD.Superglobals)
107
     * @SuppressWarnings(PHPMD.StaticAccess)
108
     */
109
    public static function getTestContainerConfig(): array
110
    {
111
        SimpleEnv::setEnv(Config::getProjectRootDirectory() . '/.env');
112
        $testConfig = $_SERVER;
113
        if (preg_match('%_test$%m', $_SERVER[ConfigInterface::PARAM_DB_NAME]) !== 1) {
114
            $testConfig[ConfigInterface::PARAM_DB_NAME] = $_SERVER[ConfigInterface::PARAM_DB_NAME] . '_test';
115
        }
116
117
        return $testConfig;
118
    }
119
120
    /**
121
     * This test checks that the fixtures can be loaded properly
122
     *
123
     * The test returns the array of fixtures which means you could choose to add a test that `depends` on this test
124
     *
125
     * @test
126
     */
127
    public function theFixtureCanBeLoaded(): array
128
    {
129
        /**
130
         * @var FixturesHelper $fixtureHelper
131
         */
132
        $fixtureHelper = $this->getFixturesHelper();
133
        /**
134
         * This can seriously hurt performance, but is needed as a default
135
         */
136
        $fixtureHelper->setLoadFromCache(false);
137
        /**
138
         * @var AbstractEntityFixtureLoader $fixture
139
         */
140
        $fixture = $fixtureHelper->createFixtureInstanceForEntityFqn(static::$testedEntityFqn);
141
        $fixtureHelper->createDb($fixture);
142
        $loaded               = $this->loadAllEntities();
143
        $expectedAmountLoaded = $fixture::BULK_AMOUNT_TO_GENERATE;
144
        $actualAmountLoaded   = count($loaded);
145
        self::assertGreaterThanOrEqual(
146
            $expectedAmountLoaded,
147
            $actualAmountLoaded,
148
            "expected to load at least $expectedAmountLoaded but only loaded $actualAmountLoaded"
149
        );
150
151
        return $loaded;
152
    }
153
154
    /**
155
     * Test that we have correctly generated an instance of our test entity
156
     *
157
     * @param array $fixtureEntities
158
     *
159
     * @return EntityInterface
160
     * @SuppressWarnings(PHPMD.StaticAccess)
161
     * @depends theFixtureCanBeLoaded
162
     * @test
163
     */
164
    public function weCanGenerateANewEntityInstance(array $fixtureEntities): EntityInterface
165
    {
166
        $generated = current($fixtureEntities);
167
        self::assertInstanceOf(static::$testedEntityFqn, $generated);
168
169
        return $generated;
170
    }
171
172
    /**
173
     * @test
174
     * Use Doctrine's built in schema validation tool to catch issues
175
     */
176
    public function theSchemaIsValidForThisEntity()
177
    {
178
        $errors  = $this->getSchemaErrors();
179
        $message = '';
180
        if (isset($errors[static::$testedEntityFqn])) {
181
            $message = "Failed ORM Validate Schema:\n";
182
            foreach ($errors[static::$testedEntityFqn] as $err) {
183
                $message .= "\n * $err \n";
184
            }
185
        }
186
        self::assertEmpty($message, $message);
187
    }
188
189
    /**
190
     * Loop through Entity fields, call the getter and where possible assert there is a value returned
191
     *
192
     * @param EntityInterface $entity
193
     *
194
     * @return EntityInterface
195
     * @throws \Doctrine\ORM\Query\QueryException
196
     * @throws \ReflectionException
197
     * @SuppressWarnings(PHPMD.StaticAccess)
198
     * @test
199
     * @depends weCanGenerateANewEntityInstance
200
     */
201
    public function theEntityGettersReturnValues(EntityInterface $entity): EntityInterface
202
    {
203
        $meta = $this->getEntityManager()->getClassMetadata(static::$testedEntityFqn);
204
        $dto  = $this->getDtoFactory()->createDtoFromEntity($entity);
205
        foreach ($meta->getFieldNames() as $fieldName) {
206
            if ('id' === $fieldName) {
207
                continue;
208
            }
209
            $type   = PersisterHelper::getTypeOfField($fieldName, $meta, $this->getEntityManager())[0];
210
            $method = $this->getGetterNameForField($fieldName, $type);
211
            if (\ts\stringContains($method, '.')) {
212
                list($getEmbeddableMethod,) = explode('.', $method);
213
                $embeddable = $entity->$getEmbeddableMethod();
214
                self::assertInstanceOf(AbstractEmbeddableObject::class, $embeddable);
215
                continue;
216
            }
217
            $reflectionMethod = new \ReflectionMethod($entity, $method);
218
            if ($reflectionMethod->hasReturnType()) {
219
                $returnType = $reflectionMethod->getReturnType();
220
                $allowsNull = $returnType->allowsNull();
221
                if ($allowsNull) {
222
                    // As we can't assert anything here so simply call
223
                    // the method and allow the type hint to raise any
224
                    // errors.
225
                    $entity->$method();
226
                    continue;
227
                }
228
                self::assertNotNull($dto->$method(), "$fieldName getter returned null");
229
                continue;
230
            }
231
            // If there is no return type then we can't assert anything,
232
            // but again we can just call the getter to check for errors
233
            $dto->$method();
234
        }
235
        if (0 === $this->getCount()) {
236
            self::assertTrue(true);
237
        }
238
239
        return $entity;
240
    }
241
242
    /**
243
     * @param EntityInterface $generated
244
     *
245
     * @return EntityInterface
246
     * @test
247
     * @depends theEntityGettersReturnValues
248
     * @throws \ErrorException
249
     */
250
    public function weCanExtendTheEntityWithUnrequiredAssociationEntities(EntityInterface $generated): EntityInterface
251
    {
252
        if ([] === $this->getTestedEntityClassMetaData()->getAssociationMappings()) {
253
            $this->markTestSkipped('No associations to test');
254
        }
255
        $this->getTestEntityGenerator()->addAssociationEntities($generated);
256
        $this->assertAllAssociationsAreNotEmpty($generated);
257
258
        return $generated;
259
    }
260
261
    /**
262
     * @param EntityInterface $generated
263
     *
264
     * @return EntityInterface
265
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
266
     * @throws \ReflectionException
267
     * @test
268
     * @depends weCanExtendTheEntityWithUnrequiredAssociationEntities
269
     */
270
    public function theEntityCanBeSavedAndReloadedFromTheDatabase(EntityInterface $generated): EntityInterface
271
    {
272
        $this->getEntitySaver()->save($generated);
273
        $loaded = $this->loadEntity($generated->getId());
274
        self::assertSame((string)$generated->getId(), (string)$loaded->getId());
275
        self::assertInstanceOf(static::$testedEntityFqn, $loaded);
276
277
        return $loaded;
278
    }
279
280
    /**
281
     * @param EntityInterface $loaded
282
     *
283
     * @return EntityInterface
284
     * @throws ConfigException
285
     * @throws \Doctrine\ORM\Query\QueryException
286
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
287
     * @throws \ErrorException
288
     * @throws \ReflectionException
289
     * @depends theEntityCanBeSavedAndReloadedFromTheDatabase
290
     * @test
291
     */
292
    public function theLoadedEntityCanBeUpdatedAndResaved(EntityInterface $loaded): EntityInterface
293
    {
294
        $this->updateEntityFields($loaded);
295
        $this->assertAllAssociationsAreNotEmpty($loaded);
296
        $this->removeAllAssociations($loaded);
297
        $this->getEntitySaver()->save($loaded);
298
299
        return $loaded;
300
    }
301
302
    /**
303
     * @param EntityInterface $entity
304
     *
305
     * @return EntityInterface
306
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
307
     * @depends theLoadedEntityCanBeUpdatedAndResaved
308
     * @test
309
     */
310
    public function theReloadedEntityHasNoAssociatedEntities(EntityInterface $entity): EntityInterface
311
    {
312
        $reLoaded     = $this->loadEntity($entity->getId());
313
        $entityDump   = $this->dump($entity);
314
        $reLoadedDump = $this->dump($reLoaded);
315
        self::assertEquals($entityDump, $reLoadedDump);
316
        $this->assertAllAssociationsAreEmpty($reLoaded);
317
318
        return $reLoaded;
319
    }
320
321
    /**
322
     * @depends weCanGenerateANewEntityInstance
323
     *
324
     * @param EntityInterface $entity
325
     *
326
     * @test
327
     */
328
    public function checkAllGettersCanBeReturnedFromDoctrineStaticMeta(EntityInterface $entity)
329
    {
330
        $getters = $entity::getDoctrineStaticMeta()->getGetters();
331
        self::assertNotEmpty($getters);
332
        foreach ($getters as $getter) {
333
            self::assertRegExp('%^(get|is|has).+%', $getter);
334
        }
335
    }
336
337
    /**
338
     * @depends weCanGenerateANewEntityInstance
339
     * @test
340
     *
341
     * @param EntityInterface $entity
342
     */
343
    public function checkAllSettersCanBeReturnedFromDoctrineStaticMeta(EntityInterface $entity)
344
    {
345
        $setters = $entity::getDoctrineStaticMeta()->getSetters();
346
        self::assertNotEmpty($setters);
347
        foreach ($setters as $setter) {
348
            self::assertRegExp('%^(set|add).+%', $setter);
349
        }
350
    }
351
352
    /**
353
     * Loop through entity fields and find unique ones
354
     *
355
     * Then ensure that the unique rule is being enforced as expected
356
     *
357
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
358
     * @throws \ErrorException
359
     * @throws \ReflectionException
360
     * @test
361
     * @depends theReloadedEntityHasNoAssociatedEntities
362
     */
363
    public function checkThatWeCanNotSaveEntitiesWithDuplicateUniqueFieldValues(): void
364
    {
365
        $meta         = $this->getTestedEntityClassMetaData();
366
        $uniqueFields = [];
367
        foreach ($meta->getFieldNames() as $fieldName) {
368
            if (IdFieldInterface::PROP_ID === $fieldName) {
369
                continue;
370
            }
371
            if (true === $this->isUniqueField($fieldName)) {
372
                $uniqueFields[] = $fieldName;
373
            }
374
        }
375
        if ([] === $uniqueFields) {
376
            self::markTestSkipped('No unique fields to check');
377
378
            return;
379
        }
380
        foreach ($uniqueFields as $fieldName) {
381
            $primary      = $this->getTestEntityGenerator()->generateEntity();
382
            $secondary    = $this->getTestEntityGenerator()->generateEntity();
383
            $secondaryDto = $this->getDtoFactory()->createDtoFromEntity($secondary);
384
            $getter       = 'get' . $fieldName;
385
            $setter       = 'set' . $fieldName;
386
            $primaryValue = $primary->$getter();
387
            $secondaryDto->$setter($primaryValue);
388
            $secondary->update($secondaryDto);
389
            $saver = $this->getEntitySaver();
390
            $this->expectException(UniqueConstraintViolationException::class);
391
            $saver->saveAll([$primary, $secondary]);
392
        }
393
    }
394
395
    protected function getFixturesHelper(): FixturesHelper
396
    {
397
        return static::$container->get(FixturesHelperFactory::class)->getFixturesHelper();
398
    }
399
400
    protected function loadAllEntities(): array
401
    {
402
        return static::$container->get(RepositoryFactory::class)
403
                                 ->getRepository(static::$testedEntityFqn)
404
                                 ->findAll();
405
    }
406
407
    /**
408
     * Use Doctrine's standard schema validation to get errors for the whole schema
409
     *
410
     * We cache this as a class property because the schema only needs validating once as a whole, after that we can
411
     * pull out Entity specific issues as required
412
     *
413
     * @param bool $update
414
     *
415
     * @return array
416
     * @throws \Exception
417
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
418
     */
419
    protected function getSchemaErrors(bool $update = false): array
420
    {
421
        if ([] === static::$schemaErrors || true === $update) {
422
            $validator            = new SchemaValidator($this->getEntityManager());
423
            static::$schemaErrors = $validator->validateMapping();
424
        }
425
426
        return static::$schemaErrors;
427
    }
428
429
    protected function getEntityManager(): EntityManagerInterface
430
    {
431
        return static::$container->get(EntityManagerInterface::class);
432
    }
433
434
    protected function getDtoFactory(): DtoFactory
435
    {
436
        return static::$container->get(DtoFactory::class);
437
    }
438
439
    protected function getGetterNameForField(string $fieldName, string $type): string
440
    {
441
        if ($type === 'boolean') {
442
            return static::$container->get(CodeHelper::class)->getGetterMethodNameForBoolean($fieldName);
443
        }
444
445
        return 'get' . $fieldName;
446
    }
447
448
    protected function getTestedEntityClassMetaData(): ClassMetadata
449
    {
450
        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...
451
    }
452
453
    protected function getTestEntityGenerator(): TestEntityGenerator
454
    {
455
        return static::$testEntityGenerator;
456
    }
457
458
    protected function assertAllAssociationsAreNotEmpty(EntityInterface $entity)
459
    {
460
        $meta = $this->getTestedEntityClassMetaData();
461
        foreach ($meta->getAssociationMappings() as $mapping) {
462
            $getter = 'get' . $mapping['fieldName'];
463
            if ($meta->isCollectionValuedAssociation($mapping['fieldName'])) {
464
                $collection = $entity->$getter()->toArray();
465
                $this->assertCorrectMappings(static::$testedEntityFqn, $mapping);
466
                self::assertNotEmpty(
467
                    $collection,
468
                    'Failed to load the collection of the associated entity [' . $mapping['fieldName']
469
                    . '] from the generated ' . static::$testedEntityFqn
470
                    . ', make sure you have reciprocal adding of the association'
471
                );
472
473
                continue;
474
            }
475
            $association = $entity->$getter();
476
            self::assertNotEmpty(
477
                $association,
478
                'Failed to load the associated entity: [' . $mapping['fieldName']
479
                . '] from the generated ' . static::$testedEntityFqn
480
            );
481
            self::assertNotEmpty(
482
                $association->getId(),
483
                'Failed to get the ID of the associated entity: [' . $mapping['fieldName']
484
                . '] from the generated ' . static::$testedEntityFqn
485
            );
486
        }
487
    }
488
489
    /**
490
     * Check the mapping of our class and the associated entity to make sure it's configured properly on both sides.
491
     * Very easy to get wrong. This is in addition to the standard Schema Validation
492
     *
493
     * @param string $classFqn
494
     * @param array  $mapping
495
     */
496
    protected function assertCorrectMappings(string $classFqn, array $mapping)
497
    {
498
        $entityManager                        = $this->getEntityManager();
499
        $pass                                 = false;
500
        $associationFqn                       = $mapping['targetEntity'];
501
        $associationMeta                      = $entityManager->getClassMetadata($associationFqn);
502
        $classTraits                          = $entityManager->getClassMetadata($classFqn)
503
                                                              ->getReflectionClass()
504
                                                              ->getTraits();
505
        $unidirectionalTraitShortNamePrefixes = [
506
            'Has' . $associationFqn::getDoctrineStaticMeta()->getSingular() . RelationsGenerator::PREFIX_UNIDIRECTIONAL,
507
            'Has' . $associationFqn::getDoctrineStaticMeta()->getPlural() . RelationsGenerator::PREFIX_UNIDIRECTIONAL,
508
            'Has' . RelationsGenerator::PREFIX_REQUIRED .
509
            $associationFqn::getDoctrineStaticMeta()->getSingular() . RelationsGenerator::PREFIX_UNIDIRECTIONAL,
510
            'Has' . RelationsGenerator::PREFIX_REQUIRED .
511
            $associationFqn::getDoctrineStaticMeta()->getPlural() . RelationsGenerator::PREFIX_UNIDIRECTIONAL,
512
        ];
513
        foreach ($classTraits as $trait) {
514
            foreach ($unidirectionalTraitShortNamePrefixes as $namePrefix) {
515
                if (0 === \stripos($trait->getShortName(), $namePrefix)) {
516
                    return;
517
                }
518
            }
519
        }
520
        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

520
        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...
521
            if ($classFqn === $associationMapping['targetEntity']) {
522
                $pass = $this->assertCorrectMapping($mapping, $associationMapping, $classFqn);
523
                break;
524
            }
525
        }
526
        self::assertTrue(
527
            $pass,
528
            'Failed finding association mapping to test for ' . "\n" . $mapping['targetEntity']
529
        );
530
    }
531
532
    /**
533
     * @param array  $mapping
534
     * @param array  $associationMapping
535
     * @param string $classFqn
536
     *
537
     * @return bool
538
     */
539
    protected function assertCorrectMapping(array $mapping, array $associationMapping, string $classFqn): bool
540
    {
541
        if (empty($mapping['joinTable'])) {
542
            self::assertArrayNotHasKey(
543
                'joinTable',
544
                $associationMapping,
545
                $classFqn . ' join table is empty,
546
                        but association ' . $mapping['targetEntity'] . ' join table is not empty'
547
            );
548
549
            return true;
550
        }
551
        self::assertNotEmpty(
552
            $associationMapping['joinTable'],
553
            "$classFqn joinTable is set to " . $mapping['joinTable']['name']
554
            . " \n association " . $mapping['targetEntity'] . ' join table is empty'
555
        );
556
        self::assertSame(
557
            $mapping['joinTable']['name'],
558
            $associationMapping['joinTable']['name'],
559
            "join tables not the same: \n * $classFqn = " . $mapping['joinTable']['name']
560
            . " \n * association " . $mapping['targetEntity']
561
            . ' = ' . $associationMapping['joinTable']['name']
562
        );
563
        self::assertArrayHasKey(
564
            'inverseJoinColumns',
565
            $associationMapping['joinTable'],
566
            "join table join columns not the same: \n * $classFqn joinColumn = "
567
            . $mapping['joinTable']['joinColumns'][0]['name']
568
            . " \n * association " . $mapping['targetEntity']
569
            . ' inverseJoinColumn is not set'
570
        );
571
        self::assertSame(
572
            $mapping['joinTable']['joinColumns'][0]['name'],
573
            $associationMapping['joinTable']['inverseJoinColumns'][0]['name'],
574
            "join table join columns not the same: \n * $classFqn joinColumn = "
575
            . $mapping['joinTable']['joinColumns'][0]['name']
576
            . " \n * association " . $mapping['targetEntity']
577
            . ' inverseJoinColumn = ' . $associationMapping['joinTable']['inverseJoinColumns'][0]['name']
578
        );
579
        self::assertSame(
580
            $mapping['joinTable']['inverseJoinColumns'][0]['name'],
581
            $associationMapping['joinTable']['joinColumns'][0]['name'],
582
            "join table join columns  not the same: \n * $classFqn inverseJoinColumn = "
583
            . $mapping['joinTable']['inverseJoinColumns'][0]['name']
584
            . " \n * association " . $mapping['targetEntity'] . ' joinColumn = '
585
            . $associationMapping['joinTable']['joinColumns'][0]['name']
586
        );
587
588
        return true;
589
    }
590
591
    protected function getEntitySaver(): EntitySaverInterface
592
    {
593
        return static::$container->get(EntitySaverFactory::class)->getSaverForEntityFqn(static::$testedEntityFqn);
594
    }
595
596
    /**
597
     * @param mixed $id
598
     *
599
     * @return EntityInterface|null
600
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
601
     */
602
    protected function loadEntity($id): EntityInterface
603
    {
604
        return static::$container->get(RepositoryFactory::class)
605
                                 ->getRepository(static::$testedEntityFqn)
606
                                 ->get($id);
607
    }
608
609
    /**
610
     * Generate a new entity and then update our Entity with the values from the generated one
611
     *
612
     * @param EntityInterface $entity
613
     *
614
     * @SuppressWarnings(PHPMD.StaticAccess)
615
     */
616
    protected function updateEntityFields(EntityInterface $entity): void
617
    {
618
        $dto = $this->getDtoFactory()->createDtoFromEntity($entity);
619
        $this->getTestEntityGenerator()->fakerUpdateDto($dto);
620
        $entity->update($dto);
621
    }
622
623
    /**
624
     * @param EntityInterface $entity
625
     *
626
     * @throws \ReflectionException
627
     * @SuppressWarnings(PHPMD.StaticAccess)
628
     */
629
    protected function removeAllAssociations(EntityInterface $entity)
630
    {
631
        $required    = $entity::getDoctrineStaticMeta()->getRequiredRelationProperties();
632
        $meta        = $this->getTestedEntityClassMetaData();
633
        $identifiers = array_flip($meta->getIdentifier());
634
        foreach ($meta->getAssociationMappings() as $mapping) {
635
            if (isset($identifiers[$mapping['fieldName']])) {
636
                continue;
637
            }
638
            if (isset($required[$mapping['fieldName']])) {
639
                continue;
640
            }
641
            $remover = 'remove' . MappingHelper::singularize($mapping['fieldName']);
642
            if ($meta->isCollectionValuedAssociation($mapping['fieldName'])) {
643
                $getter    = 'get' . $mapping['fieldName'];
644
                $relations = $entity->$getter();
645
                foreach ($relations as $relation) {
646
                    $this->initialiseEntity($relation);
647
                    $entity->$remover($relation);
648
                }
649
                continue;
650
            }
651
            $entity->$remover();
652
        }
653
        $this->assertAllAssociationsAreEmpty($entity);
654
    }
655
656
    protected function initialiseEntity(EntityInterface $entity): void
657
    {
658
        static::$testEntityGeneratorFactory
659
            ->createForEntityFqn($entity::getEntityFqn())
660
            ->getEntityFactory()
661
            ->initialiseEntity($entity);
662
    }
663
664
    protected function assertAllAssociationsAreEmpty(EntityInterface $entity)
665
    {
666
        $required    = $entity::getDoctrineStaticMeta()->getRequiredRelationProperties();
667
        $meta        = $this->getTestedEntityClassMetaData();
668
        $identifiers = array_flip($meta->getIdentifier());
669
        foreach ($meta->getAssociationMappings() as $mapping) {
670
            if (isset($identifiers[$mapping['fieldName']])) {
671
                continue;
672
            }
673
            if (isset($required[$mapping['fieldName']])) {
674
                continue;
675
            }
676
677
            $getter = 'get' . $mapping['fieldName'];
678
            if ($meta->isCollectionValuedAssociation($mapping['fieldName'])) {
679
                $collection = $entity->$getter()->toArray();
680
                self::assertEmpty(
681
                    $collection,
682
                    'Collection of the associated entity [' . $mapping['fieldName']
683
                    . '] is not empty after calling remove'
684
                );
685
                continue;
686
            }
687
            $association = $entity->$getter();
688
            self::assertEmpty(
689
                $association,
690
                'Failed to remove associated entity: [' . $mapping['fieldName']
691
                . '] from the generated ' . static::$testedEntityFqn
692
            );
693
        }
694
    }
695
696
    protected function dump(EntityInterface $entity): string
697
    {
698
        return (new EntityDebugDumper())->dump($entity, $this->getEntityManager());
699
    }
700
701
    protected function isUniqueField(string $fieldName): bool
702
    {
703
        $fieldMapping = $this->getTestedEntityClassMetaData()->getFieldMapping($fieldName);
704
705
        return array_key_exists('unique', $fieldMapping) && true === $fieldMapping['unique'];
706
    }
707
}
708