Completed
Push — master ( 696c56...a8c737 )
by André
259:23 queued 232:42
created

TestCase::createHttpRequest()   B

Complexity

Conditions 4
Paths 2

Size

Total Lines 28
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 20
nc 2
nop 6
dl 0
loc 28
rs 8.5806
c 0
b 0
f 0
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