Completed
Push — master ( 739835...9cb3a5 )
by Mahmoud
03:59
created

TestingTrait   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 387
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

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

19 Methods

Rating   Name   Duplication   Size   Complexity  
D apiCall() 0 35 9
A assertValidationErrorContain() 0 9 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 assertResponseContainKeys() 0 10 3
A assertResponseContainValues() 0 10 3
A assertResponseContainKeyValue() 0 12 2
C ddj() 0 52 15
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 4 1

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\User\Actions\CreateUserAction;
8
use Dingo\Api\Http\Response as DingoAPIResponse;
9
use Illuminate\Http\Response;
10
use Illuminate\Support\Arr as LaravelArr;
11
use Illuminate\Support\Str as LaravelStr;
12
use Mockery;
13
use Symfony\Component\Debug\Exception\UndefinedMethodException;
14
15
/**
16
 * Class TestingTrait.
17
 *
18
 * All the functions in this trait are accessible from all your tests.
19
 *
20
 * @author  Mahmoud Zalt <[email protected]>
21
 */
22
trait TestingTrait
23
{
24
25
    /**
26
     * the Logged in user, used for protected routes.
27
     *
28
     * @var User
29
     */
30
    public $loggedInTestingUser;
31
32
    /**
33
     * @param           $endpoint
34
     * @param string    $verb
35
     * @param array     $data
36
     * @param bool|true $protected
37
     * @param array     $header
38
     *
39
     * @throws \Symfony\Component\Debug\Exception\UndefinedMethodException
40
     *
41
     * @return mixed
42
     */
43
    public function apiCall($endpoint, $verb = 'get', array $data = [], $protected = true, array $header = [])
44
    {
45
        $content = json_encode($data);
46
47
        $headers = array_merge([
48
            'CONTENT_LENGTH' => mb_strlen($content, '8bit'),
49
//            'CONTENT_TYPE' => 'application/json',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% 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...
50
            'Accept'         => 'application/json',
51
        ], $header);
52
53
        // if endpoint is protected (requires token to access it's functionality)
54
        if ($protected && !array_has($header, 'Authorization')) {
55
            // append the token to the header
56
            $headers['Authorization'] = 'Bearer ' . $this->getLoggedInTestingUserToken();
57
        }
58
59
        $verb = strtolower($verb);
60
61
        switch ($verb) {
62
            case 'get':
63
                $endpoint = $data ? $endpoint . '?' . http_build_query($data) : $endpoint;
64
                $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...
65
                break;
66
            case 'post':
67
            case 'put':
68
            case 'patch':
69
            case 'delete':
70
                $response = $this->{$verb}($endpoint, $data, $headers)->response;
71
                break;
72
            default:
73
                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...
74
        }
75
76
        return $response;
77
    }
78
79
    /**
80
     * @param \Dingo\Api\Http\Response $response
81
     * @param array                    $messages
82
     */
83
    public function assertValidationErrorContain(DingoAPIResponse $response, array $messages)
84
    {
85
86
        $arrayResponse = json_decode($response->getContent());
87
88
        foreach ($messages as $key => $value) {
89
            $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...
90
        }
91
    }
92
93
    /**
94
     * get teh current logged in user.
95
     *
96
     * @return \App\Port\Tests\PHPUnit\Traits\User|mixed
97
     */
98
    public function getLoggedInTestingUser()
99
    {
100
        $user = $this->loggedInTestingUser;
101
102
        if (!$user) {
103
            $user = $this->registerAndLoginTestingUser();
104
        }
105
106
        return $user;
107
    }
108
109
    /**
110
     * This returned visitor is a normal user, with `visitor_id` means
111
     * before he became a registered user (can login) was a visitor.
112
     * So this can be used to test endpoints that are protected by visitors
113
     * access.
114
     *
115
     * @return  \App\Port\Tests\PHPUnit\Traits\User|mixed
116
     */
117
    public function getVisitor()
118
    {
119
        $user = $this->getLoggedInTestingUser();
120
121
        $user->visitor_id = str_random('20');
122
        unset($user->token);
123
        $user->save();
124
125
        return $user;
126
    }
127
128
    /**
129
     * @return  \App\Port\Tests\PHPUnit\Traits\User|mixed
130
     */
131
    public function getLoggedInTestingAdmin()
132
    {
133
        $user = $this->getLoggedInTestingUser();
134
135
        $user = $this->makeAdmin($user);
136
137
        return $user;
138
    }
139
140
    /**
141
     * @param $user
142
     *
143
     * @return  mixed
144
     */
145
    public function makeAdmin($user)
146
    {
147
        $adminRole = Role::where('name', 'admin')->first();
148
149
        $user->attachRole($adminRole);
150
151
        return $user;
152
    }
153
154
    /**
155
     * get teh current logged in user token.
156
     *
157
     * @return string
158
     */
159
    public function getLoggedInTestingUserToken()
160
    {
161
        return $this->getLoggedInTestingUser()->token;
162
    }
163
164
    /**
165
     * @param null $userDetails
166
     *
167
     * @return mixed
168
     */
169
    public function registerAndLoginTestingUser($userDetails = null)
170
    {
171
        // if no user detail provided, use the default details.
172
        if (!$userDetails) {
173
            $userDetails = [
174
                'name'     => 'Mahmoud Zalt',
175
                'email'    => '[email protected]',
176
                'password' => 'secret.Pass7',
177
            ];
178
        }
179
180
        $createUserAction = App::make(CreateUserAction::class);
181
182
        // 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...
183
        $user = $createUserAction->run(
184
            $userDetails['email'],
185
            $userDetails['password'],
186
            $userDetails['name'],
187
            null,
188
            null,
189
            true
190
        );
191
192
        return $this->loggedInTestingUser = $user;
193
    }
194
195
    /**
196
     * @param null $userDetails
197
     *
198
     * @return  mixed
199
     */
200
    public function registerAndLoginTestingAdmin($userDetails = null)
201
    {
202
        $user = $this->registerAndLoginTestingUser($userDetails);
203
204
        $user = $this->makeAdmin($user);
205
206
        return $user;
207
    }
208
209
    /**
210
     * @param $keys
211
     * @param $response
212
     */
213
    public function assertResponseContainKeys($keys, $response)
214
    {
215
        if (!is_array($keys)) {
216
            $keys = (array)$keys;
217
        }
218
219
        foreach ($keys as $key) {
220
            $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...
221
        }
222
    }
223
224
    /**
225
     * @param $values
226
     * @param $response
227
     */
228
    public function assertResponseContainValues($values, $response)
229
    {
230
        if (!is_array($values)) {
231
            $values = (array)$values;
232
        }
233
234
        foreach ($values as $value) {
235
            $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...
236
        }
237
    }
238
239
    /**
240
     * @param $data
241
     * @param $response
242
     */
243
    public function assertResponseContainKeyValue($data, $response)
244
    {
245
        $response = json_encode(LaravelArr::sortRecursive(
246
            (array)$this->responseToArray($response)
247
        ));
248
249
        foreach (LaravelArr::sortRecursive($data) as $key => $value) {
250
            $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...
251
            $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...
252
                "The JSON fragment [ {$expected} ] does not exist in the response [ {$response} ].");
253
        }
254
    }
255
256
    /**
257
     * Prettify printing JSON data (mainly for API responses) in the terminal.
258
     *
259
     * @param $json
260
     *
261
     * @return string
262
     */
263
    public function ddj($json)
264
    {
265
        $result = '';
266
        $pos = 0;
267
        $strLen = strlen($json);
268
        $indentStr = '  ';
269
        $newLine = "\n";
270
        $prevChar = '';
271
        $outOfQuotes = true;
272
273
        for ($i = 0; $i <= $strLen; ++$i) {
274
275
            // Grab the next character in the string.
276
            $char = substr($json, $i, 1);
277
278
            // Are we inside a quoted string?
279
            if ($char == '"' && $prevChar != '\\') {
280
                $outOfQuotes = !$outOfQuotes;
281
282
                // If this character is the end of an element,
283
                // output a new line and indent the next line.
284
            } else {
285
                if (($char == '}' || $char == ']') && $outOfQuotes) {
286
                    $result .= $newLine;
287
                    --$pos;
288
                    for ($j = 0; $j < $pos; ++$j) {
289
                        $result .= $indentStr;
290
                    }
291
                }
292
            }
293
294
            // Add the character to the result string.
295
            $result .= $char;
296
297
            // If the last character was the beginning of an element,
298
            // output a new line and indent the next line.
299
            if (($char == ',' || $char == '{' || $char == '[') && $outOfQuotes) {
300
                $result .= $newLine;
301
                if ($char == '{' || $char == '[') {
302
                    ++$pos;
303
                }
304
305
                for ($j = 0; $j < $pos; ++$j) {
306
                    $result .= $indentStr;
307
                }
308
            }
309
310
            $prevChar = $char;
311
        }
312
313
        dd($result);
314
    }
315
316
    /**
317
     * Migrate the database.
318
     */
319
    public function migrateDatabase()
320
    {
321
        \Artisan::call('migrate');
322
    }
323
324
    /**
325
     * @param $response
326
     *
327
     * @return mixed
328
     */
329
    private function responseToArray($response)
330
    {
331
        if ($response instanceof \Illuminate\Http\Response) {
332
            $response = json_decode($response->getContent(), true);
333
        }
334
335
        if (array_key_exists('data', $response)) {
336
            $response = $response['data'];
337
        }
338
339
        return $response;
340
    }
341
342
    /**
343
     * Format the given key and value into a JSON string for expectation checks.
344
     *
345
     * @param string $key
346
     * @param mixed  $value
347
     *
348
     * @return string
349
     */
350
    private function formatToKeyValueToString($key, $value)
351
    {
352
        $expected = json_encode([$key => $value]);
353
354
        if (LaravelStr::startsWith($expected, '{')) {
355
            $expected = substr($expected, 1);
356
        }
357
358
        if (LaravelStr::endsWith($expected, '}')) {
359
            $expected = substr($expected, 0, -1);
360
        }
361
362
        return $expected;
363
    }
364
365
    /**
366
     * Mocking helper
367
     *
368
     * @param $class
369
     *
370
     * @return  \Mockery\MockInterface
371
     */
372
    public function mock($class)
373
    {
374
        $mock = Mockery::mock($class);
375
        $this->app->instance($class, $mock);
0 ignored issues
show
Bug introduced by
The property app 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...
376
377
        return $mock;
378
    }
379
380
    /**
381
     * get response object, get the string content from it and convert it to an std object
382
     * making it easier to read
383
     *
384
     * @param $response
385
     *
386
     * @return  mixed
387
     */
388
    public function getResponseObject(Response $response)
389
    {
390
        return json_decode($response->getContent());
391
    }
392
393
    /**
394
     * Inject the ID in the Endpoint URI
395
     *
396
     * Example: you give it ('users/{id}/stores', 100) it returns 'users/100/stores'
397
     *
398
     * @param $endpoint
399
     * @param $id
400
     *
401
     * @return  mixed
402
     */
403
    public function injectEndpointId($endpoint, $id)
404
    {
405
        return str_replace("{id}", $id, $endpoint);
406
    }
407
408
}
409