Completed
Push — master ( ddc3a0...dbafdf )
by Vladimir
13s queued 11s
created

RequestParsingTest::parsePsrRequest()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 21
rs 9.584
c 0
b 0
f 0
cc 3
nc 3
nop 3
1
<?php
2
namespace GraphQL\Tests\Server;
3
4
use GraphQL\Error\Error;
5
use GraphQL\Error\InvariantViolation;
6
use GraphQL\Server\Helper;
7
use GraphQL\Server\OperationParams;
8
use GraphQL\Server\RequestError;
9
use GraphQL\Tests\Server\Psr7\PsrRequestStub;
10
use GraphQL\Tests\Server\Psr7\PsrStreamStub;
11
12
class RequestParsingTest extends \PHPUnit_Framework_TestCase
13
{
14
    public function testParsesGraphqlRequest()
15
    {
16
        $query = '{my query}';
17
        $parsed = [
18
            'raw' => $this->parseRawRequest('application/graphql', $query),
19
            'psr' => $this->parsePsrRequest('application/graphql', $query)
20
        ];
21
22
        foreach ($parsed as $source => $parsedBody) {
23
            $this->assertValidOperationParams($parsedBody, $query, null, null, null, $source);
24
            $this->assertFalse($parsedBody->isReadOnly(), $source);
25
        }
26
    }
27
28
    public function testParsesUrlencodedRequest()
29
    {
30
        $query = '{my query}';
31
        $variables = ['test' => 1, 'test2' => 2];
32
        $operation = 'op';
33
34
        $post = [
35
            'query' => $query,
36
            'variables' => $variables,
37
            'operationName' => $operation
38
        ];
39
        $parsed = [
40
            'raw' => $this->parseRawFormUrlencodedRequest($post),
41
            'psr' => $this->parsePsrFormUrlEncodedRequest($post)
42
        ];
43
44
        foreach ($parsed as $method => $parsedBody) {
45
            $this->assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method);
46
            $this->assertFalse($parsedBody->isReadOnly(), $method);
0 ignored issues
show
Bug introduced by
The method isReadOnly() does not exist on GraphQL\Server\Helper. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

46
            $this->assertFalse($parsedBody->/** @scrutinizer ignore-call */ isReadOnly(), $method);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
47
        }
48
    }
49
50
    public function testParsesGetRequest()
51
    {
52
        $query = '{my query}';
53
        $variables = ['test' => 1, 'test2' => 2];
54
        $operation = 'op';
55
56
        $get = [
57
            'query' => $query,
58
            'variables' => $variables,
59
            'operationName' => $operation
60
        ];
61
        $parsed = [
62
            'raw' => $this->parseRawGetRequest($get),
63
            'psr' => $this->parsePsrGetRequest($get)
64
        ];
65
66
        foreach ($parsed as $method => $parsedBody) {
67
            $this->assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method);
68
            $this->assertTrue($parsedBody->isReadonly(), $method);
0 ignored issues
show
Bug introduced by
The method isReadonly() does not exist on GraphQL\Server\Helper. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

68
            $this->assertTrue($parsedBody->/** @scrutinizer ignore-call */ isReadonly(), $method);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
69
        }
70
    }
71
72
    public function testParsesMultipartFormdataRequest()
73
    {
74
        $query = '{my query}';
75
        $variables = ['test' => 1, 'test2' => 2];
76
        $operation = 'op';
77
78
        $post = [
79
            'query' => $query,
80
            'variables' => $variables,
81
            'operationName' => $operation
82
        ];
83
        $parsed = [
84
            'raw' => $this->parseRawMultipartFormdataRequest($post),
85
            'psr' => $this->parsePsrMultipartFormdataRequest($post)
86
        ];
87
88
        foreach ($parsed as $method => $parsedBody) {
89
            $this->assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method);
90
            $this->assertFalse($parsedBody->isReadOnly(), $method);
91
        }
92
    }
93
94
    public function testParsesJSONRequest()
95
    {
96
        $query = '{my query}';
97
        $variables = ['test' => 1, 'test2' => 2];
98
        $operation = 'op';
99
100
        $body = [
101
            'query' => $query,
102
            'variables' => $variables,
103
            'operationName' => $operation
104
        ];
105
        $parsed = [
106
            'raw' => $this->parseRawRequest('application/json', json_encode($body)),
107
            'psr' => $this->parsePsrRequest('application/json', json_encode($body))
108
        ];
109
        foreach ($parsed as $method => $parsedBody) {
110
            $this->assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method);
111
            $this->assertFalse($parsedBody->isReadOnly(), $method);
112
        }
113
    }
114
115
    public function testParsesVariablesAsJSON()
116
    {
117
        $query = '{my query}';
118
        $variables = ['test' => 1, 'test2' => 2];
119
        $operation = 'op';
120
121
        $body = [
122
            'query' => $query,
123
            'variables' => json_encode($variables),
124
            'operationName' => $operation
125
        ];
126
        $parsed = [
127
            'raw' => $this->parseRawRequest('application/json', json_encode($body)),
128
            'psr' => $this->parsePsrRequest('application/json', json_encode($body))
129
        ];
130
        foreach ($parsed as $method => $parsedBody) {
131
            $this->assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method);
132
            $this->assertFalse($parsedBody->isReadOnly(), $method);
133
        }
134
    }
135
136
    public function testIgnoresInvalidVariablesJson()
137
    {
138
        $query = '{my query}';
139
        $variables = '"some invalid json';
140
        $operation = 'op';
141
142
        $body = [
143
            'query' => $query,
144
            'variables' => $variables,
145
            'operationName' => $operation
146
        ];
147
        $parsed = [
148
            'raw' => $this->parseRawRequest('application/json', json_encode($body)),
149
            'psr' => $this->parsePsrRequest('application/json', json_encode($body)),
150
        ];
151
        foreach ($parsed as $method => $parsedBody) {
152
            $this->assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method);
0 ignored issues
show
Bug introduced by
$variables of type string is incompatible with the type array expected by parameter $variables of GraphQL\Tests\Server\Req...tValidOperationParams(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

152
            $this->assertValidOperationParams($parsedBody, $query, null, /** @scrutinizer ignore-type */ $variables, $operation, $method);
Loading history...
153
            $this->assertFalse($parsedBody->isReadOnly(), $method);
154
        }
155
    }
156
157
    public function testParsesBatchJSONRequest()
158
    {
159
        $body = [
160
            [
161
                'query' => '{my query}',
162
                'variables' => ['test' => 1, 'test2' => 2],
163
                'operationName' => 'op'
164
            ],
165
            [
166
                'queryId' => 'my-query-id',
167
                'variables' => ['test' => 1, 'test2' => 2],
168
                'operationName' => 'op2'
169
            ],
170
        ];
171
        $parsed = [
172
            'raw' => $this->parseRawRequest('application/json', json_encode($body)),
173
            'psr' => $this->parsePsrRequest('application/json', json_encode($body))
174
        ];
175
        foreach ($parsed as $method => $parsedBody) {
176
            $this->assertInternalType('array', $parsedBody, $method);
177
            $this->assertCount(2, $parsedBody, $method);
178
            $this->assertValidOperationParams($parsedBody[0], $body[0]['query'], null, $body[0]['variables'], $body[0]['operationName'], $method);
179
            $this->assertValidOperationParams($parsedBody[1], null, $body[1]['queryId'], $body[1]['variables'], $body[1]['operationName'], $method);
180
        }
181
    }
182
183
    public function testFailsParsingInvalidRawJsonRequestRaw()
184
    {
185
        $body = 'not really{} a json';
186
187
        $this->setExpectedException(RequestError::class, 'Could not parse JSON: Syntax error');
188
            $this->parseRawRequest('application/json', $body);
189
        }
190
191
    public function testFailsParsingInvalidRawJsonRequestPsr()
192
    {
193
        $body = 'not really{} a json';
194
195
        $this->setExpectedException(InvariantViolation::class, 'PSR-7 request is expected to provide parsed body for "application/json" requests but got null');
196
            $this->parsePsrRequest('application/json', $body);
197
    }
198
199
    public function testFailsParsingNonPreParsedPsrRequest()
200
    {
201
        try {
202
            $this->parsePsrRequest('application/json', json_encode(null));
203
            $this->fail('Expected exception not thrown');
204
        } catch (InvariantViolation $e) {
205
            // Expecting parsing exception to be thrown somewhere else:
206
            $this->assertEquals(
207
                'PSR-7 request is expected to provide parsed body for "application/json" requests but got null',
208
                $e->getMessage()
209
            );
210
        }
211
    }
212
213
    // There is no equivalent for psr request, because it should throw
214
215
    public function testFailsParsingNonArrayOrObjectJsonRequestRaw()
216
    {
217
        $body = '"str"';
218
219
        $this->setExpectedException(RequestError::class, 'GraphQL Server expects JSON object or array, but got "str"');
220
            $this->parseRawRequest('application/json', $body);
221
        }
222
223
    public function testFailsParsingNonArrayOrObjectJsonRequestPsr()
224
    {
225
        $body = '"str"';
226
227
        $this->setExpectedException(RequestError::class, 'GraphQL Server expects JSON object or array, but got "str"');
228
            $this->parsePsrRequest('application/json', $body);
229
        }
230
231
    public function testFailsParsingInvalidContentTypeRaw()
232
    {
233
        $contentType = 'not-supported-content-type';
234
        $body = 'test';
235
236
        $this->setExpectedException(RequestError::class, 'Unexpected content type: "not-supported-content-type"');
237
        $this->parseRawRequest($contentType, $body);
238
    }
239
240
    public function testFailsParsingInvalidContentTypePsr()
241
    {
242
        $contentType = 'not-supported-content-type';
243
        $body = 'test';
244
245
        $this->setExpectedException(RequestError::class, 'Unexpected content type: "not-supported-content-type"');
246
            $this->parseRawRequest($contentType, $body);
247
        }
248
249
    public function testFailsWithMissingContentTypeRaw()
250
    {
251
        $this->setExpectedException(RequestError::class, 'Missing "Content-Type" header');
252
            $this->parseRawRequest(null, 'test');
253
        }
254
255
    public function testFailsWithMissingContentTypePsr()
256
    {
257
        $this->setExpectedException(RequestError::class, 'Missing "Content-Type" header');
258
            $this->parsePsrRequest(null, 'test');
259
    }
260
261
    public function testFailsOnMethodsOtherThanPostOrGetRaw()
262
    {
263
        $this->setExpectedException(RequestError::class, 'HTTP Method "PUT" is not supported');
264
        $this->parseRawRequest('application/json', json_encode([]), "PUT");
265
    }
266
267
    public function testFailsOnMethodsOtherThanPostOrGetPsr()
268
    {
269
        $this->setExpectedException(RequestError::class, 'HTTP Method "PUT" is not supported');
270
        $this->parsePsrRequest('application/json', json_encode([]), "PUT");
271
    }
272
273
    /**
274
     * @param string $contentType
275
     * @param string $content
276
     * @param $method
277
     *
278
     * @return OperationParams|OperationParams[]
279
     */
280
    private function parseRawRequest($contentType, $content, $method = 'POST')
281
    {
282
        $_SERVER['CONTENT_TYPE'] = $contentType;
283
        $_SERVER['REQUEST_METHOD'] = $method;
284
285
        $helper = new Helper();
286
        return $helper->parseHttpRequest(function() use ($content) {
287
            return $content;
288
        });
289
    }
290
291
    /**
292
     * @param string $contentType
293
     * @param string $content
294
     * @param $method
295
     *
296
     * @return OperationParams|OperationParams[]
297
     */
298
    private function parsePsrRequest($contentType, $content, $method = 'POST')
299
    {
300
        $psrRequestBody = new PsrStreamStub();
301
        $psrRequestBody->content = $content;
302
303
        $psrRequest = new PsrRequestStub();
304
        $psrRequest->headers['content-type'] = [$contentType];
305
        $psrRequest->method = $method;
306
        $psrRequest->body = $psrRequestBody;
307
308
        if ($contentType === 'application/json') {
309
            $parsedBody = json_decode($content, true);
310
            $parsedBody = $parsedBody === false ? null : $parsedBody;
311
        } else {
312
            $parsedBody = null;
313
        }
314
315
        $psrRequest->parsedBody = $parsedBody;
316
317
        $helper = new Helper();
318
        return $helper->parsePsrRequest($psrRequest);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $helper->parsePsrRequest($psrRequest) also could return the type GraphQL\Server\Helper which is incompatible with the documented return type GraphQL\Server\Operation...\Server\OperationParams.
Loading history...
319
    }
320
321
    /**
322
     * @param array $postValue
323
     * @return OperationParams|OperationParams[]
324
     */
325
    private function parseRawFormUrlencodedRequest($postValue)
326
    {
327
        $_SERVER['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
328
        $_SERVER['REQUEST_METHOD'] = 'POST';
329
        $_POST = $postValue;
330
331
        $helper = new Helper();
332
        return $helper->parseHttpRequest(function() {
333
            throw new InvariantViolation("Shouldn't read from php://input for urlencoded request");
334
        });
335
    }
336
337
    /**
338
     * @param $postValue
339
     * @return array|Helper
340
     */
341
    private function parsePsrFormUrlEncodedRequest($postValue)
342
    {
343
        $psrRequest = new PsrRequestStub();
344
        $psrRequest->headers['content-type'] = ['application/x-www-form-urlencoded'];
345
        $psrRequest->method = 'POST';
346
        $psrRequest->parsedBody = $postValue;
347
348
        $helper = new Helper();
349
        return $helper->parsePsrRequest($psrRequest);
350
    }
351
352
    /**
353
     * @param array $postValue
354
     * @return OperationParams|OperationParams[]
355
     */
356
    private function parseRawMultipartFormDataRequest($postValue)
357
    {
358
        $_SERVER['CONTENT_TYPE'] = 'multipart/form-data; boundary=----FormBoundary';
359
        $_SERVER['REQUEST_METHOD'] = 'POST';
360
        $_POST = $postValue;
361
362
        $helper = new Helper();
363
        return $helper->parseHttpRequest(function() {
364
            throw new InvariantViolation("Shouldn't read from php://input for multipart/form-data request");
365
        });
366
    }
367
368
    /**
369
     * @param $postValue
370
     * @return array|Helper
371
     */
372
    private function parsePsrMultipartFormDataRequest($postValue)
373
    {
374
        $psrRequest = new PsrRequestStub();
375
        $psrRequest->headers['content-type'] = ['multipart/form-data; boundary=----FormBoundary'];
376
        $psrRequest->method = 'POST';
377
        $psrRequest->parsedBody = $postValue;
378
379
        $helper = new Helper();
380
        return $helper->parsePsrRequest($psrRequest);
381
    }
382
383
    /**
384
     * @param $getValue
385
     * @return OperationParams
386
     */
387
    private function parseRawGetRequest($getValue)
388
    {
389
        $_SERVER['REQUEST_METHOD'] = 'GET';
390
        $_GET = $getValue;
391
392
        $helper = new Helper();
393
        return $helper->parseHttpRequest(function() {
394
            throw new InvariantViolation("Shouldn't read from php://input for urlencoded request");
395
        });
396
    }
397
398
    /**
399
     * @param $getValue
400
     * @return array|Helper
401
     */
402
    private function parsePsrGetRequest($getValue)
403
    {
404
        $psrRequest = new PsrRequestStub();
405
        $psrRequest->method = 'GET';
406
        $psrRequest->queryParams = $getValue;
407
408
        $helper = new Helper();
409
        return $helper->parsePsrRequest($psrRequest);
410
    }
411
412
    /**
413
     * @param OperationParams $params
414
     * @param string $query
415
     * @param string $queryId
416
     * @param array $variables
417
     * @param string $operation
418
     */
419
    private function assertValidOperationParams($params, $query, $queryId = null, $variables = null, $operation = null, $message = '')
420
    {
421
        $this->assertInstanceOf(OperationParams::class, $params, $message);
422
423
        $this->assertSame($query, $params->query, $message);
424
        $this->assertSame($queryId, $params->queryId, $message);
425
        $this->assertSame($variables, $params->variables, $message);
426
        $this->assertSame($operation, $params->operation, $message);
427
    }
428
}
429