1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of the Behat WebApiExtension. |
5
|
|
|
* |
6
|
|
|
* (c) Konstantin Kudryashov <[email protected]> |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
9
|
|
|
* file that was distributed with this source code. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace Behat\WebApiExtension\Context; |
13
|
|
|
|
14
|
|
|
use Behat\Gherkin\Node\PyStringNode; |
15
|
|
|
use Behat\Gherkin\Node\TableNode; |
16
|
|
|
use PHPUnit\Framework\Assert; |
17
|
|
|
use Psr\Http\Client\ClientExceptionInterface as PsrClientExceptionInterface; |
18
|
|
|
use Psr\Http\Message\RequestInterface; |
19
|
|
|
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface as SymfonyClientExceptionInterface; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* Provides web API description definitions. |
23
|
|
|
* |
24
|
|
|
* @author Konstantin Kudryashov <[email protected]> |
25
|
|
|
* @author Keyclic team <[email protected]> |
26
|
|
|
*/ |
27
|
|
|
class WebApiContext extends ApiClientContext implements ApiClientContextInterface |
28
|
|
|
{ |
29
|
|
|
/** |
30
|
|
|
* Adds Basic Authentication header to next request. |
31
|
|
|
* |
32
|
|
|
* @param string $username |
33
|
|
|
* @param string $password |
34
|
|
|
* |
35
|
|
|
* @Given /^I am basic authenticating as "([^"]*)" with "([^"]*)" password$/ |
36
|
|
|
*/ |
37
|
|
|
public function iAmBasicAuthenticatingAs($username, $password): void |
38
|
|
|
{ |
39
|
|
|
$authorization = base64_encode($username.':'.$password); |
40
|
|
|
|
41
|
|
|
$this->removeHeader('Authorization'); |
42
|
|
|
$this->addHeader('Authorization', 'Basic '.$authorization); |
43
|
|
|
} |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* Sets a HTTP Header. |
47
|
|
|
* |
48
|
|
|
* @param string $name header name |
49
|
|
|
* @param string $value header value |
50
|
|
|
* |
51
|
|
|
* @Given /^I set header "([^"]*)" with value "([^"]*)"$/ |
52
|
|
|
*/ |
53
|
|
|
public function iSetHeaderWithValue($name, $value): void |
54
|
|
|
{ |
55
|
|
|
$this->addHeader($name, $value); |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* Sends HTTP request to specific relative URL. |
60
|
|
|
* |
61
|
|
|
* @param string $method request method |
62
|
|
|
* @param string $url relative url |
63
|
|
|
* |
64
|
|
|
* @throws PsrClientExceptionInterface |
65
|
|
|
* @throws SymfonyClientExceptionInterface |
66
|
|
|
* |
67
|
|
|
* @When /^(?:I )?send a ([A-Z]+) request to "([^"]+)"$/ |
68
|
|
|
*/ |
69
|
|
|
public function iSendARequest($method, $url): void |
70
|
|
|
{ |
71
|
|
|
$url = $this->prepareUrl($url); |
72
|
|
|
|
73
|
|
|
$this->sendRequest($method, $url, $this->getHeaders()); |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* Sends HTTP request to specific URL with field values from Table. |
78
|
|
|
* |
79
|
|
|
* @param string $method request method |
80
|
|
|
* @param string $url relative url |
81
|
|
|
* @param TableNode $values table of post values |
82
|
|
|
* |
83
|
|
|
* @throws PsrClientExceptionInterface |
84
|
|
|
* @throws SymfonyClientExceptionInterface |
85
|
|
|
* |
86
|
|
|
* @When /^(?:I )?send a ([A-Z]+) request to "([^"]+)" with values:$/ |
87
|
|
|
*/ |
88
|
|
|
public function iSendARequestWithValues($method, $url, TableNode $values): void |
89
|
|
|
{ |
90
|
|
|
$url = $this->prepareUrl($url); |
91
|
|
|
|
92
|
|
|
$body = array_map(function ($value) { |
93
|
|
|
return $this->replacePlaceHolder($value); |
94
|
|
|
}, $values->getRowsHash()); |
95
|
|
|
|
96
|
|
|
$body = json_encode($body); |
97
|
|
|
|
98
|
|
|
$this->sendRequest($method, $url, $this->getHeaders(), $body); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Sends HTTP request to specific URL with raw body from PyString. |
103
|
|
|
* |
104
|
|
|
* @param string $method request method |
105
|
|
|
* @param string $url relative url |
106
|
|
|
* @param PyStringNode $body request body |
107
|
|
|
* |
108
|
|
|
* @throws PsrClientExceptionInterface |
109
|
|
|
* @throws SymfonyClientExceptionInterface |
110
|
|
|
* |
111
|
|
|
* @When /^(?:I )?send a ([A-Z]+) request to "([^"]+)" with body:$/ |
112
|
|
|
*/ |
113
|
|
|
public function iSendARequestWithBody($method, $url, PyStringNode $body): void |
114
|
|
|
{ |
115
|
|
|
$url = $this->prepareUrl($url); |
116
|
|
|
$body = $this->replacePlaceHolder(trim($body)); |
117
|
|
|
|
118
|
|
|
$this->sendRequest($method, $url, $this->getHeaders(), $body); |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* Sends HTTP request to specific URL with form data from PyString. |
123
|
|
|
* |
124
|
|
|
* @param string $method request method |
125
|
|
|
* @param string $url relative url |
126
|
|
|
* @param PyStringNode $body request body |
127
|
|
|
* |
128
|
|
|
* @throws PsrClientExceptionInterface |
129
|
|
|
* @throws SymfonyClientExceptionInterface |
130
|
|
|
* |
131
|
|
|
* @When /^(?:I )?send a ([A-Z]+) request to "([^"]+)" with form data:$/ |
132
|
|
|
*/ |
133
|
|
|
public function iSendARequestWithFormData($method, $url, PyStringNode $body): void |
134
|
|
|
{ |
135
|
|
|
$url = $this->prepareUrl($url); |
136
|
|
|
$body = $this->replacePlaceHolder(trim($body)); |
137
|
|
|
|
138
|
|
|
$fields = []; |
139
|
|
|
parse_str(implode('&', explode("\n", $body)), $fields); |
140
|
|
|
$body = http_build_query($fields, null, '&'); |
141
|
|
|
|
142
|
|
|
$this->addHeader('Content-Type', 'application/x-www-form-urlencoded'); |
143
|
|
|
|
144
|
|
|
$this->sendRequest($method, $url, $this->getHeaders(), $body); |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* Checks that response has specific status code. |
149
|
|
|
* |
150
|
|
|
* @param string $code status code |
151
|
|
|
* |
152
|
|
|
* @Then /^(?:the )?response code should be (\d+)$/ |
153
|
|
|
*/ |
154
|
|
|
public function theResponseCodeShouldBe($code): void |
155
|
|
|
{ |
156
|
|
|
$expected = intval($code); |
157
|
|
|
$statusCode = intval($this->getResponse()->getStatusCode()); |
158
|
|
|
|
159
|
|
|
Assert::assertSame($expected, $statusCode); |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* Checks that response body contains specific text. |
164
|
|
|
* |
165
|
|
|
* @param string $text |
166
|
|
|
* |
167
|
|
|
* @Then /^(?:the )?response should contain "([^"]*)"$/ |
168
|
|
|
*/ |
169
|
|
View Code Duplication |
public function theResponseShouldContain($text): void |
|
|
|
|
170
|
|
|
{ |
171
|
|
|
$expectedRegexp = '/'.preg_quote($text).'/i'; |
172
|
|
|
$bodyResponse = (string) $this->getResponse()->getBody(); |
173
|
|
|
|
174
|
|
|
Assert::assertRegExp($expectedRegexp, $bodyResponse); |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
/** |
178
|
|
|
* Checks that response body doesn't contains specific text. |
179
|
|
|
* |
180
|
|
|
* @param string $text |
181
|
|
|
* |
182
|
|
|
* @Then /^(?:the )?response should not contain "([^"]*)"$/ |
183
|
|
|
*/ |
184
|
|
View Code Duplication |
public function theResponseShouldNotContain($text): void |
|
|
|
|
185
|
|
|
{ |
186
|
|
|
$expectedRegexp = '/'.preg_quote($text).'/'; |
187
|
|
|
$bodyResponse = (string) $this->getResponse()->getBody(); |
188
|
|
|
|
189
|
|
|
Assert::assertNotRegExp($expectedRegexp, $bodyResponse); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* Checks that response body contains JSON from PyString. |
194
|
|
|
* |
195
|
|
|
* Do not check that the response body /only/ contains the JSON from PyString, |
196
|
|
|
* |
197
|
|
|
* @param PyStringNode $jsonString |
198
|
|
|
* |
199
|
|
|
* @throws \RuntimeException |
200
|
|
|
* |
201
|
|
|
* @Then /^(?:the )?response should contain json:$/ |
202
|
|
|
*/ |
203
|
|
|
public function theResponseShouldContainJson(PyStringNode $jsonString): void |
204
|
|
|
{ |
205
|
|
|
$rawJsonString = $this->replacePlaceHolder($jsonString->getRaw()); |
206
|
|
|
|
207
|
|
|
$expected = json_decode($rawJsonString, true); |
208
|
|
|
$actual = json_decode((string) $this->getResponse()->getBody(), true); |
209
|
|
|
|
210
|
|
|
Assert::assertNotNull($expected, 'Can not convert expected to json:\n'.$rawJsonString); |
211
|
|
|
Assert::assertNotNull($actual, 'Can not convert body response to json:\n'.$this->getResponse()->getBody()); |
212
|
|
|
|
213
|
|
|
Assert::assertGreaterThanOrEqual(count($expected), count($actual)); |
214
|
|
|
|
215
|
|
|
foreach ($expected as $key => $needle) { |
216
|
|
|
Assert::assertArrayHasKey($key, $actual); |
217
|
|
|
Assert::assertEquals($expected[$key], $actual[$key]); |
218
|
|
|
} |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
/** |
222
|
|
|
* Check if the response header has a specific value. |
223
|
|
|
* |
224
|
|
|
* @param string $name |
225
|
|
|
* @param string $expected |
226
|
|
|
* |
227
|
|
|
* @Then /^the response "([^"]*)" header should be "([^"]*)"$/ |
228
|
|
|
*/ |
229
|
|
|
public function theResponseHeaderShouldBe($name, $expected): void |
230
|
|
|
{ |
231
|
|
|
$actual = $this->getResponse()->getHeaderLine($name); |
232
|
|
|
Assert::assertEquals($expected, $actual); |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* Prints last response body. |
237
|
|
|
* |
238
|
|
|
* @Then print response |
239
|
|
|
*/ |
240
|
|
|
public function printResponse(): void |
241
|
|
|
{ |
242
|
|
|
$request = ''; |
243
|
|
|
if ($this->getRequest() instanceof RequestInterface) { |
244
|
|
|
$request = sprintf( |
245
|
|
|
'%s %s', |
246
|
|
|
$this->getRequest()->getMethod(), |
247
|
|
|
(string) $this->getRequest()->getUri() |
248
|
|
|
); |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
$response = sprintf( |
252
|
|
|
"%d:\n%s", |
253
|
|
|
$this->getResponse()->getStatusCode(), |
254
|
|
|
(string) $this->getResponse()->getContent(false) |
|
|
|
|
255
|
|
|
); |
256
|
|
|
|
257
|
|
|
echo sprintf('%s => %s', $request, $response); |
258
|
|
|
} |
259
|
|
|
} |
260
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.