Completed
Push — master ( 1b8ca6...90d75c )
by Mahmoud
03:54
created

TestsRequestHelperTrait::httpRequest()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 25
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 25
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 20
nc 7
nop 2
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
101
        $this->endpoint = str_replace($replace, $id, $this->endpoint);
102
103
        return $this;
104
    }
105
106
    /**
107
     * Override the default class endpoint property before making the call
108
     *
109
     * to be used as follow: $this->endpoint('verb@url')->makeCall($data);
110
     *
111
     * @param $endpoint
112
     *
113
     * @return  $this
114
     */
115
    public function endpoint($endpoint)
116
    {
117
        $this->overrideEndpoint = $endpoint;
118
119
        return $this;
120
    }
121
122
    /**
123
     * Override the default class auth property before making the call
124
     *
125
     * to be used as follow: $this->auth('false')->makeCall($data);
126
     *
127
     * @param bool $auth
128
     *
129
     * @return  $this
130
     */
131
    public function auth(bool $auth)
132
    {
133
        $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...
134
135
        return $this;
136
    }
137
138
    /**
139
     * @return  string
140
     */
141
    public function getEndpoint()
142
    {
143
        return !is_null($this->overrideEndpoint) ? $this->overrideEndpoint : $this->endpoint;
144
    }
145
146
    /**
147
     * @return  bool
148
     */
149
    public function getAuth()
150
    {
151
        return !is_null($this->overrideAuth) ? $this->overrideAuth : $this->auth;
152
    }
153
154
    /**
155
     * @param array $data
156
     * @param array $headers
157
     *
158
     * @return  mixed
159
     */
160
    private function httpRequest(array $data = [], array $headers = [])
161
    {
162
        $verb = $this->endpointVerb;
163
        $url = $this->endpointUrl;
164
165
        switch ($verb) {
166
            case 'post':
167
            case 'put':
168
            case 'patch':
169
            case 'delete':
170
                $httpResponse = $this->{$verb}($url, $data, $headers);
171
                break;
172
            case 'get':
173
                $url = $this->dataArrayToQueryParam($data, $url);
174
                $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...
175
                break;
176
            case 'json:post':
177
                $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...
178
                break;
179
            default:
180
                throw new UndefinedMethodException('Undefined HTTP Verb (' . $verb . ').');
181
        }
182
183
        return $httpResponse;
184
    }
185
186
    /**
187
     * @param $responseObject
188
     *
189
     * @return  mixed
190
     */
191
    private function getResponse($responseObject)
192
    {
193
        return $responseObject->response;
194
    }
195
196
    /**
197
     * Attach Authorization Bearer Token to the request headers
198
     * if it doesn't exist already and the authentication is required
199
     * for the endpoint `$this->auth = true`.
200
     *
201
     * @param $headers
202
     *
203
     * @return  mixed
204
     */
205
    private function injectAccessToken(array $headers = [])
206
    {
207
        // if endpoint is protected (requires token to access it's functionality)
208
        if ($this->getAuth() && !$this->headersContainAuthorization($headers)) {
209
            // append the token to the header
210
            $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...
211
        }
212
213
        return $headers;
214
    }
215
216
    /**
217
     * just check if headers array has an `Authorization` as key.
218
     *
219
     * @param $headers
220
     *
221
     * @return  bool
222
     */
223
    private function headersContainAuthorization($headers)
224
    {
225
        return array_has($headers, 'Authorization');
226
    }
227
228
    /**
229
     * @param $data
230
     * @param $endpointUrl
231
     *
232
     * @return  null|string
233
     */
234
    private function dataArrayToQueryParam($data, $url)
235
    {
236
        return $data ? $url . '?' . http_build_query($data) : $url;
237
    }
238
239
240
    /**
241
     * @param      $id
242
     * @param bool $skipEncoding
243
     *
244
     * @return  mixed
245
     */
246
    private function hashEndpointId($id, $skipEncoding = false)
247
    {
248
        return (Config::get('hello.hash-id') && !$skipEncoding) ? Hashids::encode($id) : $id;
249
    }
250
251
    /**
252
     * read `$this->endpoint` property (`verb@url`) and get `$this->endpointVerb` & `$this->endpointUrl`
253
     */
254
    private function parseEndpoint()
255
    {
256
        $this->validateEndpointExist();
257
258
        $separator = '@';
259
260
        $this->validateEndpointFormat($separator);
261
262
        // convert the string to array
263
        $asArray = explode($separator, $this->getEndpoint(), 2);
264
265
        // get the verb and url values from the array
266
        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...
267
268
        /** @var TYPE_NAME $verb */
269
        $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...
270
        /** @var TYPE_NAME $url */
271
        $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...
272
    }
273
274
    /**
275
     * @void
276
     */
277
    private function validateEndpointExist()
278
    {
279
        if (!$this->getEndpoint()) {
280
            throw new MissingTestEndpointException();
281
        }
282
    }
283
284
    /**
285
     * @param $separator
286
     */
287
    private function validateEndpointFormat($separator)
288
    {
289
        // check if string contains the separator
290
        if (!strpos($this->getEndpoint(), $separator)) {
291
            throw new WrongEndpointFormatException();
292
        }
293
    }
294
}
295