Completed
Push — master ( 94ac50...f1dd30 )
by Mahmoud
06:45
created

TestingTrait::responseToArray()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 12
rs 9.4285
cc 3
eloc 6
nc 4
nop 1
1
<?php
2
3
namespace App\Port\Tests\PHPUnit\Traits;
4
5
use App;
6
use App\Containers\User\Actions\CreateUserAction;
7
use Dingo\Api\Http\Response as DingoAPIResponse;
8
use Illuminate\Http\Response;
9
use Illuminate\Support\Arr as LaravelArr;
10
use Illuminate\Support\Str as LaravelStr;
11
use Mockery;
12
use Symfony\Component\Debug\Exception\UndefinedMethodException;
13
use App\Containers\Authorization\Models\Role;
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
54
        // if endpoint is protected (requires token to access it's functionality)
55
        if ($protected && !array_has($header, 'Authorization')) {
56
            // append the token to the header
57
            $headers['Authorization'] = 'Bearer ' . $this->getLoggedInTestingUserToken();
58
        }
59
60
        if (!$protected && !array_has($header, 'Visitor-Id')) {
61
            // append the Device ID to the header (IPhone UUID, Android ID, ...)
62
            $headers['Visitor-Id'] = str_random(40);
63
        }
64
65
        $verb = strtolower($verb);
66
67
        switch ($verb) {
68
            case 'get':
69
                $endpoint = $data ? $endpoint . '?' . http_build_query($data) : $endpoint;
70
                $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...
71
                break;
72
            case 'post':
73
            case 'put':
74
            case 'patch':
75
            case 'delete':
76
                $response = $this->{$verb}($endpoint, $data, $headers)->response;
77
                break;
78
            default:
79
                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...
80
        }
81
82
        return $response;
83
    }
84
85
    /**
86
     * @param \Dingo\Api\Http\Response $response
87
     * @param array                    $messages
88
     */
89
    public function assertValidationErrorContain(DingoAPIResponse $response, array $messages)
90
    {
91
92
        $arrayResponse = json_decode($response->getContent());
93
94
        foreach ($messages as $key => $value) {
95
            $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...
96
        }
97
    }
98
99
    /**
100
     * get teh current logged in user.
101
     *
102
     * @return \App\Port\Tests\PHPUnit\Traits\User|mixed
103
     */
104
    public function getLoggedInTestingUser()
105
    {
106
        $user = $this->loggedInTestingUser;
107
108
        if (!$user) {
109
            $user = $this->registerAndLoginTestingUser();
110
        }
111
112
        return $user;
113
    }
114
115
    /**
116
     * @return  \App\Port\Tests\PHPUnit\Traits\User|mixed
117
     */
118
    public function getLoggedInTestingAdmin()
119
    {
120
        $user = $this->getLoggedInTestingUser();
121
122
        $adminRole = Role::where('name', 'admin')->first();
123
124
        $user->attachRole($adminRole);
125
126
        return $user;
127
    }
128
129
    /**
130
     * get teh current logged in user token.
131
     *
132
     * @return string
133
     */
134
    public function getLoggedInTestingUserToken()
135
    {
136
        return $this->getLoggedInTestingUser()->token;
137
    }
138
139
    /**
140
     * @param null $userDetails
141
     *
142
     * @return mixed
143
     */
144
    public function registerAndLoginTestingUser($userDetails = null)
145
    {
146
        // if no user detail provided, use the default details.
147
        if (!$userDetails) {
148
            $userDetails = [
149
                'name'     => 'Mahmoud Zalt',
150
                'email'    => '[email protected]',
151
                'password' => 'secret.Pass7',
152
            ];
153
        }
154
155
        $CreateUserAction = App::make(CreateUserAction::class);
156
157
        // 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...
158
        $user = $CreateUserAction->run(
159
            $userDetails['email'],
160
            $userDetails['password'],
161
            $userDetails['name'],
162
            true
163
        );
164
165
        return $this->loggedInTestingUser = $user;
166
    }
167
168
    /**
169
     * @param $keys
170
     * @param $response
171
     */
172
    public function assertResponseContainKeys($keys, $response)
173
    {
174
        if (!is_array($keys)) {
175
            $keys = (array)$keys;
176
        }
177
178
        foreach ($keys as $key) {
179
            $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...
180
        }
181
    }
182
183
    /**
184
     * @param $values
185
     * @param $response
186
     */
187
    public function assertResponseContainValues($values, $response)
188
    {
189
        if (!is_array($values)) {
190
            $values = (array)$values;
191
        }
192
193
        foreach ($values as $value) {
194
            $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...
195
        }
196
    }
197
198
    /**
199
     * @param $data
200
     * @param $response
201
     */
202
    public function assertResponseContainKeyValue($data, $response)
203
    {
204
        $response = json_encode(LaravelArr::sortRecursive(
205
            (array)$this->responseToArray($response)
206
        ));
207
208
        foreach (LaravelArr::sortRecursive($data) as $key => $value) {
209
            $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...
210
            $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...
211
                "The JSON fragment [ {$expected} ] does not exist in the response [ {$response} ].");
212
        }
213
    }
214
215
    /**
216
     * Prettify printing JSON data (mainly for API responses) in the terminal.
217
     *
218
     * @param $json
219
     *
220
     * @return string
221
     */
222
    public function ddj($json)
223
    {
224
        $result = '';
225
        $pos = 0;
226
        $strLen = strlen($json);
227
        $indentStr = '  ';
228
        $newLine = "\n";
229
        $prevChar = '';
230
        $outOfQuotes = true;
231
232
        for ($i = 0; $i <= $strLen; ++$i) {
233
234
            // Grab the next character in the string.
235
            $char = substr($json, $i, 1);
236
237
            // Are we inside a quoted string?
238
            if ($char == '"' && $prevChar != '\\') {
239
                $outOfQuotes = !$outOfQuotes;
240
241
                // If this character is the end of an element,
242
                // output a new line and indent the next line.
243
            } else {
244
                if (($char == '}' || $char == ']') && $outOfQuotes) {
245
                    $result .= $newLine;
246
                    --$pos;
247
                    for ($j = 0; $j < $pos; ++$j) {
248
                        $result .= $indentStr;
249
                    }
250
                }
251
            }
252
253
            // Add the character to the result string.
254
            $result .= $char;
255
256
            // If the last character was the beginning of an element,
257
            // output a new line and indent the next line.
258
            if (($char == ',' || $char == '{' || $char == '[') && $outOfQuotes) {
259
                $result .= $newLine;
260
                if ($char == '{' || $char == '[') {
261
                    ++$pos;
262
                }
263
264
                for ($j = 0; $j < $pos; ++$j) {
265
                    $result .= $indentStr;
266
                }
267
            }
268
269
            $prevChar = $char;
270
        }
271
272
        dd($result);
273
    }
274
275
    /**
276
     * Migrate the database.
277
     */
278
    public function migrateDatabase()
279
    {
280
        \Artisan::call('migrate');
281
    }
282
283
    /**
284
     * @param $response
285
     *
286
     * @return mixed
287
     */
288
    private function responseToArray($response)
289
    {
290
        if ($response instanceof \Illuminate\Http\Response) {
291
            $response = json_decode($response->getContent(), true);
292
        }
293
294
        if (array_key_exists('data', $response)) {
295
            $response = $response['data'];
296
        }
297
298
        return $response;
299
    }
300
301
    /**
302
     * Format the given key and value into a JSON string for expectation checks.
303
     *
304
     * @param string $key
305
     * @param mixed  $value
306
     *
307
     * @return string
308
     */
309
    private function formatToKeyValueToString($key, $value)
310
    {
311
        $expected = json_encode([$key => $value]);
312
313
        if (LaravelStr::startsWith($expected, '{')) {
314
            $expected = substr($expected, 1);
315
        }
316
317
        if (LaravelStr::endsWith($expected, '}')) {
318
            $expected = substr($expected, 0, -1);
319
        }
320
321
        return $expected;
322
    }
323
324
    /**
325
     * Mocking helper
326
     *
327
     * @param $class
328
     *
329
     * @return  \Mockery\MockInterface
330
     */
331
    public function mock($class)
332
    {
333
        $mock = Mockery::mock($class);
334
        $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...
335
336
        return $mock;
337
    }
338
339
    /**
340
     * get response object, get the string content from it and convert it to an std object
341
     * making it easier to read
342
     *
343
     * @param $response
344
     *
345
     * @return  mixed
346
     */
347
    public function getResponseObject(Response $response)
348
    {
349
        return json_decode($response->getContent());
350
    }
351
352
}
353