Completed
Push — master ( 4339a8...5a694b )
by André
70:16 queued 52:05
created

BaseTest   C

Complexity

Total Complexity 58

Size/Duplication

Total Lines 556
Duplicated Lines 2.52 %

Coupling/Cohesion

Components 1
Dependencies 13

Importance

Changes 0
Metric Value
dl 14
loc 556
rs 6.3005
c 0
b 0
f 0
wmc 58
lcom 1
cbo 13

23 Methods

Rating   Name   Duplication   Size   Complexity  
B setUp() 0 29 4
A tearDown() 0 5 1
A getIdManager() 0 4 1
A generateId() 0 4 1
A parseId() 0 4 1
A getConfigValue() 0 4 1
A isVersion4() 0 4 2
A getRepository() 0 8 2
B getSetupFactory() 0 24 4
B assertPropertiesCorrect() 0 18 6
A assertPropertiesCorrectUnsorted() 7 10 3
A assertStructPropertiesCorrect() 7 14 4
A sortItems() 0 11 3
C assertPropertiesEqual() 0 24 8
B createUserVersion1() 0 32 1
A createMediaUserVersion1() 0 8 1
A createCustomUserVersion1() 0 10 1
A createCustomUserWithLogin() 0 50 1
B createUser() 0 27 2
A createDateTime() 0 9 2
B refreshSearch() 0 28 4
B createRoleWithPolicies() 0 27 4
A assertValidationErrorOccurs() 0 8 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like BaseTest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use BaseTest, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * File containing the BaseTest class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 */
9
namespace eZ\Publish\API\Repository\Tests;
10
11
use eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException;
12
use eZ\Publish\API\Repository\Tests\PHPUnitConstraint\ValidationErrorOccurs as PHPUnitConstraintValidationErrorOccurs;
13
use EzSystems\EzPlatformSolrSearchEngine\Tests\SetupFactory\LegacySetupFactory as LegacySolrSetupFactory;
14
use PHPUnit\Framework\TestCase;
15
use eZ\Publish\API\Repository\Repository;
16
use eZ\Publish\API\Repository\Values\ValueObject;
17
use eZ\Publish\API\Repository\Values\User\Limitation\RoleLimitation;
18
use eZ\Publish\API\Repository\Values\User\Limitation\SubtreeLimitation;
19
use eZ\Publish\API\Repository\Values\User\UserGroup;
20
use eZ\Publish\Core\REST\Client\Sessionable;
21
use DateTime;
22
use ArrayObject;
23
use Exception;
24
use PDOException;
25
26
/**
27
 * Base class for api specific tests.
28
 */
29
abstract class BaseTest extends TestCase
30
{
31
    /**
32
     * Maximum integer number accepted by the different backends.
33
     */
34
    const DB_INT_MAX = 2147483647;
35
36
    /**
37
     * @var \eZ\Publish\API\Repository\Tests\SetupFactory
38
     */
39
    private $setupFactory;
40
41
    /**
42
     * @var \eZ\Publish\API\Repository\Repository
43
     */
44
    private $repository;
45
46
    protected function setUp()
47
    {
48
        parent::setUp();
49
50
        try {
51
            // Use setup factory instance here w/o clearing data in case test don't need to
52
            $repository = $this->getSetupFactory()->getRepository(false);
53
54
            // Set session if we are testing the REST backend to make it
55
            // possible to persist data in the memory backend during multiple
56
            // requests.
57
            if ($repository instanceof Sessionable) {
58
                $repository->setSession($id = md5(microtime()));
59
            }
60
        } catch (PDOException $e) {
61
            $this->fail(
62
                'The communication with the database cannot be established. ' .
63
                "This is required in order to perform the tests.\n\n" .
64
                'Exception: ' . $e
65
            );
66
        } catch (Exception $e) {
67
            $this->fail(
68
                'Cannot create a repository with predefined user. ' .
69
                'Check the UserService or RoleService implementation. ' .
70
                PHP_EOL . PHP_EOL .
71
                'Exception: ' . $e
72
            );
73
        }
74
    }
75
76
    /**
77
     * Resets the temporary used repository between each test run.
78
     */
79
    protected function tearDown()
80
    {
81
        $this->repository = null;
82
        parent::tearDown();
83
    }
84
85
    /**
86
     * Returns the ID generator, fitting to the repository implementation.
87
     *
88
     * @return \eZ\Publish\API\Repository\Tests\IdManager
89
     */
90
    protected function getIdManager()
91
    {
92
        return $this->getSetupFactory()->getIdManager();
93
    }
94
95
    /**
96
     * Generates a repository specific ID value.
97
     *
98
     * @param string $type
99
     * @param mixed $rawId
100
     *
101
     * @return mixed
102
     */
103
    protected function generateId($type, $rawId)
104
    {
105
        return $this->getIdManager()->generateId($type, $rawId);
106
    }
107
108
    /**
109
     * Parses a repository specific ID value.
110
     *
111
     * @param string $type
112
     * @param mixed $id
113
     *
114
     * @return mixed
115
     */
116
    protected function parseId($type, $id)
117
    {
118
        return $this->getIdManager()->parseId($type, $id);
119
    }
120
121
    /**
122
     * Returns a config setting provided by the setup factory.
123
     *
124
     * @param string $configKey
125
     *
126
     * @return mixed
127
     */
128
    protected function getConfigValue($configKey)
129
    {
130
        return $this->getSetupFactory()->getConfigValue($configKey);
131
    }
132
133
    /**
134
     * Tests if the currently tested api is based on a V4 implementation.
135
     *
136
     * @return bool
137
     */
138
    protected function isVersion4()
139
    {
140
        return isset($_ENV['backendVersion']) && '4' === $_ENV['backendVersion'];
141
    }
142
143
    /**
144
     * @param bool $initialInitializeFromScratch Only has an effect if set in first call within a test
145
     *
146
     * @return \eZ\Publish\API\Repository\Repository
147
     */
148
    protected function getRepository($initialInitializeFromScratch = true)
149
    {
150
        if (null === $this->repository) {
151
            $this->repository = $this->getSetupFactory()->getRepository($initialInitializeFromScratch);
152
        }
153
154
        return $this->repository;
155
    }
156
157
    /**
158
     * @return \eZ\Publish\API\Repository\Tests\SetupFactory
159
     */
160
    protected function getSetupFactory()
161
    {
162
        if (null === $this->setupFactory) {
163
            if (false === isset($_ENV['setupFactory'])) {
164
                throw new \ErrorException(
165
                    'Missing mandatory setting $_ENV["setupFactory"], this should normally be set in the relevant phpunit-integration-*.xml file and refer to a setupFactory for the given StorageEngine/SearchEngine in use'
166
                );
167
            }
168
169
            $setupClass = $_ENV['setupFactory'];
170
            if (false === class_exists($setupClass)) {
171
                throw new \ErrorException(
172
                    sprintf(
173
                        '$_ENV["setupFactory"] does not reference an existing class: %s. Did you forget to install an package dependency?',
174
                        $setupClass
175
                    )
176
                );
177
            }
178
179
            $this->setupFactory = new $setupClass();
180
        }
181
182
        return $this->setupFactory;
183
    }
184
185
    /**
186
     * Asserts that properties given in $expectedValues are correctly set in
187
     * $actualObject.
188
     *
189
     * @param mixed[] $expectedValues
190
     * @param \eZ\Publish\API\Repository\Values\ValueObject $actualObject
191
     */
192
    protected function assertPropertiesCorrect(array $expectedValues, ValueObject $actualObject)
193
    {
194
        foreach ($expectedValues as $propertyName => $propertyValue) {
195
            if ($propertyValue instanceof ValueObject) {
196
                $this->assertStructPropertiesCorrect($propertyValue, $actualObject->$propertyName);
197
            } elseif (is_array($propertyValue)) {
198
                foreach ($propertyValue as $key => $value) {
199
                    if ($value instanceof ValueObject) {
200
                        $this->assertStructPropertiesCorrect($value, $actualObject->$propertyName[$key]);
201
                    } else {
202
                        $this->assertPropertiesEqual("$propertyName\[$key\]", $value, $actualObject->$propertyName[$key]);
203
                    }
204
                }
205
            } else {
206
                $this->assertPropertiesEqual($propertyName, $propertyValue, $actualObject->$propertyName);
207
            }
208
        }
209
    }
210
211
    /**
212
     * Asserts that properties given in $expectedValues are correctly set in
213
     * $actualObject.
214
     *
215
     * If the property type is array, it will be sorted before comparison.
216
     *
217
     * @TODO: introduced because of randomly failing tests, ref: https://jira.ez.no/browse/EZP-21734
218
     *
219
     * @param mixed[] $expectedValues
220
     * @param \eZ\Publish\API\Repository\Values\ValueObject $actualObject
221
     */
222
    protected function assertPropertiesCorrectUnsorted(array $expectedValues, ValueObject $actualObject)
223
    {
224 View Code Duplication
        foreach ($expectedValues as $propertyName => $propertyValue) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
225
            if ($propertyValue instanceof ValueObject) {
226
                $this->assertStructPropertiesCorrect($propertyValue, $actualObject->$propertyName);
227
            } else {
228
                $this->assertPropertiesEqual($propertyName, $propertyValue, $actualObject->$propertyName, true);
229
            }
230
        }
231
    }
232
233
    /**
234
     * Asserts all properties from $expectedValues are correctly set in
235
     * $actualObject. Additional (virtual) properties can be asserted using
236
     * $additionalProperties.
237
     *
238
     * @param \eZ\Publish\API\Repository\Values\ValueObject $expectedValues
239
     * @param \eZ\Publish\API\Repository\Values\ValueObject $actualObject
240
     * @param array $propertyNames
0 ignored issues
show
Bug introduced by
There is no parameter named $propertyNames. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
241
     */
242
    protected function assertStructPropertiesCorrect(ValueObject $expectedValues, ValueObject $actualObject, array $additionalProperties = array())
243
    {
244 View Code Duplication
        foreach ($expectedValues as $propertyName => $propertyValue) {
0 ignored issues
show
Bug introduced by
The expression $expectedValues of type object<eZ\Publish\API\Re...ory\Values\ValueObject> is not traversable.
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
245
            if ($propertyValue instanceof ValueObject) {
246
                $this->assertStructPropertiesCorrect($propertyValue, $actualObject->$propertyName);
247
            } else {
248
                $this->assertPropertiesEqual($propertyName, $propertyValue, $actualObject->$propertyName);
249
            }
250
        }
251
252
        foreach ($additionalProperties as $propertyName) {
253
            $this->assertPropertiesEqual($propertyName, $expectedValues->$propertyName, $actualObject->$propertyName);
254
        }
255
    }
256
257
    /**
258
     * @see \eZ\Publish\API\Repository\Tests\BaseTest::assertPropertiesCorrectUnsorted()
259
     *
260
     * @param array $items An array of scalar values
261
     */
262
    private function sortItems(array &$items)
263
    {
264
        $sorter = function ($a, $b) {
265
            if (!is_scalar($a) || !is_scalar($b)) {
266
                $this->fail('Wrong usage: method ' . __METHOD__ . ' accepts only an array of scalar values');
267
            }
268
269
            return strcmp($a, $b);
270
        };
271
        usort($items, $sorter);
272
    }
273
274
    private function assertPropertiesEqual($propertyName, $expectedValue, $actualValue, $sortArray = false)
275
    {
276
        if ($expectedValue instanceof ArrayObject) {
277
            $expectedValue = $expectedValue->getArrayCopy();
278
        } elseif ($expectedValue instanceof DateTime) {
279
            $expectedValue = $expectedValue->format(DateTime::RFC850);
280
        }
281
        if ($actualValue instanceof ArrayObject) {
282
            $actualValue = $actualValue->getArrayCopy();
283
        } elseif ($actualValue instanceof DateTime) {
284
            $actualValue = $actualValue->format(DateTime::RFC850);
285
        }
286
287
        if ($sortArray && is_array($actualValue) && is_array($expectedValue)) {
288
            $this->sortItems($actualValue);
289
            $this->sortItems($expectedValue);
290
        }
291
292
        $this->assertEquals(
293
            $expectedValue,
294
            $actualValue,
295
            sprintf('Object property "%s" incorrect.', $propertyName)
296
        );
297
    }
298
299
    /**
300
     * Create a user in editor user group.
301
     *
302
     * @param string $login
303
     *
304
     * @return \eZ\Publish\API\Repository\Values\User\User
305
     */
306
    protected function createUserVersion1($login = 'user')
307
    {
308
        $repository = $this->getRepository();
309
310
        /* BEGIN: Inline */
311
        // ID of the "Editors" user group in an eZ Publish demo installation
312
        $editorsGroupId = 13;
313
314
        $userService = $repository->getUserService();
315
316
        // Instantiate a create struct with mandatory properties
317
        $userCreate = $userService->newUserCreateStruct(
318
            $login,
319
            "{$login}@example.com",
320
            'secret',
321
            'eng-US'
322
        );
323
        $userCreate->enabled = true;
324
325
        // Set some fields required by the user ContentType
326
        $userCreate->setField('first_name', 'Example');
327
        $userCreate->setField('last_name', 'User');
328
329
        // Load parent group for the user
330
        $group = $userService->loadUserGroup($editorsGroupId);
331
332
        // Create a new user instance.
333
        $user = $userService->createUser($userCreate, array($group));
334
        /* END: Inline */
335
336
        return $user;
337
    }
338
339
    /**
340
     * Create a user in new user group with editor rights limited to Media Library (/1/48/).
341
     *
342
     * @uses ::createCustomUserVersion1()
343
     *
344
     * @return \eZ\Publish\API\Repository\Values\User\User
345
     */
346
    protected function createMediaUserVersion1()
347
    {
348
        return $this->createCustomUserVersion1(
349
            'Media Editor',
350
            'Editor',
351
            new SubtreeLimitation(array('limitationValues' => array('/1/43/')))
352
        );
353
    }
354
355
    /**
356
     * Create a user with new user group and assign a existing role (optionally with RoleLimitation).
357
     *
358
     * @param string $userGroupName Name of the new user group to create
359
     * @param string $roleIdentifier Role identifier to assign to the new group
360
     * @param RoleLimitation|null $roleLimitation
361
     *
362
     * @return \eZ\Publish\API\Repository\Values\User\User
363
     */
364
    protected function createCustomUserVersion1($userGroupName, $roleIdentifier, RoleLimitation $roleLimitation = null)
365
    {
366
        return $this->createCustomUserWithLogin(
367
            'user',
368
            '[email protected]',
369
            $userGroupName,
370
            $roleIdentifier,
371
            $roleLimitation
372
        );
373
    }
374
375
    /**
376
     * Create a user with new user group and assign a existing role (optionally with RoleLimitation).
377
     *
378
     * @param string $login User login
379
     * @param string $email User e-mail
380
     * @param string $userGroupName Name of the new user group to create
381
     * @param string $roleIdentifier Role identifier to assign to the new group
382
     * @param RoleLimitation|null $roleLimitation
383
     * @return \eZ\Publish\API\Repository\Values\User\User
384
     */
385
    protected function createCustomUserWithLogin(
386
        $login,
387
        $email,
388
        $userGroupName,
389
        $roleIdentifier,
390
        RoleLimitation $roleLimitation = null
391
    ) {
392
        $repository = $this->getRepository();
393
394
        /* BEGIN: Inline */
395
        // ID of the "Users" user group in an eZ Publish demo installation
396
        $rootUsersGroupId = $this->generateId('location', 4);
397
398
        $roleService = $repository->getRoleService();
399
        $userService = $repository->getUserService();
400
401
        // Get a group create struct
402
        $userGroupCreate = $userService->newUserGroupCreateStruct('eng-US');
403
        $userGroupCreate->setField('name', $userGroupName);
404
405
        // Create new group with media editor rights
406
        $userGroup = $userService->createUserGroup(
407
            $userGroupCreate,
408
            $userService->loadUserGroup($rootUsersGroupId)
409
        );
410
        $roleService->assignRoleToUserGroup(
411
            $roleService->loadRoleByIdentifier($roleIdentifier),
412
            $userGroup,
413
            $roleLimitation
414
        );
415
416
        // Instantiate a create struct with mandatory properties
417
        $userCreate = $userService->newUserCreateStruct(
418
            $login,
419
            $email,
420
            'secret',
421
            'eng-US'
422
        );
423
        $userCreate->enabled = true;
424
425
        // Set some fields required by the user ContentType
426
        $userCreate->setField('first_name', 'Example');
427
        $userCreate->setField('last_name', ucfirst($login));
428
429
        // Create a new user instance.
430
        $user = $userService->createUser($userCreate, array($userGroup));
431
        /* END: Inline */
432
433
        return $user;
434
    }
435
436
    /**
437
     * Create a user using given data.
438
     *
439
     * @param string $login
440
     * @param string $firstName
441
     * @param string $lastName
442
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup|null $userGroup optional user group, Editor by default
443
     *
444
     * @return \eZ\Publish\API\Repository\Values\User\User
445
     */
446
    protected function createUser($login, $firstName, $lastName, UserGroup $userGroup = null)
447
    {
448
        $repository = $this->getRepository();
449
450
        $userService = $repository->getUserService();
451
        if (null === $userGroup) {
452
            $userGroup = $userService->loadUserGroup(13);
453
        }
454
455
        // Instantiate a create struct with mandatory properties
456
        $userCreate = $userService->newUserCreateStruct(
457
            $login,
458
            "{$login}@example.com",
459
            'secret',
460
            'eng-US'
461
        );
462
        $userCreate->enabled = true;
463
464
        // Set some fields required by the user ContentType
465
        $userCreate->setField('first_name', $firstName);
466
        $userCreate->setField('last_name', $lastName);
467
468
        // Create a new user instance.
469
        $user = $userService->createUser($userCreate, array($userGroup));
470
471
        return $user;
472
    }
473
474
    /**
475
     * Only for internal use.
476
     *
477
     * Creates a \DateTime object for $timestamp in the current time zone
478
     *
479
     * @param int $timestamp
480
     *
481
     * @return \DateTime
482
     */
483
    public function createDateTime($timestamp = null)
484
    {
485
        $dateTime = new \DateTime();
486
        if ($timestamp !== null) {
487
            $dateTime->setTimestamp($timestamp);
488
        }
489
490
        return $dateTime;
491
    }
492
493
    /**
494
     * Calls given Repository's aggregated SearchHandler::refresh().
495
     *
496
     * Currently implemented only in Solr search engine.
497
     *
498
     * @param \eZ\Publish\API\Repository\Repository $repository
499
     */
500
    protected function refreshSearch(Repository $repository)
501
    {
502
        $setupFactory = $this->getSetupFactory();
503
504
        if (!$setupFactory instanceof LegacySolrSetupFactory) {
0 ignored issues
show
Bug introduced by
The class EzSystems\EzPlatformSolr...tory\LegacySetupFactory does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
505
            return;
506
        }
507
508
        while (true) {
509
            $repositoryReflection = new \ReflectionObject($repository);
510
            // If the repository is decorated, we need to recurse in the "repository" property
511
            if (!$repositoryReflection->hasProperty('repository')) {
512
                break;
513
            }
514
515
            $repositoryProperty = $repositoryReflection->getProperty('repository');
516
            $repositoryProperty->setAccessible(true);
517
            $repository = $repositoryProperty->getValue($repository);
518
        }
519
520
        $searchHandlerProperty = new \ReflectionProperty($repository, 'searchHandler');
521
        $searchHandlerProperty->setAccessible(true);
522
523
        /** @var \EzSystems\EzPlatformSolrSearchEngine\Handler $searchHandler */
524
        $searchHandler = $searchHandlerProperty->getValue($repository);
525
526
        $searchHandler->commit();
527
    }
528
529
    /**
530
     * Create role of a given name with the given policies described by an array.
531
     *
532
     * @param $roleName
533
     * @param array $policiesData [['module' => 'content', 'function' => 'read', 'limitations' => []]
534
     *
535
     * @return \eZ\Publish\API\Repository\Values\User\Role
536
     *
537
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
538
     * @throws \eZ\Publish\API\Repository\Exceptions\LimitationValidationException
539
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
540
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
541
     */
542
    public function createRoleWithPolicies($roleName, array $policiesData)
543
    {
544
        $repository = $this->getRepository(false);
545
        $roleService = $repository->getRoleService();
546
547
        $roleCreateStruct = $roleService->newRoleCreateStruct($roleName);
548
        foreach ($policiesData as $policyData) {
549
            $policyCreateStruct = $roleService->newPolicyCreateStruct(
550
                $policyData['module'],
551
                $policyData['function']
552
            );
553
554
            if (isset($policyData['limitations'])) {
555
                foreach ($policyData['limitations'] as $limitation) {
556
                    $policyCreateStruct->addLimitation($limitation);
557
                }
558
            }
559
560
            $roleCreateStruct->addPolicy($policyCreateStruct);
561
        }
562
563
        $roleDraft = $roleService->createRole($roleCreateStruct);
564
565
        $roleService->publishRoleDraft($roleDraft);
566
567
        return $roleService->loadRole($roleDraft->id);
568
    }
569
570
    /**
571
     * Traverse all errors for all fields in all Translations to find expected one.
572
     *
573
     * @param \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException $exception
574
     * @param string $expectedValidationErrorMessage
575
     */
576
    protected function assertValidationErrorOccurs(
577
        ContentFieldValidationException $exception,
578
        $expectedValidationErrorMessage
579
    ) {
580
        $constraint = new PHPUnitConstraintValidationErrorOccurs($expectedValidationErrorMessage);
581
582
        self::assertThat($exception, $constraint);
583
    }
584
}
585