Completed
Pull Request — master (#43)
by Melissa
02:48
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
        $this->assertEtalonKeyValuesEqualsActual($etalon, $actual);
254
    }
255
256
    /**
257
     * Asserts that *only* the keys defined in etalon exist and are equal to the values defined in actual.
258
     *
259
     * @param array $etalon
260
     * @param array $actual
261
     */
262
    private function assertEtalonKeyValuesEqualsActual(array $etalon, array $actual)
263
    {
264
        foreach ($etalon as $key => $needle) {
265
            if(is_array($needle)) {
266
                $this->assertEtalonKeyValuesEqualsActual($etalon[$key], $actual[$key]);
267
268
                continue;
269
            }
270
            Assertions::assertArrayHasKey($key, $actual);
271
            Assertions::assertEquals($etalon[$key], $actual[$key]);
272
        }
273
    }
274
275
    /**
276
     * Checks that JSON response key equals the value specified.
277
     *
278
     * Do not check that the response body /only/ contains the JSON from PyString,
279
     *
280
     * @param string $key
281
     * @param string $value
282
     *
283
     * @Then /^(?:the )?response key "([^"]*)" should equal "([^"]*)"$/
284
     */
285
    public function theResponseKeyShouldEqual($key, $value)
286
    {
287
        $actual = $this->response->json();
288
289
        Assertions::assertArrayHasKey($key, $actual);
290
        Assertions::assertEquals($value, $actual[$key]);
291
    }
292
293
    /**
294
     * Checks that JSON response key matches the value specified.
295
     *
296
     * Do not check that the response body /only/ contains the JSON from PyString,
297
     *
298
     * @param string $key
299
     * @param string $value
300
     *
301
     * @Then /^(?:the )?response key "([^"]*)" should match "([^"]*)"$/
302
     */
303
    public function theResponseKeyShouldMatch($key, $value)
304
    {
305
        $actual = $this->response->json();
306
307
        Assertions::assertArrayHasKey($key, $actual);
308
        Assertions::assertRegExp($value, $actual[$key]);
309
    }
310
311
    /**
312
     * Prints last response body.
313
     *
314
     * @Then print response
315
     */
316
    public function printResponse()
317
    {
318
        $request = $this->request;
319
        $response = $this->response;
320
321
        echo sprintf(
322
            "%s %s => %d:\n%s",
323
            $request->getMethod(),
324
            $request->getUrl(),
325
            $response->getStatusCode(),
326
            $response->getBody()
327
        );
328
    }
329
330
    /**
331
     * Prepare URL by replacing placeholders and trimming slashes.
332
     *
333
     * @param string $url
334
     *
335
     * @return string
336
     */
337
    private function prepareUrl($url)
338
    {
339
        return ltrim($this->replacePlaceHolder($url), '/');
340
    }
341
342
    /**
343
     * Sets place holder for replacement.
344
     *
345
     * you can specify placeholders, which will
346
     * be replaced in URL, request or response body.
347
     *
348
     * @param string $key   token name
349
     * @param string $value replace value
350
     */
351
    public function setPlaceHolder($key, $value)
352
    {
353
        $this->placeHolders[$key] = $value;
354
    }
355
356
    /**
357
     * Replaces placeholders in provided text.
358
     *
359
     * @param string $string
360
     *
361
     * @return string
362
     */
363
    protected function replacePlaceHolder($string)
364
    {
365
        foreach ($this->placeHolders as $key => $val) {
366
            $string = str_replace($key, $val, $string);
367
        }
368
369
        return $string;
370
    }
371
372
    /**
373
     * Returns headers, that will be used to send requests.
374
     *
375
     * @return array
376
     */
377
    protected function getHeaders()
378
    {
379
        return $this->headers;
380
    }
381
382
    /**
383
     * Adds header
384
     *
385
     * @param string $name
386
     * @param string $value
387
     */
388
    protected function addHeader($name, $value)
389
    {
390
        if (isset($this->headers[$name])) {
391
            if (!is_array($this->headers[$name])) {
392
                $this->headers[$name] = array($this->headers[$name]);
393
            }
394
395
            $this->headers[$name][] = $value;
396
        } else {
397
            $this->headers[$name] = $value;
398
        }
399
    }
400
401
    /**
402
     * Removes a header identified by $headerName
403
     *
404
     * @param string $headerName
405
     */
406
    protected function removeHeader($headerName)
407
    {
408
        if (array_key_exists($headerName, $this->headers)) {
409
            unset($this->headers[$headerName]);
410
        }
411
    }
412
413
    private function sendRequest()
414
    {
415
        try {
416
            $this->response = $this->getClient()->send($this->request);
417
        } catch (RequestException $e) {
418
            $this->response = $e->getResponse();
419
420
            if (null === $this->response) {
421
                throw $e;
422
            }
423
        }
424
    }
425
426
    private function getClient()
427
    {
428
        if (null === $this->client) {
429
            throw new \RuntimeException('Client has not been set in WebApiContext');
430
        }
431
432
        return $this->client;
433
    }
434
435
    /**
436
     * @return \GuzzleHttp\Message\RequestInterface
437
     */
438
    public function getRequest()
439
    {
440
        return $this->request;
441
    }
442
443
    /**
444
     * @return \GuzzleHttp\Message\ResponseInterface
445
     */
446
    public function getResponse()
447
    {
448
        return $this->response;
449
    }
450
}
451