Completed
Push — master ( 658d5b...53259c )
by John
02:58
created

ApiTestCase::assembleUri()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 9
rs 9.6666
cc 2
eloc 5
nc 2
nop 2
1
<?php
2
/*
3
 * This file is part of the KleijnWeb\SwaggerBundle package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
9
namespace KleijnWeb\SwaggerBundle\Test;
10
11
use FR3D\SwaggerAssertions\PhpUnit\AssertsTrait;
12
use FR3D\SwaggerAssertions\SchemaManager;
13
use JsonSchema\Validator;
14
use KleijnWeb\SwaggerBundle\Document\DocumentRepository;
15
use KleijnWeb\SwaggerBundle\Document\SwaggerDocument;
16
use org\bovigo\vfs\vfsStream;
17
use org\bovigo\vfs\vfsStreamDirectory;
18
use org\bovigo\vfs\vfsStreamWrapper;
19
use Symfony\Component\HttpFoundation\Response;
20
use Symfony\Component\Yaml\Yaml;
21
22
/**
23
 * @author John Kleijn <[email protected]>
24
 */
25
trait ApiTestCase
26
{
27
    use AssertsTrait;
28
29
    /**
30
     * @var SchemaManager
31
     */
32
    protected static $schemaManager;
33
34
    /**
35
     * @var SwaggerDocument
36
     */
37
    protected static $document;
38
39
    /**
40
     * @var ApiTestClient
41
     */
42
    protected $client;
43
44
    /**
45
     * PHPUnit cannot add this to code coverage
46
     *
47
     * @codeCoverageIgnore
48
     *
49
     * @param $swaggerPath
50
     *
51
     * @throws \InvalidArgumentException
52
     * @throws \org\bovigo\vfs\vfsStreamException
53
     */
54
    public static function initSchemaManager($swaggerPath)
55
    {
56
        $validator = new Validator();
57
        $validator->check(
58
            json_decode(json_encode(Yaml::parse(file_get_contents($swaggerPath)))),
59
            json_decode(file_get_contents(__DIR__ . '/../../assets/swagger-schema.json'))
60
        );
61
62
        if (!$validator->isValid()) {
63
            throw new \InvalidArgumentException(
64
                "Swagger '$swaggerPath' not valid"
65
            );
66
        }
67
68
        vfsStreamWrapper::register();
69
        vfsStreamWrapper::setRoot(new vfsStreamDirectory('root'));
70
71
        file_put_contents(
72
            vfsStream::url('root') . '/swagger.json',
73
            json_encode(Yaml::parse(file_get_contents($swaggerPath)))
74
        );
75
76
        self::$schemaManager = new SchemaManager(vfsStream::url('root') . '/swagger.json');
77
        $repository = new DocumentRepository(dirname($swaggerPath));
78
        self::$document = $repository->get(basename($swaggerPath));
79
    }
80
81
    /**
82
     * Create a client, booting the kernel using SYMFONY_ENV = $this->env
83
     */
84
    protected function setUp()
85
    {
86
        $this->client = static::createClient(['environment' => $this->env ?: 'test', 'debug' => true]);
0 ignored issues
show
Bug introduced by
The property env 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...
87
88
        parent::setUp();
89
    }
90
91
92
    /**
93
     * @param string $path
94
     * @param array  $params
95
     *
96
     * @return object
97
     * @throws ApiResponseErrorException
98
     */
99
    protected function get($path, array $params = [])
100
    {
101
        return $this->sendRequest($path, 'GET', $params);
0 ignored issues
show
Documentation introduced by
'GET' is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
102
    }
103
104
    /**
105
     * @param string $path
106
     * @param array  $params
107
     *
108
     * @return object
109
     * @throws ApiResponseErrorException
110
     */
111
    protected function delete($path, array $params = [])
112
    {
113
        return $this->sendRequest($path, 'DELETE', $params);
0 ignored issues
show
Documentation introduced by
'DELETE' is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
114
    }
115
116
    /**
117
     * @param string $path
118
     * @param array  $content
119
     * @param array  $params
120
     *
121
     * @return object
122
     * @throws ApiResponseErrorException
123
     */
124
    protected function patch($path, array $content, array $params = [])
125
    {
126
        return $this->sendRequest($path, 'PATCH', $params, $content);
0 ignored issues
show
Documentation introduced by
'PATCH' is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
127
    }
128
129
    /**
130
     * @param string $path
131
     * @param array  $content
132
     * @param array  $params
133
     *
134
     * @return object
135
     * @throws ApiResponseErrorException
136
     */
137
    protected function post($path, array $content, array $params = [])
138
    {
139
        return $this->sendRequest($path, 'POST', $params, $content);
0 ignored issues
show
Documentation introduced by
'POST' is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
140
    }
141
142
    /**
143
     * @param string $path
144
     * @param array  $content
145
     * @param array  $params
146
     *
147
     * @return object
148
     * @throws ApiResponseErrorException
149
     */
150
    protected function put($path, array $content, array $params = [])
151
    {
152
        return $this->sendRequest($path, 'PUT', $params, $content);
0 ignored issues
show
Documentation introduced by
'PUT' is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
153
    }
154
155
    /**
156
     * @param string     $path
157
     * @param array      $method
158
     * @param array      $params
159
     * @param array|null $content
160
     *
161
     * @return object
162
     * @throws ApiResponseErrorException
163
     */
164
    protected function sendRequest($path, $method, array $params = [], array $content = null)
165
    {
166
        $request = new ApiRequest($this->assembleUri($path, $params), $method);
0 ignored issues
show
Documentation introduced by
$method is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
167
        $defaults = isset($this->defaultServerVars) ? $this->defaultServerVars : [];
0 ignored issues
show
Bug introduced by
The property defaultServerVars 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...
168
        $request->setServer(array_merge($defaults ?: [], ['CONTENT_TYPE' => 'application/json']));
169
        if ($content !== null) {
170
            $request->setContent(json_encode($content));
171
        }
172
        $this->client->requestFromRequest($request);
173
174
        return $this->getJsonForLastRequest($path, $method);
0 ignored issues
show
Documentation introduced by
$method is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
175
    }
176
177
178
    /**
179
     * @param string $path
180
     * @param array  $params
181
     *
182
     * @return string
183
     */
184
    private function assembleUri($path, array $params = [])
185
    {
186
        $uri = $path;
187
        if ($params) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $params of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
188
            $uri = $path . '?' . http_build_query($params);
189
        }
190
191
        return $uri;
192
    }
193
194
    /**
195
     * @param string $fullPath
196
     * @param string $method
197
     *
198
     * @return object|null
199
     * @throws ApiResponseErrorException
200
     */
201
    private function getJsonForLastRequest($fullPath, $method)
202
    {
203
        $method = strtolower($method);
204
        $response = $this->client->getResponse();
205
        $json = $response->getContent();
206
        $data = json_decode($json);
207
208
        if ($response->getStatusCode() !== 204) {
209
            static $errors = [
210
                JSON_ERROR_NONE           => 'No error',
211
                JSON_ERROR_DEPTH          => 'Maximum stack depth exceeded',
212
                JSON_ERROR_STATE_MISMATCH => 'State mismatch (invalid or malformed JSON)',
213
                JSON_ERROR_CTRL_CHAR      => 'Control character error, possibly incorrectly encoded',
214
                JSON_ERROR_SYNTAX         => 'Syntax error',
215
                JSON_ERROR_UTF8           => 'Malformed UTF-8 characters, possibly incorrectly encoded'
216
            ];
217
            $error = json_last_error();
218
            $jsonErrorMessage = isset($errors[$error]) ? $errors[$error] : 'Unknown error';
219
            $this->assertSame(
0 ignored issues
show
Bug introduced by
It seems like assertSame() 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...
220
                JSON_ERROR_NONE,
221
                json_last_error(),
222
                "Not valid JSON: " . $jsonErrorMessage . "(" . var_export($json, true) . ")"
223
            );
224
        }
225
226
        if (substr($response->getStatusCode(), 0, 1) != '2') {
227
            if (!isset($this->validateErrorResponse) || $this->validateErrorResponse) {
0 ignored issues
show
Bug introduced by
The property validateErrorResponse 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...
228
                $this->validateResponse($response->getStatusCode(), $response, $method, $fullPath, $data);
229
            }
230
            // This throws an exception so that tests can catch it when it is expected
231
            throw new ApiResponseErrorException($json, $data, $response->getStatusCode());
232
        }
233
234
        $this->validateResponse($response->getStatusCode(), $response, $method, $fullPath, $data);
235
236
        return $data;
237
    }
238
239
    /**
240
     * @param          $code
241
     * @param Response $response
242
     * @param string   $method
243
     * @param string   $fullPath
244
     * @param mixed    $data
245
     */
246
    private function validateResponse($code, $response, $method, $fullPath, $data)
247
    {
248
        $request = $this->client->getRequest();
249
        if (!self::$schemaManager->hasPath(['paths', $request->get('_swagger_path'), $method, 'responses', $code])) {
250
            $statusClass = (int)substr((string)$code, 0, 1);
251
            if (in_array($statusClass, [4, 5])) {
252
                return;
253
            }
254
            throw new \UnexpectedValueException(
255
                "There is no $code response definition for {$request->get('_swagger_path')}:$method. "
256
            );
257
        }
258
        $headers = [];
259
260
        foreach ($response->headers->all() as $key => $values) {
261
            $headers[str_replace(' ', '-', ucwords(str_replace('-', ' ', $key)))] = $values[0];
262
        }
263
        try {
264
            $this->assertResponseHeadersMatch($headers, self::$schemaManager, $fullPath, $method, $code);
265
            $this->assertResponseBodyMatch($data, self::$schemaManager, $fullPath, $method, $code);
266
        } catch (\UnexpectedValueException $e) {
267
            $statusClass = (int)(string)$code[0];
268
            if (in_array($statusClass, [4, 5])) {
269
                return;
270
            }
271
        }
272
    }
273
}
274