Completed
Push — master ( d81c59...0775da )
by André
14:46
created

BaseTest   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 542
Duplicated Lines 2.58 %

Coupling/Cohesion

Components 1
Dependencies 12

Importance

Changes 0
Metric Value
dl 14
loc 542
rs 6.433
c 0
b 0
f 0
wmc 57
lcom 1
cbo 12

22 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 15 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

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 EzSystems\EzPlatformSolrSearchEngine\Tests\SetupFactory\LegacySetupFactory as LegacySolrSetupFactory;
12
use PHPUnit\Framework\TestCase;
13
use eZ\Publish\API\Repository\Repository;
14
use eZ\Publish\API\Repository\Values\ValueObject;
15
use eZ\Publish\API\Repository\Values\User\Limitation\RoleLimitation;
16
use eZ\Publish\API\Repository\Values\User\Limitation\SubtreeLimitation;
17
use eZ\Publish\API\Repository\Values\User\UserGroup;
18
use eZ\Publish\Core\REST\Client\Sessionable;
19
use DateTime;
20
use ArrayObject;
21
use Exception;
22
use PDOException;
23
24
/**
25
 * Base class for api specific tests.
26
 */
27
abstract class BaseTest extends TestCase
28
{
29
    /**
30
     * Maximum integer number accepted by the different backends.
31
     */
32
    const DB_INT_MAX = 2147483647;
33
34
    /**
35
     * @var \eZ\Publish\API\Repository\Tests\SetupFactory
36
     */
37
    private $setupFactory;
38
39
    /**
40
     * @var \eZ\Publish\API\Repository\Repository
41
     */
42
    private $repository;
43
44
    protected function setUp()
45
    {
46
        parent::setUp();
47
48
        try {
49
            // Use setup factory instance here w/o clearing data in case test don't need to
50
            $repository = $this->getSetupFactory()->getRepository(false);
51
52
            // Set session if we are testing the REST backend to make it
53
            // possible to persist data in the memory backend during multiple
54
            // requests.
55
            if ($repository instanceof Sessionable) {
56
                $repository->setSession($id = md5(microtime()));
57
            }
58
        } catch (PDOException $e) {
59
            $this->fail(
60
                'The communication with the database cannot be established. ' .
61
                "This is required in order to perform the tests.\n\n" .
62
                'Exception: ' . $e
63
            );
64
        } catch (Exception $e) {
65
            $this->fail(
66
                'Cannot create a repository with predefined user. ' .
67
                'Check the UserService or RoleService implementation. ' .
68
                PHP_EOL . PHP_EOL .
69
                'Exception: ' . $e
70
            );
71
        }
72
    }
73
74
    /**
75
     * Resets the temporary used repository between each test run.
76
     */
77
    protected function tearDown()
78
    {
79
        $this->repository = null;
80
        parent::tearDown();
81
    }
82
83
    /**
84
     * Returns the ID generator, fitting to the repository implementation.
85
     *
86
     * @return \eZ\Publish\API\Repository\Tests\IdManager
87
     */
88
    protected function getIdManager()
89
    {
90
        return $this->getSetupFactory()->getIdManager();
91
    }
92
93
    /**
94
     * Generates a repository specific ID value.
95
     *
96
     * @param string $type
97
     * @param mixed $rawId
98
     *
99
     * @return mixed
100
     */
101
    protected function generateId($type, $rawId)
102
    {
103
        return $this->getIdManager()->generateId($type, $rawId);
104
    }
105
106
    /**
107
     * Parses a repository specific ID value.
108
     *
109
     * @param string $type
110
     * @param mixed $id
111
     *
112
     * @return mixed
113
     */
114
    protected function parseId($type, $id)
115
    {
116
        return $this->getIdManager()->parseId($type, $id);
117
    }
118
119
    /**
120
     * Returns a config setting provided by the setup factory.
121
     *
122
     * @param string $configKey
123
     *
124
     * @return mixed
125
     */
126
    protected function getConfigValue($configKey)
127
    {
128
        return $this->getSetupFactory()->getConfigValue($configKey);
129
    }
130
131
    /**
132
     * Tests if the currently tested api is based on a V4 implementation.
133
     *
134
     * @return bool
135
     */
136
    protected function isVersion4()
137
    {
138
        return isset($_ENV['backendVersion']) && '4' === $_ENV['backendVersion'];
139
    }
140
141
    /**
142
     * @param bool $initialInitializeFromScratch Only has an effect if set in first call within a test
143
     *
144
     * @return \eZ\Publish\API\Repository\Repository
145
     */
146
    protected function getRepository($initialInitializeFromScratch = true)
147
    {
148
        if (null === $this->repository) {
149
            $this->repository = $this->getSetupFactory()->getRepository($initialInitializeFromScratch);
150
        }
151
152
        return $this->repository;
153
    }
154
155
    /**
156
     * @return \eZ\Publish\API\Repository\Tests\SetupFactory
157
     */
158
    protected function getSetupFactory()
159
    {
160
        if (null === $this->setupFactory) {
161
            if (false === isset($_ENV['setupFactory'])) {
162
                throw new \ErrorException(
163
                    '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'
164
                );
165
            }
166
167
            $setupClass = $_ENV['setupFactory'];
168
            if (false === class_exists($setupClass)) {
169
                throw new \ErrorException(
170
                    sprintf(
171
                        '$_ENV["setupFactory"] does not reference an existing class: %s. Did you forget to install an package dependency?',
172
                        $setupClass
173
                    )
174
                );
175
            }
176
177
            $this->setupFactory = new $setupClass();
178
        }
179
180
        return $this->setupFactory;
181
    }
182
183
    /**
184
     * Asserts that properties given in $expectedValues are correctly set in
185
     * $actualObject.
186
     *
187
     * @param mixed[] $expectedValues
188
     * @param \eZ\Publish\API\Repository\Values\ValueObject $actualObject
189
     */
190
    protected function assertPropertiesCorrect(array $expectedValues, ValueObject $actualObject)
191
    {
192
        foreach ($expectedValues as $propertyName => $propertyValue) {
193
            if ($propertyValue instanceof ValueObject) {
194
                $this->assertStructPropertiesCorrect($propertyValue, $actualObject->$propertyName);
195
            } elseif (is_array($propertyValue)) {
196
                foreach ($propertyValue as $key => $value) {
197
                    if ($value instanceof ValueObject) {
198
                        $this->assertStructPropertiesCorrect($value, $actualObject->$propertyName[$key]);
199
                    } else {
200
                        $this->assertPropertiesEqual("$propertyName\[$key\]", $value, $actualObject->$propertyName[$key]);
201
                    }
202
                }
203
            } else {
204
                $this->assertPropertiesEqual($propertyName, $propertyValue, $actualObject->$propertyName);
205
            }
206
        }
207
    }
208
209
    /**
210
     * Asserts that properties given in $expectedValues are correctly set in
211
     * $actualObject.
212
     *
213
     * If the property type is array, it will be sorted before comparison.
214
     *
215
     * @TODO: introduced because of randomly failing tests, ref: https://jira.ez.no/browse/EZP-21734
216
     *
217
     * @param mixed[] $expectedValues
218
     * @param \eZ\Publish\API\Repository\Values\ValueObject $actualObject
219
     */
220
    protected function assertPropertiesCorrectUnsorted(array $expectedValues, ValueObject $actualObject)
221
    {
222 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...
223
            if ($propertyValue instanceof ValueObject) {
224
                $this->assertStructPropertiesCorrect($propertyValue, $actualObject->$propertyName);
225
            } else {
226
                $this->assertPropertiesEqual($propertyName, $propertyValue, $actualObject->$propertyName, true);
227
            }
228
        }
229
    }
230
231
    /**
232
     * Asserts all properties from $expectedValues are correctly set in
233
     * $actualObject. Additional (virtual) properties can be asserted using
234
     * $additionalProperties.
235
     *
236
     * @param \eZ\Publish\API\Repository\Values\ValueObject $expectedValues
237
     * @param \eZ\Publish\API\Repository\Values\ValueObject $actualObject
238
     * @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...
239
     */
240
    protected function assertStructPropertiesCorrect(ValueObject $expectedValues, ValueObject $actualObject, array $additionalProperties = array())
241
    {
242 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...
243
            if ($propertyValue instanceof ValueObject) {
244
                $this->assertStructPropertiesCorrect($propertyValue, $actualObject->$propertyName);
245
            } else {
246
                $this->assertPropertiesEqual($propertyName, $propertyValue, $actualObject->$propertyName);
247
            }
248
249
        }
250
251
        foreach ($additionalProperties as $propertyName) {
252
            $this->assertPropertiesEqual($propertyName, $expectedValues->$propertyName, $actualObject->$propertyName);
253
        }
254
    }
255
256
    /**
257
     * @see \eZ\Publish\API\Repository\Tests\BaseTest::assertPropertiesCorrectUnsorted()
258
     *
259
     * @param array $items An array of scalar values
260
     */
261
    private function sortItems(array &$items)
262
    {
263
        $sorter = function ($a, $b) {
264
            if (!is_scalar($a) || !is_scalar($b)) {
265
                $this->fail('Wrong usage: method ' . __METHOD__ . ' accepts only an array of scalar values');
266
            }
267
268
            return strcmp($a, $b);
269
        };
270
        usort($items, $sorter);
271
    }
272
273
    private function assertPropertiesEqual($propertyName, $expectedValue, $actualValue, $sortArray = false)
274
    {
275
        if ($expectedValue instanceof ArrayObject) {
276
            $expectedValue = $expectedValue->getArrayCopy();
277
        } elseif ($expectedValue instanceof DateTime) {
278
            $expectedValue = $expectedValue->format(DateTime::RFC850);
279
        }
280
        if ($actualValue instanceof ArrayObject) {
281
            $actualValue = $actualValue->getArrayCopy();
282
        } elseif ($actualValue instanceof DateTime) {
283
            $actualValue = $actualValue->format(DateTime::RFC850);
284
        }
285
286
        if ($sortArray && is_array($actualValue) && is_array($expectedValue)) {
287
            $this->sortItems($actualValue);
288
            $this->sortItems($expectedValue);
289
        }
290
291
        $this->assertEquals(
292
            $expectedValue,
293
            $actualValue,
294
            sprintf('Object property "%s" incorrect.', $propertyName)
295
        );
296
    }
297
298
    /**
299
     * Create a user in editor user group.
300
     *
301
     * @param string $login
302
     *
303
     * @return \eZ\Publish\API\Repository\Values\User\User
304
     */
305
    protected function createUserVersion1($login = 'user')
306
    {
307
        $repository = $this->getRepository();
308
309
        /* BEGIN: Inline */
310
        // ID of the "Editors" user group in an eZ Publish demo installation
311
        $editorsGroupId = 13;
312
313
        $userService = $repository->getUserService();
314
315
        // Instantiate a create struct with mandatory properties
316
        $userCreate = $userService->newUserCreateStruct(
317
            $login,
318
            "{$login}@example.com",
319
            'secret',
320
            'eng-US'
321
        );
322
        $userCreate->enabled = true;
323
324
        // Set some fields required by the user ContentType
325
        $userCreate->setField('first_name', 'Example');
326
        $userCreate->setField('last_name', 'User');
327
328
        // Load parent group for the user
329
        $group = $userService->loadUserGroup($editorsGroupId);
330
331
        // Create a new user instance.
332
        $user = $userService->createUser($userCreate, array($group));
333
        /* END: Inline */
334
335
        return $user;
336
    }
337
338
    /**
339
     * Create a user in new user group with editor rights limited to Media Library (/1/48/).
340
     *
341
     * @uses ::createCustomUserVersion1()
342
     *
343
     * @return \eZ\Publish\API\Repository\Values\User\User
344
     */
345
    protected function createMediaUserVersion1()
346
    {
347
        return $this->createCustomUserVersion1(
348
            'Media Editor',
349
            'Editor',
350
            new SubtreeLimitation(array('limitationValues' => array('/1/43/')))
351
        );
352
    }
353
354
    /**
355
     * Create a user with new user group and assign a existing role (optionally with RoleLimitation).
356
     *
357
     * @param string $userGroupName Name of the new user group to create
358
     * @param string $roleIdentifier Role identifier to assign to the new group
359
     * @param RoleLimitation|null $roleLimitation
360
     *
361
     * @return \eZ\Publish\API\Repository\Values\User\User
362
     */
363
    protected function createCustomUserVersion1($userGroupName, $roleIdentifier, RoleLimitation $roleLimitation = null)
364
    {
365
        return $this->createCustomUserWithLogin(
366
            'user',
367
            '[email protected]',
368
            $userGroupName,
369
            $roleIdentifier,
370
            $roleLimitation
371
        );
372
    }
373
374
    /**
375
     * Create a user with new user group and assign a existing role (optionally with RoleLimitation).
376
     *
377
     * @param string $login User login
378
     * @param string $email User e-mail
379
     * @param string $userGroupName Name of the new user group to create
380
     * @param string $roleIdentifier Role identifier to assign to the new group
381
     * @param RoleLimitation|null $roleLimitation
382
     * @return \eZ\Publish\API\Repository\Values\User\User
383
     */
384
    protected function createCustomUserWithLogin(
385
        $login,
386
        $email,
387
        $userGroupName,
388
        $roleIdentifier,
389
        RoleLimitation $roleLimitation = null
390
    ) {
391
        $repository = $this->getRepository();
392
393
        /* BEGIN: Inline */
394
        // ID of the "Users" user group in an eZ Publish demo installation
395
        $rootUsersGroupId = $this->generateId('location', 4);
396
397
        $roleService = $repository->getRoleService();
398
        $userService = $repository->getUserService();
399
400
        // Get a group create struct
401
        $userGroupCreate = $userService->newUserGroupCreateStruct('eng-US');
402
        $userGroupCreate->setField('name', $userGroupName);
403
404
        // Create new group with media editor rights
405
        $userGroup = $userService->createUserGroup(
406
            $userGroupCreate,
407
            $userService->loadUserGroup($rootUsersGroupId)
408
        );
409
        $roleService->assignRoleToUserGroup(
410
            $roleService->loadRoleByIdentifier($roleIdentifier),
411
            $userGroup,
412
            $roleLimitation
413
        );
414
415
        // Instantiate a create struct with mandatory properties
416
        $userCreate = $userService->newUserCreateStruct(
417
            $login,
418
            $email,
419
            'secret',
420
            'eng-US'
421
        );
422
        $userCreate->enabled = true;
423
424
        // Set some fields required by the user ContentType
425
        $userCreate->setField('first_name', 'Example');
426
        $userCreate->setField('last_name', ucfirst($login));
427
428
        // Create a new user instance.
429
        $user = $userService->createUser($userCreate, array($userGroup));
430
        /* END: Inline */
431
432
        return $user;
433
    }
434
435
    /**
436
     * Create a user using given data.
437
     *
438
     * @param string $login
439
     * @param string $firstName
440
     * @param string $lastName
441
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup|null $userGroup optional user group, Editor by default
442
     *
443
     * @return \eZ\Publish\API\Repository\Values\User\User
444
     */
445
    protected function createUser($login, $firstName, $lastName, UserGroup $userGroup = null)
446
    {
447
        $repository = $this->getRepository();
448
449
        $userService = $repository->getUserService();
450
        if (null === $userGroup) {
451
            $userGroup = $userService->loadUserGroup(13);
452
        }
453
454
        // Instantiate a create struct with mandatory properties
455
        $userCreate = $userService->newUserCreateStruct(
456
            $login,
457
            "{$login}@example.com",
458
            'secret',
459
            'eng-US'
460
        );
461
        $userCreate->enabled = true;
462
463
        // Set some fields required by the user ContentType
464
        $userCreate->setField('first_name', $firstName);
465
        $userCreate->setField('last_name', $lastName);
466
467
        // Create a new user instance.
468
        $user = $userService->createUser($userCreate, array($userGroup));
469
470
        return $user;
471
    }
472
473
    /**
474
     * Only for internal use.
475
     *
476
     * Creates a \DateTime object for $timestamp in the current time zone
477
     *
478
     * @param int $timestamp
479
     *
480
     * @return \DateTime
481
     */
482
    public function createDateTime($timestamp = null)
483
    {
484
        $dateTime = new \DateTime();
485
        if ($timestamp !== null) {
486
            $dateTime->setTimestamp($timestamp);
487
        }
488
489
        return $dateTime;
490
    }
491
492
    /**
493
     * Calls given Repository's aggregated SearchHandler::refresh().
494
     *
495
     * Currently implemented only in Solr search engine.
496
     *
497
     * @param \eZ\Publish\API\Repository\Repository $repository
498
     */
499
    protected function refreshSearch(Repository $repository)
500
    {
501
        $setupFactory = $this->getSetupFactory();
502
503
        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...
504
            return;
505
        }
506
507
        while (true) {
508
            $repositoryReflection = new \ReflectionObject($repository);
509
            // If the repository is decorated, we need to recurse in the "repository" property
510
            if (!$repositoryReflection->hasProperty('repository')) {
511
                break;
512
            }
513
514
            $repositoryProperty = $repositoryReflection->getProperty('repository');
515
            $repositoryProperty->setAccessible(true);
516
            $repository = $repositoryProperty->getValue($repository);
517
        }
518
519
        $searchHandlerProperty = new \ReflectionProperty($repository, 'searchHandler');
520
        $searchHandlerProperty->setAccessible(true);
521
522
        /** @var \EzSystems\EzPlatformSolrSearchEngine\Handler $searchHandler */
523
        $searchHandler = $searchHandlerProperty->getValue($repository);
524
525
        $searchHandler->commit();
526
    }
527
528
    /**
529
     * Create role of a given name with the given policies described by an array.
530
     *
531
     * @param $roleName
532
     * @param array $policiesData [['module' => 'content', 'function' => 'read', 'limitations' => []]
533
     *
534
     * @return \eZ\Publish\API\Repository\Values\User\Role
535
     *
536
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
537
     * @throws \eZ\Publish\API\Repository\Exceptions\LimitationValidationException
538
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
539
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
540
     */
541
    public function createRoleWithPolicies($roleName, array $policiesData)
542
    {
543
        $repository = $this->getRepository(false);
544
        $roleService = $repository->getRoleService();
545
546
        $roleCreateStruct = $roleService->newRoleCreateStruct($roleName);
547
        foreach ($policiesData as $policyData) {
548
            $policyCreateStruct = $roleService->newPolicyCreateStruct(
549
                $policyData['module'],
550
                $policyData['function']
551
            );
552
553
            if (isset($policyData['limitations'])) {
554
                foreach ($policyData['limitations'] as $limitation) {
555
                    $policyCreateStruct->addLimitation($limitation);
556
                }
557
            }
558
559
            $roleCreateStruct->addPolicy($policyCreateStruct);
560
        }
561
562
        $roleDraft = $roleService->createRole($roleCreateStruct);
563
564
        $roleService->publishRoleDraft($roleDraft);
565
566
        return $roleService->loadRole($roleDraft->id);
567
    }
568
}
569