Completed
Pull Request — master (#43)
by Melissa
02:00
created

WebApiContext::getRequest()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
/*
4
 * This file is part of the Behat WebApiExtension.
5
 * (c) Konstantin Kudryashov <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Behat\WebApiExtension\Context;
12
13
use Behat\Gherkin\Node\PyStringNode;
14
use Behat\Gherkin\Node\TableNode;
15
use GuzzleHttp\ClientInterface;
16
use GuzzleHttp\Exception\RequestException;
17
use PHPUnit_Framework_Assert as Assertions;
18
19
/**
20
 * Provides web API description definitions.
21
 *
22
 * @author Konstantin Kudryashov <[email protected]>
23
 */
24
class WebApiContext implements ApiClientAwareContext
25
{
26
    /**
27
     * @var string
28
     */
29
    private $authorization;
30
31
    /**
32
     * @var ClientInterface
33
     */
34
    private $client;
35
36
    /**
37
     * @var array
38
     */
39
    private $headers = array();
40
41
    /**
42
     * @var \GuzzleHttp\Message\RequestInterface
43
     */
44
    private $request;
45
46
    /**
47
     * @var \GuzzleHttp\Message\ResponseInterface
48
     */
49
    private $response;
50
51
    private $placeHolders = array();
52
53
    /**
54
     * {@inheritdoc}
55
     */
56
    public function setClient(ClientInterface $client)
57
    {
58
        $this->client = $client;
59
    }
60
61
    /**
62
     * Adds Basic Authentication header to next request.
63
     *
64
     * @param string $username
65
     * @param string $password
66
     *
67
     * @Given /^I am authenticating as "([^"]*)" with "([^"]*)" password$/
68
     */
69
    public function iAmAuthenticatingAs($username, $password)
70
    {
71
        $this->removeHeader('Authorization');
72
        $this->authorization = base64_encode($username . ':' . $password);
73
        $this->addHeader('Authorization', 'Basic ' . $this->authorization);
74
    }
75
76
    /**
77
     * Sets a HTTP Header.
78
     *
79
     * @param string $name  header name
80
     * @param string $value header value
81
     *
82
     * @Given /^I set header "([^"]*)" with value "([^"]*)"$/
83
     */
84
    public function iSetHeaderWithValue($name, $value)
85
    {
86
        $this->addHeader($name, $value);
87
    }
88
89
    /**
90
     * Sends HTTP request to specific relative URL.
91
     *
92
     * @param string $method request method
93
     * @param string $url    relative url
94
     *
95
     * @When /^(?:I )?send a ([A-Z]+) request to "([^"]+)"$/
96
     */
97
    public function iSendARequest($method, $url)
98
    {
99
        $url = $this->prepareUrl($url);
100
        $this->request = $this->getClient()->createRequest($method, $url);
101
        if (!empty($this->headers)) {
102
            $this->request->addHeaders($this->headers);
103
        }
104
105
        $this->sendRequest();
106
    }
107
108
    /**
109
     * Sends HTTP request to specific URL with field values from Table.
110
     *
111
     * @param string    $method request method
112
     * @param string    $url    relative url
113
     * @param TableNode $post   table of post values
114
     *
115
     * @When /^(?:I )?send a ([A-Z]+) request to "([^"]+)" with values:$/
116
     */
117
    public function iSendARequestWithValues($method, $url, TableNode $post)
118
    {
119
        $url = $this->prepareUrl($url);
120
        $fields = array();
121
122
        foreach ($post->getRowsHash() as $key => $val) {
123
            $fields[$key] = $this->replacePlaceHolder($val);
124
        }
125
126
        $bodyOption = array(
127
            'body' => json_encode($fields),
128
        );
129
        $this->request = $this->getClient()->createRequest($method, $url, $bodyOption);
130
        if (!empty($this->headers)) {
131
            $this->request->addHeaders($this->headers);
132
        }
133
134
        $this->sendRequest();
135
    }
136
137
    /**
138
     * Sends HTTP request to specific URL with raw body from PyString.
139
     *
140
     * @param string       $method request method
141
     * @param string       $url    relative url
142
     * @param PyStringNode $string request body
143
     *
144
     * @When /^(?:I )?send a ([A-Z]+) request to "([^"]+)" with body:$/
145
     */
146
    public function iSendARequestWithBody($method, $url, PyStringNode $string)
147
    {
148
        $url = $this->prepareUrl($url);
149
        $string = $this->replacePlaceHolder(trim($string));
150
151
        $this->request = $this->getClient()->createRequest(
152
            $method,
153
            $url,
154
            array(
155
                'headers' => $this->getHeaders(),
156
                'body' => $string,
157
            )
158
        );
159
        $this->sendRequest();
160
    }
161
162
    /**
163
     * Sends HTTP request to specific URL with form data from PyString.
164
     *
165
     * @param string       $method request method
166
     * @param string       $url    relative url
167
     * @param PyStringNode $body   request body
168
     *
169
     * @When /^(?:I )?send a ([A-Z]+) request to "([^"]+)" with form data:$/
170
     */
171
    public function iSendARequestWithFormData($method, $url, PyStringNode $body)
172
    {
173
        $url = $this->prepareUrl($url);
174
        $body = $this->replacePlaceHolder(trim($body));
175
176
        $fields = array();
177
        parse_str(implode('&', explode("\n", $body)), $fields);
178
        $this->request = $this->getClient()->createRequest($method, $url);
179
        /** @var \GuzzleHttp\Post\PostBodyInterface $requestBody */
180
        $requestBody = $this->request->getBody();
181
        foreach ($fields as $key => $value) {
0 ignored issues
show
Bug introduced by
The expression $fields of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
182
            $requestBody->setField($key, $value);
183
        }
184
185
        $this->sendRequest();
186
    }
187
188
    /**
189
     * Checks that response has specific status code.
190
     *
191
     * @param string $code status code
192
     *
193
     * @Then /^(?:the )?response code should be (\d+)$/
194
     */
195
    public function theResponseCodeShouldBe($code)
196
    {
197
        $expected = intval($code);
198
        $actual = intval($this->response->getStatusCode());
199
        Assertions::assertSame($expected, $actual);
200
    }
201
202
    /**
203
     * Checks that response body contains specific text.
204
     *
205
     * @param string $text
206
     *
207
     * @Then /^(?:the )?response should contain "([^"]*)"$/
208
     */
209
    public function theResponseShouldContain($text)
210
    {
211
        $expectedRegexp = '/' . preg_quote($text) . '/i';
212
        $actual = (string) $this->response->getBody();
213
        Assertions::assertRegExp($expectedRegexp, $actual);
214
    }
215
216
    /**
217
     * Checks that response body doesn't contains specific text.
218
     *
219
     * @param string $text
220
     *
221
     * @Then /^(?:the )?response should not contain "([^"]*)"$/
222
     */
223
    public function theResponseShouldNotContain($text)
224
    {
225
        $expectedRegexp = '/' . preg_quote($text) . '/';
226
        $actual = (string) $this->response->getBody();
227
        Assertions::assertNotRegExp($expectedRegexp, $actual);
228
    }
229
230
    /**
231
     * Checks that response body contains JSON from PyString.
232
     *
233
     * Do not check that the response body /only/ contains the JSON from PyString,
234
     *
235
     * @param PyStringNode $jsonString
236
     *
237
     * @throws \RuntimeException
238
     *
239
     * @Then /^(?:the )?response should contain json:$/
240
     */
241
    public function theResponseShouldContainJson(PyStringNode $jsonString)
242
    {
243
        $etalon = json_decode($this->replacePlaceHolder($jsonString->getRaw()), true);
244
        $actual = $this->response->json();
245
246
        if (null === $etalon) {
247
            throw new \RuntimeException(
248
                "Can not convert etalon to json:\n" . $this->replacePlaceHolder($jsonString->getRaw())
249
            );
250
        }
251
252
        Assertions::assertGreaterThanOrEqual(count($etalon), count($actual));
253
        foreach ($etalon as $key => $needle) {
254
            Assertions::assertArrayHasKey($key, $actual);
255
            Assertions::assertEquals($etalon[$key], $actual[$key]);
256
        }
257
    }
258
259
    /**
260
     * Prints last response body.
261
     *
262
     * @Then print response
263
     */
264
    public function printResponse()
265
    {
266
        $request = $this->request;
267
        $response = $this->response;
268
269
        echo sprintf(
270
            "%s %s => %d:\n%s",
271
            $request->getMethod(),
272
            $request->getUrl(),
273
            $response->getStatusCode(),
274
            $response->getBody()
275
        );
276
    }
277
278
    /**
279
     * Prepare URL by replacing placeholders and trimming slashes.
280
     *
281
     * @param string $url
282
     *
283
     * @return string
284
     */
285
    private function prepareUrl($url)
286
    {
287
        return ltrim($this->replacePlaceHolder($url), '/');
288
    }
289
290
    /**
291
     * Sets place holder for replacement.
292
     *
293
     * you can specify placeholders, which will
294
     * be replaced in URL, request or response body.
295
     *
296
     * @param string $key   token name
297
     * @param string $value replace value
298
     */
299
    public function setPlaceHolder($key, $value)
300
    {
301
        $this->placeHolders[$key] = $value;
302
    }
303
304
    /**
305
     * Replaces placeholders in provided text.
306
     *
307
     * @param string $string
308
     *
309
     * @return string
310
     */
311
    protected function replacePlaceHolder($string)
312
    {
313
        foreach ($this->placeHolders as $key => $val) {
314
            $string = str_replace($key, $val, $string);
315
        }
316
317
        return $string;
318
    }
319
320
    /**
321
     * Returns headers, that will be used to send requests.
322
     *
323
     * @return array
324
     */
325
    protected function getHeaders()
326
    {
327
        return $this->headers;
328
    }
329
330
    /**
331
     * Adds header
332
     *
333
     * @param string $name
334
     * @param string $value
335
     */
336
    protected function addHeader($name, $value)
337
    {
338
        if (isset($this->headers[$name])) {
339
            if (!is_array($this->headers[$name])) {
340
                $this->headers[$name] = array($this->headers[$name]);
341
            }
342
343
            $this->headers[$name][] = $value;
344
        } else {
345
            $this->headers[$name] = $value;
346
        }
347
    }
348
349
    /**
350
     * Removes a header identified by $headerName
351
     *
352
     * @param string $headerName
353
     */
354
    protected function removeHeader($headerName)
355
    {
356
        if (array_key_exists($headerName, $this->headers)) {
357
            unset($this->headers[$headerName]);
358
        }
359
    }
360
361
    private function sendRequest()
362
    {
363
        try {
364
            $this->response = $this->getClient()->send($this->request);
365
        } catch (RequestException $e) {
366
            $this->response = $e->getResponse();
367
368
            if (null === $this->response) {
369
                throw $e;
370
            }
371
        }
372
    }
373
374
    private function getClient()
375
    {
376
        if (null === $this->client) {
377
            throw new \RuntimeException('Client has not been set in WebApiContext');
378
        }
379
380
        return $this->client;
381
    }
382
383
    /**
384
     * @return \GuzzleHttp\Message\RequestInterface
385
     */
386
    public function getRequest()
387
    {
388
        return $this->request;
389
    }
390
391
    /**
392
     * @return \GuzzleHttp\Message\ResponseInterface
393
     */
394
    public function getResponse()
395
    {
396
        return $this->response;
397
    }
398
}
399