Completed
Pull Request — master (#64)
by John
02:40
created

ApiTestCase::delete()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
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
 * @property bool   validateErrorResponse
26
 * @property string env
27
 * @property array  defaultServerVars
28
 */
29
trait ApiTestCase
30
{
31
    use AssertsTrait;
32
33
    /**
34
     * @var SchemaManager
35
     */
36
    protected static $schemaManager;
37
38
    /**
39
     * @var SwaggerDocument
40
     */
41
    protected static $document;
42
43
    /**
44
     * @var ApiTestClient
45
     */
46
    protected $client;
47
48
    /**
49
     * PHPUnit cannot add this to code coverage
50
     *
51
     * @codeCoverageIgnore
52
     *
53
     * @param string $swaggerPath
54
     *
55
     * @throws \InvalidArgumentException
56
     * @throws \org\bovigo\vfs\vfsStreamException
57
     */
58
    public static function initSchemaManager($swaggerPath)
59
    {
60
        $validator = new Validator();
61
        $validator->check(
62
            json_decode(json_encode(Yaml::parse(file_get_contents($swaggerPath)))),
63
            json_decode(file_get_contents(__DIR__ . '/../../assets/swagger-schema.json'))
64
        );
65
66
        if (!$validator->isValid()) {
67
            throw new \InvalidArgumentException(
68
                "Swagger '$swaggerPath' not valid"
69
            );
70
        }
71
72
        vfsStreamWrapper::register();
73
        vfsStreamWrapper::setRoot(new vfsStreamDirectory('root'));
74
75
        file_put_contents(
76
            vfsStream::url('root') . '/swagger.json',
77
            json_encode(Yaml::parse(file_get_contents($swaggerPath)))
78
        );
79
80
        self::$schemaManager = new SchemaManager(vfsStream::url('root') . '/swagger.json');
81
        $repository = new DocumentRepository(dirname($swaggerPath));
82
        self::$document = $repository->get(basename($swaggerPath));
83
    }
84
85
    /**
86
     * Create a client, booting the kernel using SYMFONY_ENV = $this->env
87
     */
88
    protected function setUp()
89
    {
90
        $this->client = static::createClient(['environment' => $this->getEnv(), 'debug' => true]);
91
92
        parent::setUp();
93
    }
94
95
    /**
96
     * @return array
97
     */
98
    protected function getDefaultServerVars()
99
    {
100
        return isset($this->defaultServerVars) ? $this->defaultServerVars : [];
101
    }
102
103
    /**
104
     * @return array
105
     */
106
    protected function getEnv()
107
    {
108
        return isset($this->env) ? $this->env : 'test';
109
    }
110
111
    /**
112
     * @return bool
113
     */
114
    protected function getValidateErrorResponse()
115
    {
116
        return isset($this->validateErrorResponse) ? $this->validateErrorResponse : false;
117
    }
118
119
    /**
120
     * @param string $path
121
     * @param array  $params
122
     *
123
     * @return object
124
     * @throws ApiResponseErrorException
125
     */
126
    protected function get($path, array $params = [])
127
    {
128
        return $this->sendRequest($path, 'GET', $params);
129
    }
130
131
    /**
132
     * @param string $path
133
     * @param array  $params
134
     *
135
     * @return object
136
     * @throws ApiResponseErrorException
137
     */
138
    protected function delete($path, array $params = [])
139
    {
140
        return $this->sendRequest($path, 'DELETE', $params);
141
    }
142
143
    /**
144
     * @param string $path
145
     * @param array  $content
146
     * @param array  $params
147
     *
148
     * @return object
149
     * @throws ApiResponseErrorException
150
     */
151
    protected function patch($path, array $content, array $params = [])
152
    {
153
        return $this->sendRequest($path, 'PATCH', $params, $content);
154
    }
155
156
    /**
157
     * @param string $path
158
     * @param array  $content
159
     * @param array  $params
160
     *
161
     * @return object
162
     * @throws ApiResponseErrorException
163
     */
164
    protected function post($path, array $content, array $params = [])
165
    {
166
        return $this->sendRequest($path, 'POST', $params, $content);
167
    }
168
169
    /**
170
     * @param string $path
171
     * @param array  $content
172
     * @param array  $params
173
     *
174
     * @return object
175
     * @throws ApiResponseErrorException
176
     */
177
    protected function put($path, array $content, array $params = [])
178
    {
179
        return $this->sendRequest($path, 'PUT', $params, $content);
180
    }
181
182
    /**
183
     * @param string     $path
184
     * @param string     $method
185
     * @param array      $params
186
     * @param array|null $content
187
     *
188
     * @return object
189
     * @throws ApiResponseErrorException
190
     */
191
    protected function sendRequest($path, $method, array $params = [], array $content = null)
192
    {
193
        $request = new ApiRequest($this->assembleUri($path, $params), $method);
194
        $request->setServer(array_merge(['CONTENT_TYPE' => 'application/json'], $this->getDefaultServerVars()));
195
        if ($content !== null) {
196
            $request->setContent(json_encode($content));
197
        }
198
        $this->client->requestFromRequest($request);
199
200
        return $this->getJsonForLastRequest($path, $method);
201
    }
202
203
    /**
204
     * @param string $path
205
     * @param array  $params
206
     *
207
     * @return string
208
     */
209
    private function assembleUri($path, array $params = [])
210
    {
211
        $uri = $path;
212
        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...
213
            $uri = $path . '?' . http_build_query($params);
214
        }
215
216
        return $uri;
217
    }
218
219
    /**
220
     * @param string $fullPath
221
     * @param string $method
222
     *
223
     * @return object|null
224
     * @throws ApiResponseErrorException
225
     */
226
    private function getJsonForLastRequest($fullPath, $method)
227
    {
228
        $method = strtolower($method);
229
        $response = $this->client->getResponse();
230
        $json = $response->getContent();
231
        $data = json_decode($json);
232
233
        if ($response->getStatusCode() !== 204) {
234
            static $errors = [
235
                JSON_ERROR_NONE           => 'No error',
236
                JSON_ERROR_DEPTH          => 'Maximum stack depth exceeded',
237
                JSON_ERROR_STATE_MISMATCH => 'State mismatch (invalid or malformed JSON)',
238
                JSON_ERROR_CTRL_CHAR      => 'Control character error, possibly incorrectly encoded',
239
                JSON_ERROR_SYNTAX         => 'Syntax error',
240
                JSON_ERROR_UTF8           => 'Malformed UTF-8 characters, possibly incorrectly encoded'
241
            ];
242
            $error = json_last_error();
243
            $jsonErrorMessage = isset($errors[$error]) ? $errors[$error] : 'Unknown error';
244
            $this->assertSame(
245
                JSON_ERROR_NONE,
246
                json_last_error(),
247
                "Not valid JSON: " . $jsonErrorMessage . "(" . var_export($json, true) . ")"
248
            );
249
        }
250
251
        if (substr($response->getStatusCode(), 0, 1) != '2') {
252
            if ($this->getValidateErrorResponse()) {
253
                $this->validateResponse($response->getStatusCode(), $response, $method, $fullPath, $data);
254
            }
255
            // This throws an exception so that tests can catch it when it is expected
256
            throw new ApiResponseErrorException($json, $data, $response->getStatusCode());
257
        }
258
259
        $this->validateResponse($response->getStatusCode(), $response, $method, $fullPath, $data);
260
261
        return $data;
262
    }
263
264
    /**
265
     * @param          $code
266
     * @param Response $response
267
     * @param string   $method
268
     * @param string   $fullPath
269
     * @param mixed    $data
270
     */
271
    private function validateResponse($code, $response, $method, $fullPath, $data)
272
    {
273
        $request = $this->client->getRequest();
274
        if (!self::$schemaManager->hasPath(['paths', $request->get('_swagger_path'), $method, 'responses', $code])) {
275
            $statusClass = (int)substr((string)$code, 0, 1);
276
            if (in_array($statusClass, [4, 5])) {
277
                return;
278
            }
279
            throw new \UnexpectedValueException(
280
                "There is no $code response definition for {$request->get('_swagger_path')}:$method. "
281
            );
282
        }
283
        $headers = [];
284
285
        foreach ($response->headers->all() as $key => $values) {
286
            $headers[str_replace(' ', '-', ucwords(str_replace('-', ' ', $key)))] = $values[0];
287
        }
288
        try {
289
            try {
290
                $this->assertResponseMediaTypeMatch(
291
                    $response->headers->get('Content-Type'),
292
                    self::$schemaManager,
293
                    $fullPath,
294
                    $method
295
                );
296
            } catch (\InvalidArgumentException $e) {
297
                // Not required, so skip if not found
298
            }
299
300
            $this->assertResponseHeadersMatch($headers, self::$schemaManager, $fullPath, $method, $code);
301
            $this->assertResponseBodyMatch($data, self::$schemaManager, $fullPath, $method, $code);
302
        } catch (\UnexpectedValueException $e) {
303
            $statusClass = (int)(string)$code[0];
304
            if (in_array($statusClass, [4, 5])) {
305
                return;
306
            }
307
        }
308
    }
309
310
    /**
311
     * @param mixed  $expected
312
     * @param mixed  $actual
313
     * @param string $message
314
     *
315
     * @return mixed
316
     */
317
    public abstract function assertSame($expected, $actual, $message = '');
318
}
319