Completed
Push — fulltext_indexing_test ( 4cf81d )
by André
33:01
created

EzRest::getResponseError()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 19
rs 8.8571
cc 5
eloc 11
nc 5
nop 1
1
<?php
2
3
/**
4
 * File containing the EzRest for RestContext.
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
 * @version //autogentag//
10
 */
11
namespace eZ\Bundle\EzPublishRestBundle\Features\Context\SubContext;
12
13
use eZ\Publish\Core\REST\Client\Values\ViewInput;
14
use EzSystems\BehatBundle\Helper\ValueObject as ValueObjectHelper;
15
use eZ\Publish\API\Repository\Values\ValueObject;
16
use eZ\Publish\Core\REST\Server\Values\SessionInput;
17
use eZ\Publish\Core\REST\Common\Message;
18
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
19
use PHPUnit_Framework_Assert as Assertion;
20
21
/**
22
 * EzRest is the responsible to have all the specific REST actions of eZ Publish.
23
 */
24
trait EzRest
25
{
26
    /**
27
     * @var string Type of request/response body [JSON/XML]
28
     */
29
    private $restBodyType = 'json';
30
31
    /**
32
     * Since there is a need to prepare an object in several steps it needs to be
33
     * hold until sent to the request body.
34
     *
35
     * @var \eZ\Publish\API\Repository\Values\ValueObject
36
     */
37
    public $requestObject;
38
39
    /**
40
     * Same idea as the $requestObject, since we need to verify it step by step
41
     * it need to be stored (as object) for testing.
42
     *
43
     * @var \eZ\Publish\API\Repository\Values\ValueObject|\Exception
44
     */
45
    public $responseObject;
46
47
    /**
48
     * @When I set header :header with/for :object object
49
     */
50
    public function setHeaderWithObject($header, $object)
51
    {
52
        $value = $this->makeObjectHeader($object);
53
        $this->restDriver->setHeader($header, $value);
0 ignored issues
show
Bug introduced by
The property restDriver does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
54
    }
55
56
    /**
57
     * @Then response header :header has/contains :object (object)
58
     */
59
    public function assertHeaderHasObject($header, $object)
60
    {
61
        Assertion::assertEquals(
62
            $this->decomposeObjectHeader($this->restDriver->getHeader($header)),
63
            $object,
64
            $this->restDriver->getBody()
65
        );
66
    }
67
68
    /**
69
     * @Then response error status code is :code
70
     */
71
    public function assertResponseErrorStatusCode($code)
72
    {
73
        $errorStatusCode = $this->getResponseError('code');
74
75
        Assertion::assertEquals(
76
            $code,
77
            $errorStatusCode,
78
            "Expected '$code' status code found '$errorStatusCode' with message:" . $this->restDriver->getStatusMessage()
79
        );
80
    }
81
82
    /**
83
     * @Then response error description is :errorDescriptionRegEx
84
     */
85
    public function assertResponseErrorDescription($errorDescriptionRegEx)
86
    {
87
        $errorDescription = $this->getResponseError('description');
88
89
        Assertion::assertEquals(
90
            preg_match($errorDescriptionRegEx, $errorDescription),
91
            1,
92
            "Expected to find a description that matched '$errorDescriptionRegEx' RegEx but found '$errorDescription'"
93
        );
94
    }
95
96
    /**
97
     * @When I make a(n) :type object
98
     *
99
     * This will create an object of the type passed for step by step be filled
100
     */
101
    public function makeObject($type)
102
    {
103
        $this->createRequestObject($type);
104
    }
105
106
    /**
107
     * @When I set field :field to :value
108
     */
109
    public function setFieldToValue($field, $value)
110
    {
111
        // normally fields are defined in lower camelCase
112
        $field = lcfirst($field);
113
114
        ValueObjectHelper::setProperty($this->getRequestObject(), $field, $value);
115
    }
116
117
    /**
118
     * @When I set field :field to an empty array
119
     */
120
    public function setFieldToEmptyArray($field)
121
    {
122
        $this->setFieldToValue($field, array());
123
    }
124
125
    /**
126
     * @Then response object has field :field with :value
127
     */
128
    public function assertObjectFieldHasValue($field, $value)
129
    {
130
        $responseObject = $this->getResponseObject();
131
        $actualValue = ValueObjectHelper::getProperty($responseObject, $field);
132
133
        Assertion::assertEquals(
134
            $actualValue,
135
            $value,
136
            "Expected '$field' property to have '$value' value but found '$actualValue' value"
137
        );
138
    }
139
140
    /**
141
     * @Then response has/contains a(n) :object object
142
     *
143
     * @param string $object Object should be the object name with namespace
144
     */
145
    public function assertResponseObject($object)
146
    {
147
        $responseObject = $this->getResponseObject();
148
149
        Assertion::assertTrue(
150
            $responseObject instanceof $object,
151
            "Expect body object to be an instance of '$object' but got a '" . get_class($responseObject) . "'"
152
        );
153
    }
154
155
    /**
156
     * @When I set the Content-Type header to :contentType in version :version
157
     */
158
    public function iSetTheContentTypeHeaderToInVersion($contentType, $version)
159
    {
160
        $this->restDriver->setHeader('Content-Type', "$contentType+$this->restBodyType; version=$version");
161
    }
162
163
    /**
164
     * Set body type of requests and responses.
165
     *
166
     * @param string $type Type of the REST body
167
     */
168
    protected function setRestBodyType($type)
169
    {
170
        $type = strtolower($type);
171
        switch ($type) {
172
            case 'json':
173
            case 'xml':
174
                $this->restBodyType = $type;
175
                break;
176
177
            default:
178
                throw new \Exception("REST body type '$type' not suported");
179
        }
180
    }
181
182
    /**
183
     * Get property from the returned Exception.
184
     *
185
     * @param string $property Property to return
186
     *
187
     * @return int|mixed|string Property
188
     *
189
     * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentException If property is not defined
190
     */
191
    protected function getResponseError($property)
192
    {
193
        $exception = $this->getResponseObject();
194
195
        if (!$exception instanceof \Exception) {
196
            throw new InvalidArgumentException('response object', 'is not an exception');
197
        }
198
199
        switch ($property) {
200
            case 'code':
201
                return $exception->getCode();
202
203
            case 'description':
204
            case 'message':
205
                return $exception->getMessage();
206
        }
207
208
        throw new InvalidArgumentException($property, 'is invalid');
209
    }
210
211
    /**
212
     * Make content-type/accept header with prefix and type.
213
     */
214
    protected function makeObjectHeader($object)
215
    {
216
        return 'application/vnd.ez.api.' . $object . '+' . $this->restBodyType;
217
    }
218
219
    /**
220
     * Decompose the header to get only the object type of the accept/conten-type headers.
221
     *
222
     * @return false|string Decomposed string if found, false other wise
223
     */
224
    protected function decomposeObjectHeader($header)
225
    {
226
        preg_match('/application\/vnd\.ez\.api\.(.*)\+(?:json|xml)/i', $header, $match);
227
228
        return empty($match) ? false : $match[1];
229
    }
230
231
    /**
232
     * Get the response object (if it's not converted do the conversion also).
233
     *
234
     * @return \eZ\Publish\API\Repository\Values\ValueObject
235
     */
236
    public function getResponseObject()
237
    {
238
        if (empty($this->responseObject)) {
239
            $this->responseObject = $this->convertResponseBodyToObject(
240
                $this->restDriver->getBody(),
241
                $this->restDriver->getHeader('content-type')
242
            );
243
        }
244
245
        return $this->responseObject;
246
    }
247
248
    /**
249
     * Get the request object.
250
     *
251
     * @return \eZ\Publish\API\Repository\Values\ValueObject
252
     */
253
    public function getRequestObject()
254
    {
255
        return $this->requestObject;
256
    }
257
258
    /**
259
     * Convert an object and add it to the body/content of the request.
260
     *
261
     * @param \eZ\Publish\API\Repository\Values\ValueObject $object Object to be converted
262
     * @param string $type Type for the body of the request (XML, JSON)
263
     */
264
    protected function addObjectToRequestBody(ValueObject $object, $type)
265
    {
266
        $request = $this->convertObjectTo($object, $type);
267
268
        $this->restDriver->setBody($request->getContent());
269
    }
270
271
    /**
272
     * Convert an object to a request.
273
     *
274
     * @param \eZ\Publish\API\Repository\Values\ValueObject $object Object to be converted
275
     * @param string $type Type for conversion
276
     *
277
     * @return \Symfony\Component\HttpFoundation\Response
278
     *
279
     * @throws InvalidArgumentException If the type is not known
280
     */
281
    protected function convertObjectTo(ValueObject $object, $type)
282
    {
283
        $type = strtolower($type);
284
        switch ($type) {
285
            case 'json':
286
            case 'xml':
287
                $visitor = $this->getKernel()->getContainer()->get('ezpublish_rest.output.visitor.' . $type);
0 ignored issues
show
Bug introduced by
It seems like getKernel() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
288
                break;
289
290
            default:
291
                throw new InvalidArgumentException('rest body type', $type);
292
        }
293
294
        return $visitor->visit($object);
295
    }
296
297
    /**
298
     * Convert the body/content of a response into an object.
299
     *
300
     * @param string $responseBody Body/content of the response (with the object)
301
     * @param string $contentTypeHeader Value of the content-type header
302
     *
303
     * @return \eZ\Publish\API\Repository\Values\ValueObject
304
     */
305
    public function convertResponseBodyToObject($responseBody, $contentTypeHeader)
306
    {
307
        try {
308
            $this->responseObject = $this->getKernel()->getContainer()->get('ezpublish_rest.input.dispatcher')->parse(
0 ignored issues
show
Bug introduced by
It seems like getKernel() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
309
                new Message(
310
                    array('Content-Type' => $contentTypeHeader),
311
                    $responseBody
312
                )
313
            );
314
        } catch (\Exception $e) {
315
            // when errors/exceptions popup on form the response we need also to
316
            // test/verify them
317
            $this->responseObject = $e;
318
        }
319
320
        return $this->responseObject;
321
    }
322
323
    /**
324
     * Create an object of the specified type.
325
     *
326
     * @param string $objectType the name of the object to be created
327
     *
328
     * @throws \Behat\Behat\Exception\PendingException When the object requested is not implemented yet
329
     */
330
    protected function createRequestObject($objectType)
331
    {
332
        $repository = $this->getRepository();
0 ignored issues
show
Bug introduced by
It seems like getRepository() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
333
334
        switch ($objectType) {
335
            case 'SessionInput':
336
                $this->requestObject = new SessionInput();
337
                break;
338
            case 'ContentTypeGroupCreateStruct':
339
                $this->requestObject = $repository
340
                    ->getContentTypeService()
341
                    ->newContentTypeGroupCreateStruct('identifier');
342
                break;
343
344
            case 'ContentTypeGroupUpdateStruct':
345
                $this->requestObject = $repository
346
                    ->getContentTypeService()
347
                    ->newContentTypeGroupUpdateStruct();
348
                break;
349
350
            case 'ViewInput':
351
                $this->requestObject = new ViewInput();
352
                break;
353
354
            default:
355
                throw new InvalidArgumentException($objectType, 'type is not defined yet');
356
        }
357
    }
358
}
359