Completed
Push — master ( 0b995a...c9ed4b )
by Mahmoud
03:16
created

TestingTrait   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 367
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
wmc 53
lcom 1
cbo 7
dl 0
loc 367
rs 7.4757
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
D apiCall() 0 28 10
A getTestingFile() 0 6 1
A getTestingImage() 0 4 1
A getTestingAdmin() 0 7 1
A getLoggedInTestingUserToken() 0 4 1
A getTestingUser() 0 8 2
A getTestingUserWithoutPermissions() 0 8 2
B createTestingUser() 0 25 4
B setupTestingUserAccess() 0 13 6
A assertValidationErrorContain() 0 8 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 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\Test\PHPUnit\Traits;
4
5
use App;
6
use App\Containers\Authentication\Tasks\ApiLoginThisUserObjectTask;
7
use App\Containers\User\Models\User;
8
use Artisan;
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\Facades\Hash;
15
use Illuminate\Support\Str as LaravelStr;
16
use Mockery;
17
use Symfony\Component\Debug\Exception\UndefinedMethodException;
18
use Vinkla\Hashids\Facades\Hashids;
19
20
/**
21
 * Class TestingTrait.
22
 *
23
 * All the functions in this trait are accessible from all your tests.
24
 *
25
 * @author  Mahmoud Zalt <[email protected]>
26
 */
27
trait TestingTrait
28
{
29
30
    /**
31
     * the Logged in user, used for protected routes.
32
     *
33
     * @var User
34
     */
35
    public $loggedInTestingUser;
36
37
    /**
38
     * @param        $endpoint
39
     * @param string $verb
40
     * @param array  $data
41
     * @param bool   $protected
42
     * @param array  $headers
43
     *
44
     * @return  mixed
45
     * @throws \Symfony\Component\Debug\Exception\UndefinedMethodException
46
     */
47
    public function apiCall($endpoint, $verb = 'get', array $data = [], $protected = true, array $headers = [])
48
    {
49
        // if endpoint is protected (requires token to access it's functionality)
50
        if ($protected && !array_has($headers, 'Authorization')) {
51
            // append the token to the header
52
            $headers['Authorization'] = 'Bearer ' . $this->getLoggedInTestingUserToken();
53
        }
54
55
        switch ($verb) {
56
            case 'get':
57
                $endpoint = $data ? $endpoint . '?' . http_build_query($data) : $endpoint;
58
                $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...
59
                break;
60
            case 'post':
61
            case 'put':
62
            case 'patch':
63
            case 'delete':
64
                $response = $this->{$verb}($endpoint, $data, $headers)->response;
65
                break;
66
            case 'json:post':
67
                $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...
68
                break;
69
            default:
70
                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...
71
        }
72
73
        return $response;
74
    }
75
76
    /**
77
     * @param        $fileName
78
     * @param        $stubDirPath
79
     * @param string $mimeType
80
     * @param null   $size
81
     *
82
     * @return  \Illuminate\Http\UploadedFile
83
     */
84
    public function getTestingFile($fileName, $stubDirPath, $mimeType = 'text/plain', $size = null)
85
    {
86
        $file = $stubDirPath . $fileName;
87
88
        return new UploadedFile($file, $fileName, $mimeType, $size, null, true); // null = null | $testMode = 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...
89
    }
90
91
    /**
92
     * @param        $imageName
93
     * @param        $stubDirPath
94
     * @param string $mimeType
95
     * @param null   $size
96
     *
97
     * @return  \Illuminate\Http\UploadedFile
98
     */
99
    public function getTestingImage($imageName, $stubDirPath, $mimeType = 'image/jpeg', $size = null)
100
    {
101
        return $this->getTestingFile($imageName, $stubDirPath, $mimeType, $size);
102
    }
103
104
105
    /**
106
     * get teh current logged in user OR create new one if no one exist
107
     *
108
     * @param null $access
109
     *
110
     * @return  \App\Containers\User\Models\User|mixed
111
     */
112
    public function getTestingUser($access = null)
113
    {
114
        if (!$user = $this->loggedInTestingUser) {
115
            $user = $this->createTestingUser($access);
116
        }
117
118
        return $user;
119
    }
120
121
    /**
122
     * @return  \App\Containers\User\Models\User|mixed
123
     */
124
    public function getTestingUserWithoutPermissions()
125
    {
126
        if (!$user = $this->loggedInTestingUser) {
127
            $user = $this->getTestingUser(['permissions' => null, 'roles' => null]);
128
        }
129
130
        return $user;
131
    }
132
133
    /**
134
     * @param null $permissions
135
     *
136
     * @return  \App\Containers\User\Models\User|mixed
137
     */
138
    public function getTestingAdmin($permissions = null)
139
    {
140
        return $this->getTestingUser([
141
            'roles'        => 'admin',
142
            '$permissions' => $permissions,
143
        ]);
144
    }
145
146
    /**
147
     * get teh current logged in user token.
148
     *
149
     * @return string
150
     */
151
    public function getLoggedInTestingUserToken()
152
    {
153
        return $this->getTestingUser()->token;
154
    }
155
156
    /**
157
     * @param null $access
158
     * @param null $userDetails
159
     *
160
     * @return  mixed
161
     */
162
    public function createTestingUser($access = null, $userDetails = null)
163
    {
164
165
        // if no user detail provided, use the default details.
166
        $userDetails = $userDetails ? : [
167
            'name'     => 'Developer user',
168
            'email'    => $this->faker->email,
0 ignored issues
show
Bug introduced by
The property faker 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...
169
            'password' => 'testing-pass',
170
        ];
171
172
        // create new user and login
173
        $user = factory(User::class)->create([
174
            'email'    => $userDetails['email'],
175
            'password' => Hash::make($userDetails['password']),
176
            'name'     => $userDetails['name'],
177
        ]);
178
179
        // assign roles and permissions
180
        $user = $this->setupTestingUserAccess($user, $access ? : (isset($this->access) ? $this->access : null));
0 ignored issues
show
Bug introduced by
The property access 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...
181
182
        // log the user in
183
        $user = App::make(ApiLoginThisUserObjectTask::class)->run($user);
184
185
        return $this->loggedInTestingUser = $user;
186
    }
187
188
    /**
189
     * @param $user
190
     * @param $access
191
     *
192
     * @return  mixed
193
     */
194
    private function setupTestingUserAccess($user, $access)
195
    {
196
        if (isset($access['permissions']) && !empty($access['permissions'])) {
197
            $user->givePermissionTo($access['permissions']);
198
        }
199
        if (isset($access['roles']) && !empty($access['roles'])) {
200
            if (!$user->hasRole($access['roles'])) {
201
                $user->assignRole($access['roles']);
202
            }
203
        }
204
205
        return $user;
206
    }
207
208
209
    /**
210
     * @param \Dingo\Api\Http\Response $response
211
     * @param array                    $messages
212
     */
213
    public function assertValidationErrorContain(DingoAPIResponse $response, array $messages)
214
    {
215
        $arrayResponse = json_decode($response->getContent());
216
217
        foreach ($messages as $key => $value) {
218
            $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...
219
        }
220
    }
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
     * override default URL subDomain in case you want to change it for some tests
373
     *
374
     * @param      $subDomain
375
     * @param null $url
376
     */
377
    public function overrideSubDomain($subDomain, $url = null)
378
    {
379
        $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...
380
381
        $info = parse_url($url);
382
383
        $array = explode('.', $info['host']);
384
385
        $withoutDomain = (array_key_exists(count($array) - 2,
386
                $array) ? $array[count($array) - 2] : '') . '.' . $array[count($array) - 1];
387
388
        $newSubDomain = $info['scheme'] . '://' . $subDomain . '.' . $withoutDomain;
389
390
        $this->baseUrl = $newSubDomain;
391
    }
392
393
}
394