TestsRequestHelperTrait::injectAccessToken()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 4
nc 2
nop 1
1
<?php
2
3
namespace App\Ship\Features\Tests\PhpUnit;
4
5
use App;
6
use App\Ship\Features\Exceptions\MissingTestEndpointException;
7
use App\Ship\Features\Exceptions\UndefinedMethodException;
8
use App\Ship\Features\Exceptions\WrongEndpointFormatException;
9
use Illuminate\Support\Facades\Config;
10
use Vinkla\Hashids\Facades\Hashids;
11
12
/**
13
 * Class TestsRequestHelperTrait
14
 *
15
 * Tests helper for making HTTP requests.
16
 *
17
 * @author  Mahmoud Zalt  <[email protected]>
18
 */
19
trait TestsRequestHelperTrait
20
{
21
22
    /**
23
     * property to be set on the user test class
24
     *
25
     * @var  string
26
     */
27
    protected $endpoint = '';
28
29
    /**
30
     * property to be set on the user test class
31
     *
32
     * @var  bool
33
     */
34
    protected $auth = true;
35
36
    /**
37
     * property to be set before making a call to override the default class property
38
     *
39
     * @var string
40
     */
41
    protected $overrideEndpoint;
42
43
    /**
44
     * property to be set before making a call to override the default class property
45
     *
46
     * @var string
47
     */
48
    protected $overrideAuth;
49
50
    /**
51
     * the $endpoint property will be extracted to $endpointVerb and $endpointUrl after parsing
52
     *
53
     * @var string
54
     */
55
    private $endpointVerb;
56
57
    /**
58
     * the $endpoint property will be extracted to $endpointVerb and $endpointUrl after parsing
59
     *
60
     * @var string
61
     */
62
    private $endpointUrl;
63
64
    /**
65
     * @param        $this ->endpointUrl
66
     * @param string $this ->endpointVerb
0 ignored issues
show
Bug introduced by
There is no parameter named $this. 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...
67
     * @param array  $data
68
     * @param bool   $protected
0 ignored issues
show
Bug introduced by
There is no parameter named $protected. 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...
69
     * @param array  $headers
70
     *
71
     * @return  mixed
72
     * @throws \Symfony\Component\Debug\Exception\UndefinedMethodException
73
     */
74
    public function makeCall(array $data = [], array $headers = [])
75
    {
76
        $this->parseEndpoint();
77
78
        $headers = $this->injectAccessToken($headers);
79
80
        return $this->getResponse($this->httpRequest($data, $headers));
81
    }
82
83
    /**
84
     * Inject the ID in the Endpoint URI before making the call by
85
     * overriding the `$this->endpoint` property
86
     *
87
     * Example: you give it ('users/{id}/stores', 100) it returns 'users/100/stores'
88
     *
89
     * @param        $this ->url
90
     * @param        $id
91
     * @param bool   $skipEncoding
92
     * @param string $replace
93
     *
94
     * @return  mixed
95
     */
96
    public function injectId($id, $skipEncoding = false, $replace = '{id}')
97
    {
98
        // In case Hash ID is enabled it will encode the ID first
99
        $id = $this->hashEndpointId($id, $skipEncoding);
100
        $this->endpoint = str_replace($replace, $id, $this->endpoint);
101
102
        return $this;
103
    }
104
105
    /**
106
     * Override the default class endpoint property before making the call
107
     *
108
     * to be used as follow: $this->endpoint('verb@url')->makeCall($data);
109
     *
110
     * @param $endpoint
111
     *
112
     * @return  $this
113
     */
114
    public function endpoint($endpoint)
115
    {
116
        $this->overrideEndpoint = $endpoint;
117
118
        return $this;
119
    }
120
121
    /**
122
     * Override the default class auth property before making the call
123
     *
124
     * to be used as follow: $this->auth('false')->makeCall($data);
125
     *
126
     * @param bool $auth
127
     *
128
     * @return  $this
129
     */
130
    public function auth(bool $auth)
131
    {
132
        $this->overrideAuth = $auth;
0 ignored issues
show
Documentation Bug introduced by
The property $overrideAuth was declared of type string, but $auth is of type boolean. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
133
134
        return $this;
135
    }
136
137
    /**
138
     * @return  string
139
     */
140
    public function getEndpoint()
141
    {
142
        return !is_null($this->overrideEndpoint) ? $this->overrideEndpoint : $this->endpoint;
143
    }
144
145
    /**
146
     * @return  bool
147
     */
148
    public function getAuth()
149
    {
150
        return !is_null($this->overrideAuth) ? $this->overrideAuth : $this->auth;
151
    }
152
153
    /**
154
     * @param array $data
155
     * @param array $headers
156
     *
157
     * @return  mixed
158
     */
159
    private function httpRequest(array $data = [], array $headers = [])
160
    {
161
        $verb = $this->endpointVerb;
162
        $url = $this->endpointUrl;
163
164
        switch ($verb) {
165
            case 'post':
166
            case 'put':
167
            case 'patch':
168
            case 'delete':
169
                $httpResponse = $this->{$verb}($url, $data, $headers);
170
                break;
171
            case 'get':
172
                $url = $this->dataArrayToQueryParam($data, $url);
173
                $httpResponse = $this->get($url, $headers);
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...
174
                break;
175
            case 'json:post':
176
                $httpResponse = $this->json('post', $url, $data, $headers);
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...
177
                break;
178
            default:
179
                throw new UndefinedMethodException('Undefined HTTP Verb (' . $verb . ').');
180
        }
181
182
        return $httpResponse;
183
    }
184
185
    /**
186
     * @param $responseContent
187
     *
188
     * @return  mixed
189
     */
190
    private function getResponse($responseContent)
191
    {
192
        return $responseContent->response;
193
    }
194
195
    /**
196
     * Attach Authorization Bearer Token to the request headers
197
     * if it doesn't exist already and the authentication is required
198
     * for the endpoint `$this->auth = true`.
199
     *
200
     * @param $headers
201
     *
202
     * @return  mixed
203
     */
204
    private function injectAccessToken(array $headers = [])
205
    {
206
        // if endpoint is protected (requires token to access it's functionality)
207
        if ($this->getAuth() && !$this->headersContainAuthorization($headers)) {
208
            // append the token to the header
209
            $headers['Authorization'] = 'Bearer ' . $this->getTestingUser()->token;
0 ignored issues
show
Bug introduced by
It seems like getTestingUser() 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
        }
211
212
        return $headers;
213
    }
214
215
    /**
216
     * just check if headers array has an `Authorization` as key.
217
     *
218
     * @param $headers
219
     *
220
     * @return  bool
221
     */
222
    private function headersContainAuthorization($headers)
223
    {
224
        return array_has($headers, 'Authorization');
225
    }
226
227
    /**
228
     * @param $data
229
     * @param $endpointUrl
230
     *
231
     * @return  null|string
232
     */
233
    private function dataArrayToQueryParam($data, $url)
234
    {
235
        return $data ? $url . '?' . http_build_query($data) : $url;
236
    }
237
238
239
    /**
240
     * @param      $id
241
     * @param bool $skipEncoding
242
     *
243
     * @return  mixed
244
     */
245
    private function hashEndpointId($id, $skipEncoding = false)
246
    {
247
        return (Config::get('hello.hash-id') && !$skipEncoding) ? Hashids::encode($id) : $id;
248
    }
249
250
    /**
251
     * read `$this->endpoint` property (`verb@url`) and get `$this->endpointVerb` & `$this->endpointUrl`
252
     */
253
    private function parseEndpoint()
254
    {
255
        $this->validateEndpointExist();
256
257
        $separator = '@';
258
259
        $this->validateEndpointFormat($separator);
260
261
        // convert the string to array
262
        $asArray = explode($separator, $this->getEndpoint(), 2);
263
264
        // get the verb and url values from the array
265
        extract(array_combine(['verb', 'url'], $asArray));
0 ignored issues
show
Bug introduced by
array_combine(array('verb', 'url'), $asArray) cannot be passed to extract() as the parameter $var_array expects a reference.
Loading history...
266
267
        /** @var TYPE_NAME $verb */
268
        $this->endpointVerb = $verb;
0 ignored issues
show
Documentation Bug introduced by
It seems like $verb of type object<App\Ship\Features\Tests\PhpUnit\TYPE_NAME> is incompatible with the declared type string of property $endpointVerb.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
269
        /** @var TYPE_NAME $url */
270
        $this->endpointUrl = $url;
0 ignored issues
show
Documentation Bug introduced by
It seems like $url of type object<App\Ship\Features\Tests\PhpUnit\TYPE_NAME> is incompatible with the declared type string of property $endpointUrl.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
271
    }
272
273
    /**
274
     * @void
275
     */
276
    private function validateEndpointExist()
277
    {
278
        if (!$this->getEndpoint()) {
279
            throw new MissingTestEndpointException();
280
        }
281
    }
282
283
    /**
284
     * @param $separator
285
     */
286
    private function validateEndpointFormat($separator)
287
    {
288
        // check if string contains the separator
289
        if (!strpos($this->getEndpoint(), $separator)) {
290
            throw new WrongEndpointFormatException();
291
        }
292
    }
293
}
294