1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* File containing the Functional\TestCase class. |
5
|
|
|
* |
6
|
|
|
* @copyright Copyright (C) eZ Systems AS. All rights reserved. |
7
|
|
|
* @license For full copyright and license information view LICENSE file distributed with this source code. |
8
|
|
|
*/ |
9
|
|
|
namespace eZ\Bundle\EzPublishRestBundle\Tests\Functional; |
10
|
|
|
|
11
|
|
|
use Buzz\Client\Curl; |
12
|
|
|
use Nyholm\Psr7\Request as HttpRequest; |
13
|
|
|
use PHPUnit\Framework\TestCase as BaseTestCase; |
14
|
|
|
use PHPUnit\Framework\ExpectationFailedException; |
15
|
|
|
use Psr\Http\Message\RequestInterface; |
16
|
|
|
use Psr\Http\Message\ResponseInterface; |
17
|
|
|
|
18
|
|
|
class TestCase extends BaseTestCase |
19
|
|
|
{ |
20
|
|
|
const X_HTTP_METHOD_OVERRIDE_MAP = [ |
21
|
|
|
'PUBLISH' => 'POST', |
22
|
|
|
'MOVE' => 'POST', |
23
|
|
|
'PATCH' => 'PATCH', |
24
|
|
|
'COPY' => 'POST', |
25
|
|
|
]; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* @var \Buzz\Client\BuzzClientInterface |
29
|
|
|
*/ |
30
|
|
|
private $httpClient; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @var string |
34
|
|
|
*/ |
35
|
|
|
private $httpHost; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* @var string |
39
|
|
|
* Basic auth login:password |
40
|
|
|
*/ |
41
|
|
|
private $httpAuth; |
42
|
|
|
|
43
|
|
|
protected static $testSuffix; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @var array |
47
|
|
|
*/ |
48
|
|
|
private $headers = []; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* The username to use for login. |
52
|
|
|
* @var string |
53
|
|
|
*/ |
54
|
|
|
private $loginUsername; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* The password to use for login. |
58
|
|
|
* @var string |
59
|
|
|
*/ |
60
|
|
|
private $loginPassword; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* If true, a login request is automatically done during setUp(). |
64
|
|
|
* @var bool |
65
|
|
|
*/ |
66
|
|
|
protected $autoLogin = true; |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* List of REST contentId (/content/objects/12345) created by tests. |
70
|
|
|
* |
71
|
|
|
* @var array |
72
|
|
|
*/ |
73
|
|
|
private static $createdContent = array(); |
74
|
|
|
|
75
|
|
|
protected function setUp() |
76
|
|
|
{ |
77
|
|
|
parent::setUp(); |
78
|
|
|
|
79
|
|
|
$this->httpHost = getenv('EZP_TEST_REST_HOST') ?: 'localhost'; |
80
|
|
|
$this->httpAuth = getenv('EZP_TEST_REST_AUTH') ?: 'admin:publish'; |
81
|
|
|
list($this->loginUsername, $this->loginPassword) = explode(':', $this->httpAuth); |
82
|
|
|
|
83
|
|
|
$this->httpClient = new Curl([ |
84
|
|
|
'verify' => false, |
85
|
|
|
'timeout' => 90, |
86
|
|
|
'allow_redirects' => false, |
87
|
|
|
]); |
88
|
|
|
|
89
|
|
|
if ($this->autoLogin) { |
90
|
|
|
$session = $this->login(); |
91
|
|
|
$this->headers['Cookie'] = sprintf('%s=%s', $session->name, $session->identifier); |
92
|
|
|
$this->headers['X-CSRF-Token'] = $session->csrfToken; |
93
|
|
|
} |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* @param \Psr\Http\Message\RequestInterface $request |
98
|
|
|
* |
99
|
|
|
* @return \Psr\Http\Message\ResponseInterface |
100
|
|
|
* |
101
|
|
|
* @throws \Psr\Http\Client\ClientException |
102
|
|
|
*/ |
103
|
|
|
public function sendHttpRequest(RequestInterface $request): ResponseInterface |
104
|
|
|
{ |
105
|
|
|
return $this->httpClient->sendRequest($request); |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
protected function getHttpHost() |
109
|
|
|
{ |
110
|
|
|
return $this->httpHost; |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
protected function getLoginUsername() |
114
|
|
|
{ |
115
|
|
|
return $this->loginUsername; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
protected function getLoginPassword() |
119
|
|
|
{ |
120
|
|
|
return $this->loginPassword; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* Get base URI for \Buzz\Browser based requests. |
125
|
|
|
* |
126
|
|
|
* @return string |
127
|
|
|
*/ |
128
|
|
|
protected function getBaseURI() |
129
|
|
|
{ |
130
|
|
|
return "http://{$this->httpHost}"; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* @param string $method |
135
|
|
|
* @param string $uri |
136
|
|
|
* @param string $contentType |
137
|
|
|
* @param string $acceptType |
138
|
|
|
* @param string $body |
139
|
|
|
* |
140
|
|
|
* @param array $extraHeaders [key => value] array of extra headers |
141
|
|
|
* |
142
|
|
|
* @return \Psr\Http\Message\RequestInterface |
143
|
|
|
*/ |
144
|
|
|
public function createHttpRequest( |
145
|
|
|
string $method, |
146
|
|
|
string $uri, |
147
|
|
|
string $contentType = '', |
148
|
|
|
string $acceptType = '', |
149
|
|
|
string $body = '', |
150
|
|
|
array $extraHeaders = [] |
151
|
|
|
): RequestInterface { |
152
|
|
|
$headers = array_merge( |
153
|
|
|
$method === 'POST' && $uri === '/api/ezp/v2/user/sessions' ? [] : $this->headers, |
154
|
|
|
[ |
155
|
|
|
'Content-Type' => $this->generateMediaTypeString($contentType), |
156
|
|
|
'Accept' => $this->generateMediaTypeString($acceptType), |
157
|
|
|
] |
158
|
|
|
); |
159
|
|
|
|
160
|
|
|
if (isset(static::X_HTTP_METHOD_OVERRIDE_MAP[$method])) { |
161
|
|
|
$headers['X-HTTP-Method-Override'] = $method; |
162
|
|
|
$method = static::X_HTTP_METHOD_OVERRIDE_MAP[$method]; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
return new HttpRequest( |
166
|
|
|
$method, |
167
|
|
|
$this->httpHost . $uri, |
168
|
|
|
array_merge($headers, $extraHeaders), |
169
|
|
|
$body |
170
|
|
|
); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
protected function assertHttpResponseCodeEquals(ResponseInterface $response, $expected) |
174
|
|
|
{ |
175
|
|
|
$responseCode = $response->getStatusCode(); |
176
|
|
|
try { |
177
|
|
|
self::assertEquals($expected, $responseCode); |
178
|
|
|
} catch (ExpectationFailedException $e) { |
|
|
|
|
179
|
|
|
$errorMessageString = ''; |
180
|
|
|
$contentTypeHeader = $response->hasHeader('Content-Type') |
181
|
|
|
? $response->getHeader('Content-Type')[0] |
182
|
|
|
: ''; |
183
|
|
|
if (strpos($contentTypeHeader, 'application/vnd.ez.api.ErrorMessage+xml') !== false) { |
184
|
|
|
$body = \simplexml_load_string($response->getBody()); |
185
|
|
|
$errorMessageString = $this->getHttpResponseCodeErrorMessage($body); |
186
|
|
|
} elseif (strpos($contentTypeHeader, 'application/vnd.ez.api.ErrorMessage+json') !== false) { |
187
|
|
|
$body = json_decode($response->getBody()); |
188
|
|
|
$errorMessageString = $this->getHttpResponseCodeErrorMessage($body->ErrorMessage); |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
self::assertEquals($expected, $responseCode, $errorMessageString); |
192
|
|
|
} |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
private function getHttpResponseCodeErrorMessage($errorMessage) |
196
|
|
|
{ |
197
|
|
|
$errorMessageString = <<< EOF |
198
|
|
|
Server error message ({$errorMessage->errorCode}): {$errorMessage->errorMessage} |
199
|
|
|
|
200
|
|
|
{$errorMessage->errorDescription} |
201
|
|
|
|
202
|
|
|
EOF; |
203
|
|
|
|
204
|
|
|
// If server is in debug mode it will return file, line and trace. |
205
|
|
|
if (!empty($errorMessage->file)) { |
206
|
|
|
$errorMessageString .= "\nIn {$errorMessage->file}:{$errorMessage->line}\n\n{$errorMessage->trace}"; |
207
|
|
|
} else { |
208
|
|
|
$errorMessageString .= "\nIn \<no trace, debug disabled\>"; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
return $errorMessageString; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
protected function assertHttpResponseHasHeader(ResponseInterface $response, $header, $expectedValue = null) |
215
|
|
|
{ |
216
|
|
|
$headerValue = $response->hasHeader($header) ? $response->getHeader($header)[0] : null; |
217
|
|
|
self::assertNotNull($headerValue, "Failed asserting that response has a {$header} header"); |
218
|
|
|
if ($expectedValue !== null) { |
219
|
|
|
self::assertEquals($expectedValue, $headerValue); |
220
|
|
|
} |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
protected function generateMediaTypeString($typeString) |
224
|
|
|
{ |
225
|
|
|
return "application/vnd.ez.api.$typeString"; |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
protected function getMediaFromTypeString($typeString) |
229
|
|
|
{ |
230
|
|
|
$prefix = 'application/vnd.ez.api.'; |
231
|
|
|
self::assertStringStartsWith( |
232
|
|
|
$prefix, |
233
|
|
|
$typeString, |
234
|
|
|
"Unknown media: {$typeString}" |
235
|
|
|
); |
236
|
|
|
|
237
|
|
|
return substr($typeString, strlen($prefix)); |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
protected function addCreatedElement($href) |
241
|
|
|
{ |
242
|
|
|
$testCase = $this; |
243
|
|
|
self::$createdContent[$href] = function () use ($href, $testCase) { |
244
|
|
|
$testCase->sendHttpRequest( |
245
|
|
|
$testCase->createHttpRequest('DELETE', $href) |
246
|
|
|
); |
247
|
|
|
}; |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
public static function tearDownAfterClass() |
251
|
|
|
{ |
252
|
|
|
self::clearCreatedElement(self::$createdContent); |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
private static function clearCreatedElement(array $contentArray) |
256
|
|
|
{ |
257
|
|
|
foreach (array_reverse($contentArray) as $href => $callback) { |
258
|
|
|
$callback(); |
259
|
|
|
} |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
/** |
263
|
|
|
* @param string $parentLocationId The REST id of the parent location |
264
|
|
|
* |
265
|
|
|
* @return array created Content, as an array |
266
|
|
|
*/ |
267
|
|
|
protected function createFolder($string, $parentLocationId) |
268
|
|
|
{ |
269
|
|
|
$string = $this->addTestSuffix($string); |
270
|
|
|
$remoteId = md5(uniqid($string, true)); |
271
|
|
|
$xml = <<< XML |
272
|
|
|
<?xml version="1.0" encoding="UTF-8"?> |
273
|
|
|
<ContentCreate> |
274
|
|
|
<ContentType href="/api/ezp/v2/content/types/1" /> |
275
|
|
|
<mainLanguageCode>eng-GB</mainLanguageCode> |
276
|
|
|
<LocationCreate> |
277
|
|
|
<ParentLocation href="{$parentLocationId}" /> |
278
|
|
|
<priority>0</priority> |
279
|
|
|
<hidden>false</hidden> |
280
|
|
|
<sortField>PATH</sortField> |
281
|
|
|
<sortOrder>ASC</sortOrder> |
282
|
|
|
</LocationCreate> |
283
|
|
|
<Section href="/api/ezp/v2/content/sections/1" /> |
284
|
|
|
<alwaysAvailable>true</alwaysAvailable> |
285
|
|
|
<remoteId>{$remoteId}</remoteId> |
286
|
|
|
<User href="/api/ezp/v2/user/users/14" /> |
287
|
|
|
<modificationDate>2012-09-30T12:30:00</modificationDate> |
288
|
|
|
<fields> |
289
|
|
|
<field> |
290
|
|
|
<fieldDefinitionIdentifier>name</fieldDefinitionIdentifier> |
291
|
|
|
<languageCode>eng-GB</languageCode> |
292
|
|
|
<fieldValue>{$string}</fieldValue> |
293
|
|
|
</field> |
294
|
|
|
</fields> |
295
|
|
|
</ContentCreate> |
296
|
|
|
XML; |
297
|
|
|
|
298
|
|
|
return $this->createContent($xml); |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
/** |
302
|
|
|
* @param $xml |
303
|
|
|
* |
304
|
|
|
* @return array Content key of the Content struct array |
305
|
|
|
*/ |
306
|
|
|
protected function createContent($xml) |
307
|
|
|
{ |
308
|
|
|
$request = $this->createHttpRequest( |
309
|
|
|
'POST', |
310
|
|
|
'/api/ezp/v2/content/objects', |
311
|
|
|
'ContentCreate+xml', |
312
|
|
|
'Content+json', |
313
|
|
|
$xml |
314
|
|
|
); |
315
|
|
|
$response = $this->sendHttpRequest($request); |
316
|
|
|
|
317
|
|
|
self::assertHttpResponseCodeEquals($response, 201); |
318
|
|
|
|
319
|
|
|
$content = json_decode($response->getBody(), true); |
320
|
|
|
|
321
|
|
|
if (!isset($content['Content']['CurrentVersion']['Version'])) { |
322
|
|
|
self::fail("Incomplete response (no version):\n" . $response->getBody() . "\n"); |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
$response = $this->sendHttpRequest( |
326
|
|
|
$request = $this->createHttpRequest('PUBLISH', $content['Content']['CurrentVersion']['Version']['_href']) |
327
|
|
|
); |
328
|
|
|
|
329
|
|
|
self::assertHttpResponseCodeEquals($response, 204); |
330
|
|
|
|
331
|
|
|
$this->addCreatedElement($content['Content']['_href']); |
332
|
|
|
|
333
|
|
|
return $content['Content']; |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
/** |
337
|
|
|
* @param string $contentHref |
338
|
|
|
* |
339
|
|
|
* @return array |
340
|
|
|
*/ |
341
|
|
View Code Duplication |
protected function getContentLocations($contentHref) |
342
|
|
|
{ |
343
|
|
|
$response = $this->sendHttpRequest( |
344
|
|
|
$this->createHttpRequest('GET', "$contentHref/locations", '', 'LocationList+json') |
345
|
|
|
); |
346
|
|
|
self::assertHttpResponseCodeEquals($response, 200); |
347
|
|
|
$folderLocations = json_decode($response->getBody(), true); |
348
|
|
|
|
349
|
|
|
return $folderLocations; |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
protected function addTestSuffix($string) |
353
|
|
|
{ |
354
|
|
|
if (!isset(self::$testSuffix)) { |
355
|
|
|
self::$testSuffix = uniqid(); |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
return $string . '_' . self::$testSuffix; |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
/** |
362
|
|
|
* Sends a login request to the REST server. |
363
|
|
|
* |
364
|
|
|
* @return \stdClass an object with the name, identifier, csrftoken properties. |
365
|
|
|
*/ |
366
|
|
|
protected function login() |
367
|
|
|
{ |
368
|
|
|
$request = $this->createAuthenticationHttpRequest($this->getLoginUsername(), $this->getLoginPassword()); |
369
|
|
|
$response = $this->sendHttpRequest($request); |
370
|
|
|
self::assertHttpResponseCodeEquals($response, 201); |
371
|
|
|
|
372
|
|
|
return json_decode($response->getBody())->Session; |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
/** |
376
|
|
|
* @param string $login |
377
|
|
|
* @param string $password |
378
|
|
|
* @param array $extraHeaders extra [key => value] headers to be passed with the authentication request. |
379
|
|
|
* |
380
|
|
|
* @return \Psr\Http\Message\RequestInterface |
381
|
|
|
*/ |
382
|
|
|
protected function createAuthenticationHttpRequest(string $login, string $password, array $extraHeaders = []) |
383
|
|
|
{ |
384
|
|
|
return $this->createHttpRequest( |
385
|
|
|
'POST', |
386
|
|
|
'/api/ezp/v2/user/sessions', |
387
|
|
|
'SessionInput+json', |
388
|
|
|
'Session+json', |
389
|
|
|
sprintf('{"SessionInput": {"login": "%s", "password": "%s"}}', $login, $password), |
390
|
|
|
$extraHeaders |
391
|
|
|
); |
392
|
|
|
} |
393
|
|
|
} |
394
|
|
|
|
Scrutinizer analyzes your
composer.json
/composer.lock
file if available to determine the classes, and functions that are defined by your dependencies.It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.