Completed
Push — master ( 947d72...ae3446 )
by Mahmoud
03:01
created

TestingTrait   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 410
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
wmc 48
c 0
b 0
f 0
lcom 1
cbo 6
dl 0
loc 410
rs 8.4864

22 Methods

Rating   Name   Duplication   Size   Complexity  
D apiCall() 0 28 10
A assertValidationErrorContain() 0 8 2
A getLoggedInTestingUser() 0 10 2
A getVisitor() 0 10 1
A getLoggedInTestingAdmin() 0 8 1
A makeAdmin() 0 8 1
A getLoggedInTestingUserToken() 0 4 1
B registerAndLoginTestingUser() 0 25 2
A registerAndLoginTestingAdmin() 0 8 1
A registerAndLoginTestingDeveloper() 0 11 2
A assertResponseContainKeys() 0 10 3
A assertResponseContainValues() 0 10 3
A assertResponseContainKeyValue() 0 12 2
A migrateDatabase() 0 4 1
A responseToArray() 0 12 3
A formatToKeyValueToString() 0 14 3
A mock() 0 7 1
A getResponseObject() 0 4 1
A injectEndpointId() 0 12 3
A getTestingFile() 0 10 1
A getApplicationTokenHeader() 0 13 1
A overrideSubDomain() 0 15 3

How to fix   Complexity   

Complex Class

Complex classes like TestingTrait 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 TestingTrait, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace App\Port\Tests\PHPUnit\Traits;
4
5
use App;
6
use App\Containers\Authorization\Models\Role;
7
use App\Containers\Authorization\Tasks\AttachRoleTask;
8
use App\Containers\User\Actions\CreateUserAction;
9
use Dingo\Api\Http\Response as DingoAPIResponse;
10
use Illuminate\Http\Response;
11
use Illuminate\Http\UploadedFile;
12
use Illuminate\Support\Arr as LaravelArr;
13
use Illuminate\Support\Facades\Config;
14
use Illuminate\Support\Str as LaravelStr;
15
use Mockery;
16
use Symfony\Component\Debug\Exception\UndefinedMethodException;
17
use Vinkla\Hashids\Facades\Hashids;
18
19
/**
20
 * Class TestingTrait.
21
 *
22
 * All the functions in this trait are accessible from all your tests.
23
 *
24
 * @author  Mahmoud Zalt <[email protected]>
25
 */
26
trait TestingTrait
27
{
28
29
    /**
30
     * the Logged in user, used for protected routes.
31
     *
32
     * @var User
33
     */
34
    public $loggedInTestingUser;
35
36
    /**
37
     * @param        $endpoint
38
     * @param string $verb
39
     * @param array  $data
40
     * @param bool   $protected
41
     * @param array  $headers
42
     *
43
     * @return  mixed
44
     * @throws \Symfony\Component\Debug\Exception\UndefinedMethodException
45
     */
46
    public function apiCall($endpoint, $verb = 'get', array $data = [], $protected = true, array $headers = [])
47
    {
48
        // if endpoint is protected (requires token to access it's functionality)
49
        if ($protected && !array_has($headers, 'Authorization')) {
50
            // append the token to the header
51
            $headers['Authorization'] = 'Bearer ' . $this->getLoggedInTestingUserToken();
52
        }
53
54
        switch ($verb) {
55
            case 'get':
56
                $endpoint = $data ? $endpoint . '?' . http_build_query($data) : $endpoint;
57
                $response = $this->get($endpoint, $headers)->response;
0 ignored issues
show
Bug introduced by
It seems like get() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
58
                break;
59
            case 'post':
60
            case 'put':
61
            case 'patch':
62
            case 'delete':
63
                $response = $this->{$verb}($endpoint, $data, $headers)->response;
64
                break;
65
            case 'json:post':
66
                $response = $this->json('post', $endpoint, $data, $headers)->response;
0 ignored issues
show
Bug introduced by
It seems like json() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
67
                break;
68
            default:
69
                throw new UndefinedMethodException('Undefined HTTP Verb (' . $verb . ').');
0 ignored issues
show
Bug introduced by
The call to UndefinedMethodException::__construct() misses a required argument $previous.

This check looks for function calls that miss required arguments.

Loading history...
70
        }
71
72
        return $response;
73
    }
74
75
    /**
76
     * @param \Dingo\Api\Http\Response $response
77
     * @param array                    $messages
78
     */
79
    public function assertValidationErrorContain(DingoAPIResponse $response, array $messages)
80
    {
81
        $arrayResponse = json_decode($response->getContent());
82
83
        foreach ($messages as $key => $value) {
84
            $this->assertEquals($arrayResponse->errors->{$key}[0], $value);
0 ignored issues
show
Bug introduced by
It seems like assertEquals() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
85
        }
86
    }
87
88
    /**
89
     * get teh current logged in user.
90
     *
91
     * @return \App\Port\Tests\PHPUnit\Traits\User|mixed
92
     */
93
    public function getLoggedInTestingUser()
94
    {
95
        $user = $this->loggedInTestingUser;
96
97
        if (!$user) {
98
            $user = $this->registerAndLoginTestingUser();
99
        }
100
101
        return $user;
102
    }
103
104
    /**
105
     * This returned visitor is a normal user, with `visitor_id` means
106
     * before he became a registered user (can login) was a visitor.
107
     * So this can be used to test endpoints that are protected by visitors
108
     * access.
109
     *
110
     * @return  \App\Port\Tests\PHPUnit\Traits\User|mixed
111
     */
112
    public function getVisitor()
113
    {
114
        $user = $this->getLoggedInTestingUser();
115
116
        $user->visitor_id = str_random('20');
117
        unset($user->token);
118
        $user->save();
119
120
        return $user;
121
    }
122
123
    /**
124
     * @return  \App\Port\Tests\PHPUnit\Traits\User|mixed
125
     */
126
    public function getLoggedInTestingAdmin()
127
    {
128
        $user = $this->getLoggedInTestingUser();
129
130
        $user = $this->makeAdmin($user);
131
132
        return $user;
133
    }
134
135
    /**
136
     * @param $user
137
     *
138
     * @return  mixed
139
     */
140
    public function makeAdmin($user)
141
    {
142
        $adminRole = Role::where('name', 'admin')->first();
143
144
        $user->attachRole($adminRole);
145
146
        return $user;
147
    }
148
149
    /**
150
     * get teh current logged in user token.
151
     *
152
     * @return string
153
     */
154
    public function getLoggedInTestingUserToken()
155
    {
156
        return $this->getLoggedInTestingUser()->token;
157
    }
158
159
    /**
160
     * @param null $userDetails
161
     *
162
     * @return mixed
163
     */
164
    public function registerAndLoginTestingUser($userDetails = null)
165
    {
166
        // if no user detail provided, use the default details.
167
        if (!$userDetails) {
168
            $userDetails = [
169
                'name'     => 'Mahmoud Zalt',
170
                'email'    => '[email protected]',
171
                'password' => 'secret.Pass7',
172
            ];
173
        }
174
175
        $createUserAction = App::make(CreateUserAction::class);
176
177
        // create new user and login (true)
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
178
        $user = $createUserAction->run(
179
            $userDetails['email'],
180
            $userDetails['password'],
181
            $userDetails['name'],
182
            null,
183
            null,
184
            true
185
        );
186
187
        return $this->loggedInTestingUser = $user;
188
    }
189
190
    /**
191
     * @param null $userDetails
192
     *
193
     * @return  mixed
194
     */
195
    public function registerAndLoginTestingAdmin($userDetails = null)
196
    {
197
        $user = $this->registerAndLoginTestingUser($userDetails);
198
199
        $user = $this->makeAdmin($user);
200
201
        return $user;
202
    }
203
204
    /**
205
     * Normal user with Developer Role
206
     *
207
     * @param null $userDetails
208
     *
209
     * @return  mixed
210
     */
211
    public function registerAndLoginTestingDeveloper($userDetails = null)
212
    {
213
        $user = $this->registerAndLoginTestingUser($userDetails);
214
215
        // Give Developer Role to this User if he doesn't have it already
216
        if (!$user->hasRole('developer')) {
217
            App::make(AttachRoleTask::class)->run($user, ['developer']);
218
        }
219
220
        return $user;
221
    }
222
223
    /**
224
     * @param $keys
225
     * @param $response
226
     */
227
    public function assertResponseContainKeys($keys, $response)
228
    {
229
        if (!is_array($keys)) {
230
            $keys = (array)$keys;
231
        }
232
233
        foreach ($keys as $key) {
234
            $this->assertTrue(array_key_exists($key, $this->responseToArray($response)));
0 ignored issues
show
Bug introduced by
It seems like assertTrue() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
235
        }
236
    }
237
238
    /**
239
     * @param $values
240
     * @param $response
241
     */
242
    public function assertResponseContainValues($values, $response)
243
    {
244
        if (!is_array($values)) {
245
            $values = (array)$values;
246
        }
247
248
        foreach ($values as $value) {
249
            $this->assertTrue(in_array($value, $this->responseToArray($response)));
0 ignored issues
show
Bug introduced by
It seems like assertTrue() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
250
        }
251
    }
252
253
    /**
254
     * @param $data
255
     * @param $response
256
     */
257
    public function assertResponseContainKeyValue($data, $response)
258
    {
259
        $response = json_encode(LaravelArr::sortRecursive(
260
            (array)$this->responseToArray($response)
261
        ));
262
263
        foreach (LaravelArr::sortRecursive($data) as $key => $value) {
264
            $expected = $this->formatToExpectedJson($key, $value);
0 ignored issues
show
Bug introduced by
It seems like formatToExpectedJson() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
265
            $this->assertTrue(LaravelStr::contains($response, $expected),
0 ignored issues
show
Bug introduced by
It seems like assertTrue() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
266
                "The JSON fragment [ {$expected} ] does not exist in the response [ {$response} ].");
267
        }
268
    }
269
270
    /**
271
     * Migrate the database.
272
     */
273
    public function migrateDatabase()
274
    {
275
        \Artisan::call('migrate');
276
    }
277
278
    /**
279
     * @param $response
280
     *
281
     * @return mixed
282
     */
283
    private function responseToArray($response)
284
    {
285
        if ($response instanceof \Illuminate\Http\Response) {
286
            $response = json_decode($response->getContent(), true);
287
        }
288
289
        if (array_key_exists('data', $response)) {
290
            $response = $response['data'];
291
        }
292
293
        return $response;
294
    }
295
296
    /**
297
     * Format the given key and value into a JSON string for expectation checks.
298
     *
299
     * @param string $key
300
     * @param mixed  $value
301
     *
302
     * @return string
303
     */
304
    private function formatToKeyValueToString($key, $value)
305
    {
306
        $expected = json_encode([$key => $value]);
307
308
        if (LaravelStr::startsWith($expected, '{')) {
309
            $expected = substr($expected, 1);
310
        }
311
312
        if (LaravelStr::endsWith($expected, '}')) {
313
            $expected = substr($expected, 0, -1);
314
        }
315
316
        return $expected;
317
    }
318
319
    /**
320
     * Mocking helper
321
     *
322
     * @param $class
323
     *
324
     * @return  \Mockery\MockInterface
325
     */
326
    public function mock($class)
327
    {
328
        $mock = Mockery::mock($class);
329
        App::instance($class, $mock);
330
331
        return $mock;
332
    }
333
334
    /**
335
     * get response object, get the string content from it and convert it to an std object
336
     * making it easier to read
337
     *
338
     * @param $response
339
     *
340
     * @return  mixed
341
     */
342
    public function getResponseObject(Response $response)
343
    {
344
        return json_decode($response->getContent());
345
    }
346
347
    /**
348
     * Inject the ID in the Endpoint URI
349
     *
350
     * Example: you give it ('users/{id}/stores', 100) it returns 'users/100/stores'
351
     *
352
     * @param      $endpoint
353
     * @param      $id
354
     * @param bool $skipEncoding
355
     *
356
     * @return  mixed
357
     */
358
    public function injectEndpointId($endpoint, $id, $skipEncoding = false)
359
    {
360
        // In case Hash ID is enabled it will encode the ID first
361
        if (Config::get('hello.hash-id')) {
362
363
            if (!$skipEncoding) {
364
                $id = Hashids::encode($id);
365
            }
366
        }
367
368
        return str_replace("{id}", $id, $endpoint);
369
    }
370
371
    /**
372
     * Make sure you have an image in `storage/tests/` named to `a.jpeg` or anything else
373
     *
374
     * @param string $original_name
375
     * @param string $mime_type
376
     * @param int    $size
377
     *
378
     * @return  \Illuminate\Http\UploadedFile
379
     */
380
    public static function getTestingFile($original_name = 'a.jpeg', $mime_type = 'image/jpeg', $size = 2476)
381
    {
382
        $path = storage_path('tests/' . $original_name);
383
        $error = null;
384
        $test = true;
385
386
        $file = new UploadedFile($path, $original_name, $mime_type, $size, $error, $test);
387
388
        return $file;
389
    }
390
391
    /**
392
     * Create Application in the database with Token based on the User who made the request.
393
     * And return headers array with the Application stored token in it.
394
     * This is made to be used with the endpoints protected with `app.auth` middleware.
395
     *
396
     * @param string $endpoint
397
     * @param string $verb
398
     * @param array  $data
399
     */
400
    public function getApplicationTokenHeader(
401
        $endpoint = 'apps/',
402
        $verb = 'post',
403
        $data = ['name' => 'Testing Application']
404
    ) {
405
        $application = $this->apiCall($endpoint, $verb, $data);
406
407
        //TODO: remove dependency data->token depend of the Authorization container transformer response of the App
408
        // override the header with the application token instead of the default user token
409
        $headers['Authorization'] = 'Bearer ' . $this->getResponseObject($application)->data->token;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$headers was never initialized. Although not strictly required by PHP, it is generally a good practice to add $headers = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
410
411
        return $headers;
412
    }
413
414
    /**
415
     * override default URL subDomain in case you want to change it for some tests
416
     *
417
     * @param $subDomain
418
     */
419
    public function overrideSubDomain($subDomain, $url = null)
420
    {
421
        $url = ($url) ? : $this->baseUrl;
0 ignored issues
show
Bug introduced by
The property baseUrl does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
422
423
        $info = parse_url($url);
424
425
        $array = explode('.', $info['host']);
426
427
        $withoutDomain = (array_key_exists(count($array) - 2,
428
                $array) ? $array[count($array) - 2] : '') . '.' . $array[count($array) - 1];
429
430
        $newSubDomain = $info['scheme'] . '://' . $subDomain . '.' . $withoutDomain;
431
432
        $this->baseUrl = $newSubDomain;
433
    }
434
435
}
436