Passed
Push — master ( a80d38...5ac3ee )
by Vladimir
07:04 queued 01:38
created

parseRawFormUrlencodedRequest()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 10
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Tests\Server;
6
7
use GraphQL\Error\InvariantViolation;
8
use GraphQL\Server\Helper;
9
use GraphQL\Server\OperationParams;
10
use GraphQL\Server\RequestError;
11
use GraphQL\Tests\Server\Psr7\PsrRequestStub;
12
use GraphQL\Tests\Server\Psr7\PsrStreamStub;
13
use PHPUnit\Framework\TestCase;
14
use function json_decode;
15
use function json_encode;
16
17
class RequestParsingTest extends TestCase
18
{
19
    public function testParsesGraphqlRequest() : void
20
    {
21
        $query  = '{my query}';
22
        $parsed = [
23
            'raw' => $this->parseRawRequest('application/graphql', $query),
24
            'psr' => $this->parsePsrRequest('application/graphql', $query),
25
        ];
26
27
        foreach ($parsed as $source => $parsedBody) {
28
            self::assertValidOperationParams($parsedBody, $query, null, null, null, null, $source);
29
            self::assertFalse($parsedBody->isReadOnly(), $source);
30
        }
31
    }
32
33
    /**
34
     * @param string $contentType
35
     * @param string $content
36
     *
37
     * @return OperationParams|OperationParams[]
38
     */
39
    private function parseRawRequest($contentType, $content, string $method = 'POST')
40
    {
41
        $_SERVER['CONTENT_TYPE']   = $contentType;
42
        $_SERVER['REQUEST_METHOD'] = $method;
43
44
        $helper = new Helper();
45
46
        return $helper->parseHttpRequest(static function () use ($content) {
47
            return $content;
48
        });
49
    }
50
51
    /**
52
     * @param string $contentType
53
     * @param string $content
54
     *
55
     * @return OperationParams|OperationParams[]
56
     */
57
    private function parsePsrRequest($contentType, $content, string $method = 'POST')
58
    {
59
        $psrRequestBody          = new PsrStreamStub();
60
        $psrRequestBody->content = $content;
61
62
        $psrRequest                          = new PsrRequestStub();
63
        $psrRequest->headers['content-type'] = [$contentType];
64
        $psrRequest->method                  = $method;
65
        $psrRequest->body                    = $psrRequestBody;
66
67
        if ($contentType === 'application/json') {
68
            $parsedBody = json_decode($content, true);
69
            $parsedBody = $parsedBody === false ? null : $parsedBody;
70
        } else {
71
            $parsedBody = null;
72
        }
73
74
        $psrRequest->parsedBody = $parsedBody;
75
76
        $helper = new Helper();
77
78
        return $helper->parsePsrRequest($psrRequest);
79
    }
80
81
    /**
82
     * @param OperationParams $params
83
     * @param string          $query
84
     * @param string          $queryId
85
     * @param mixed|null      $variables
86
     * @param string          $operation
87
     */
88
    private static function assertValidOperationParams(
89
        $params,
90
        $query,
91
        $queryId = null,
92
        $variables = null,
93
        $operation = null,
94
        $extensions = null,
95
        $message = ''
96
    ) {
97
        self::assertInstanceOf(OperationParams::class, $params, $message);
98
99
        self::assertSame($query, $params->query, $message);
100
        self::assertSame($queryId, $params->queryId, $message);
101
        self::assertSame($variables, $params->variables, $message);
102
        self::assertSame($operation, $params->operation, $message);
103
        self::assertSame($extensions, $params->extensions, $message);
104
    }
105
106
    public function testParsesUrlencodedRequest() : void
107
    {
108
        $query     = '{my query}';
109
        $variables = ['test' => 1, 'test2' => 2];
110
        $operation = 'op';
111
112
        $post   = [
113
            'query'         => $query,
114
            'variables'     => $variables,
115
            'operationName' => $operation,
116
        ];
117
        $parsed = [
118
            'raw' => $this->parseRawFormUrlencodedRequest($post),
119
            'psr' => $this->parsePsrFormUrlEncodedRequest($post),
120
        ];
121
122
        foreach ($parsed as $method => $parsedBody) {
123
            self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method);
124
            self::assertFalse($parsedBody->isReadOnly(), $method);
125
        }
126
    }
127
128
    /**
129
     * @param mixed[] $postValue
130
     *
131
     * @return OperationParams|OperationParams[]
132
     */
133
    private function parseRawFormUrlencodedRequest($postValue)
134
    {
135
        $_SERVER['CONTENT_TYPE']   = 'application/x-www-form-urlencoded';
136
        $_SERVER['REQUEST_METHOD'] = 'POST';
137
        $_POST                     = $postValue;
138
139
        $helper = new Helper();
140
141
        return $helper->parseHttpRequest(static function () {
142
            throw new InvariantViolation("Shouldn't read from php://input for urlencoded request");
143
        });
144
    }
145
146
    /**
147
     * @param mixed[] $postValue
148
     *
149
     * @return OperationParams[]|OperationParams
150
     */
151
    private function parsePsrFormUrlEncodedRequest($postValue)
152
    {
153
        $psrRequest                          = new PsrRequestStub();
154
        $psrRequest->headers['content-type'] = ['application/x-www-form-urlencoded'];
155
        $psrRequest->method                  = 'POST';
156
        $psrRequest->parsedBody              = $postValue;
157
158
        $helper = new Helper();
159
160
        return $helper->parsePsrRequest($psrRequest);
161
    }
162
163
    public function testParsesGetRequest() : void
164
    {
165
        $query     = '{my query}';
166
        $variables = ['test' => 1, 'test2' => 2];
167
        $operation = 'op';
168
169
        $get    = [
170
            'query'         => $query,
171
            'variables'     => $variables,
172
            'operationName' => $operation,
173
        ];
174
        $parsed = [
175
            'raw' => $this->parseRawGetRequest($get),
176
            'psr' => $this->parsePsrGetRequest($get),
177
        ];
178
179
        foreach ($parsed as $method => $parsedBody) {
180
            self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method);
181
            self::assertTrue($parsedBody->isReadonly(), $method);
182
        }
183
    }
184
185
    /**
186
     * @param mixed[] $getValue
187
     *
188
     * @return OperationParams
189
     */
190
    private function parseRawGetRequest($getValue)
191
    {
192
        $_SERVER['REQUEST_METHOD'] = 'GET';
193
        $_GET                      = $getValue;
194
195
        $helper = new Helper();
196
197
        return $helper->parseHttpRequest(static function () {
198
            throw new InvariantViolation("Shouldn't read from php://input for urlencoded request");
199
        });
200
    }
201
202
    /**
203
     * @param mixed[] $getValue
204
     *
205
     * @return OperationParams[]|OperationParams
206
     */
207
    private function parsePsrGetRequest($getValue)
208
    {
209
        $psrRequest              = new PsrRequestStub();
210
        $psrRequest->method      = 'GET';
211
        $psrRequest->queryParams = $getValue;
212
213
        $helper = new Helper();
214
215
        return $helper->parsePsrRequest($psrRequest);
216
    }
217
218
    public function testParsesMultipartFormdataRequest() : void
219
    {
220
        $query     = '{my query}';
221
        $variables = ['test' => 1, 'test2' => 2];
222
        $operation = 'op';
223
224
        $post   = [
225
            'query'         => $query,
226
            'variables'     => $variables,
227
            'operationName' => $operation,
228
        ];
229
        $parsed = [
230
            'raw' => $this->parseRawMultipartFormDataRequest($post),
231
            'psr' => $this->parsePsrMultipartFormDataRequest($post),
232
        ];
233
234
        foreach ($parsed as $method => $parsedBody) {
235
            self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method);
236
            self::assertFalse($parsedBody->isReadOnly(), $method);
237
        }
238
    }
239
240
    /**
241
     * @param mixed[] $postValue
242
     *
243
     * @return OperationParams|OperationParams[]
244
     */
245
    private function parseRawMultipartFormDataRequest($postValue)
246
    {
247
        $_SERVER['CONTENT_TYPE']   = 'multipart/form-data; boundary=----FormBoundary';
248
        $_SERVER['REQUEST_METHOD'] = 'POST';
249
        $_POST                     = $postValue;
250
251
        $helper = new Helper();
252
253
        return $helper->parseHttpRequest(static function () {
254
            throw new InvariantViolation("Shouldn't read from php://input for multipart/form-data request");
255
        });
256
    }
257
258
    /**
259
     * @param mixed[] $postValue
260
     *
261
     * @return OperationParams|OperationParams[]
262
     */
263
    private function parsePsrMultipartFormDataRequest($postValue)
264
    {
265
        $psrRequest                          = new PsrRequestStub();
266
        $psrRequest->headers['content-type'] = ['multipart/form-data; boundary=----FormBoundary'];
267
        $psrRequest->method                  = 'POST';
268
        $psrRequest->parsedBody              = $postValue;
269
270
        $helper = new Helper();
271
272
        return $helper->parsePsrRequest($psrRequest);
273
    }
274
275
    public function testParsesJSONRequest() : void
276
    {
277
        $query     = '{my query}';
278
        $variables = ['test' => 1, 'test2' => 2];
279
        $operation = 'op';
280
281
        $body   = [
282
            'query'         => $query,
283
            'variables'     => $variables,
284
            'operationName' => $operation,
285
        ];
286
        $parsed = [
287
            'raw' => $this->parseRawRequest('application/json', json_encode($body)),
288
            'psr' => $this->parsePsrRequest('application/json', json_encode($body)),
289
        ];
290
        foreach ($parsed as $method => $parsedBody) {
291
            self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method);
292
            self::assertFalse($parsedBody->isReadOnly(), $method);
293
        }
294
    }
295
296
    public function testParsesParamsAsJSON() : void
297
    {
298
        $query      = '{my query}';
299
        $variables  = ['test1' => 1, 'test2' => 2];
300
        $extensions = ['test3' => 3, 'test4' => 4];
301
        $operation  = 'op';
302
303
        $body   = [
304
            'query'         => $query,
305
            'extensions'    => json_encode($extensions),
306
            'variables'     => json_encode($variables),
307
            'operationName' => $operation,
308
        ];
309
        $parsed = [
310
            'raw' => $this->parseRawRequest('application/json', json_encode($body)),
311
            'psr' => $this->parsePsrRequest('application/json', json_encode($body)),
312
        ];
313
        foreach ($parsed as $method => $parsedBody) {
314
            self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $extensions, $method);
315
            self::assertFalse($parsedBody->isReadOnly(), $method);
316
        }
317
    }
318
319
    public function testIgnoresInvalidVariablesJson() : void
320
    {
321
        $query     = '{my query}';
322
        $variables = '"some invalid json';
323
        $operation = 'op';
324
325
        $body   = [
326
            'query'         => $query,
327
            'variables'     => $variables,
328
            'operationName' => $operation,
329
        ];
330
        $parsed = [
331
            'raw' => $this->parseRawRequest('application/json', json_encode($body)),
332
            'psr' => $this->parsePsrRequest('application/json', json_encode($body)),
333
        ];
334
        foreach ($parsed as $method => $parsedBody) {
335
            self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method);
336
            self::assertFalse($parsedBody->isReadOnly(), $method);
337
        }
338
    }
339
340
    public function testParsesApolloPersistedQueryJSONRequest() : void
341
    {
342
        $queryId    = 'my-query-id';
343
        $extensions = ['persistedQuery' => ['sha256Hash' => $queryId]];
344
        $variables  = ['test' => 1, 'test2' => 2];
345
        $operation  = 'op';
346
347
        $body   = [
348
            'extensions'    => $extensions,
349
            'variables'     => $variables,
350
            'operationName' => $operation,
351
        ];
352
        $parsed = [
353
            'raw' => $this->parseRawRequest('application/json', json_encode($body)),
354
            'psr' => $this->parsePsrRequest('application/json', json_encode($body)),
355
        ];
356
        foreach ($parsed as $method => $parsedBody) {
357
            self::assertValidOperationParams($parsedBody, null, $queryId, $variables, $operation, $extensions, $method);
358
            self::assertFalse($parsedBody->isReadOnly(), $method);
359
        }
360
    }
361
362
    public function testParsesBatchJSONRequest() : void
363
    {
364
        $body   = [
365
            [
366
                'query'         => '{my query}',
367
                'variables'     => ['test' => 1, 'test2' => 2],
368
                'operationName' => 'op',
369
            ],
370
            [
371
                'queryId'       => 'my-query-id',
372
                'variables'     => ['test' => 1, 'test2' => 2],
373
                'operationName' => 'op2',
374
            ],
375
        ];
376
        $parsed = [
377
            'raw' => $this->parseRawRequest('application/json', json_encode($body)),
378
            'psr' => $this->parsePsrRequest('application/json', json_encode($body)),
379
        ];
380
        foreach ($parsed as $method => $parsedBody) {
381
            self::assertInternalType('array', $parsedBody, $method);
0 ignored issues
show
Deprecated Code introduced by
The function PHPUnit\Framework\Assert::assertInternalType() has been deprecated: https://github.com/sebastianbergmann/phpunit/issues/3369 ( Ignorable by Annotation )

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

381
            /** @scrutinizer ignore-deprecated */ self::assertInternalType('array', $parsedBody, $method);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
382
            self::assertCount(2, $parsedBody, $method);
383
            self::assertValidOperationParams(
384
                $parsedBody[0],
385
                $body[0]['query'],
386
                null,
387
                $body[0]['variables'],
388
                $body[0]['operationName'],
389
                null,
390
                $method
391
            );
392
            self::assertValidOperationParams(
393
                $parsedBody[1],
394
                null,
395
                $body[1]['queryId'],
396
                $body[1]['variables'],
397
                $body[1]['operationName'],
398
                null,
399
                $method
400
            );
401
        }
402
    }
403
404
    public function testFailsParsingInvalidRawJsonRequestRaw() : void
405
    {
406
        $body = 'not really{} a json';
407
408
        $this->expectException(RequestError::class);
409
        $this->expectExceptionMessage('Could not parse JSON: Syntax error');
410
        $this->parseRawRequest('application/json', $body);
411
    }
412
413
    public function testFailsParsingInvalidRawJsonRequestPsr() : void
414
    {
415
        $body = 'not really{} a json';
416
417
        $this->expectException(InvariantViolation::class);
418
        $this->expectExceptionMessage('PSR-7 request is expected to provide parsed body for "application/json" requests but got null');
419
        $this->parsePsrRequest('application/json', $body);
420
    }
421
422
    public function testFailsParsingNonPreParsedPsrRequest() : void
423
    {
424
        try {
425
            $this->parsePsrRequest('application/json', json_encode(null));
426
            self::fail('Expected exception not thrown');
427
        } catch (InvariantViolation $e) {
428
            // Expecting parsing exception to be thrown somewhere else:
429
            self::assertEquals(
430
                'PSR-7 request is expected to provide parsed body for "application/json" requests but got null',
431
                $e->getMessage()
432
            );
433
        }
434
    }
435
436
    /**
437
     * There is no equivalent for psr request, because it should throw
438
     */
439
    public function testFailsParsingNonArrayOrObjectJsonRequestRaw() : void
440
    {
441
        $body = '"str"';
442
443
        $this->expectException(RequestError::class);
444
        $this->expectExceptionMessage('GraphQL Server expects JSON object or array, but got "str"');
445
        $this->parseRawRequest('application/json', $body);
446
    }
447
448
    public function testFailsParsingNonArrayOrObjectJsonRequestPsr() : void
449
    {
450
        $body = '"str"';
451
452
        $this->expectException(RequestError::class);
453
        $this->expectExceptionMessage('GraphQL Server expects JSON object or array, but got "str"');
454
        $this->parsePsrRequest('application/json', $body);
455
    }
456
457
    public function testFailsParsingInvalidContentTypeRaw() : void
458
    {
459
        $contentType = 'not-supported-content-type';
460
        $body        = 'test';
461
462
        $this->expectException(RequestError::class);
463
        $this->expectExceptionMessage('Unexpected content type: "not-supported-content-type"');
464
        $this->parseRawRequest($contentType, $body);
465
    }
466
467
    public function testFailsParsingInvalidContentTypePsr() : void
468
    {
469
        $contentType = 'not-supported-content-type';
470
        $body        = 'test';
471
472
        $this->expectException(RequestError::class);
473
        $this->expectExceptionMessage('Unexpected content type: "not-supported-content-type"');
474
        $this->parseRawRequest($contentType, $body);
475
    }
476
477
    public function testFailsWithMissingContentTypeRaw() : void
478
    {
479
        $this->expectException(RequestError::class);
480
        $this->expectExceptionMessage('Missing "Content-Type" header');
481
        $this->parseRawRequest(null, 'test');
482
    }
483
484
    public function testFailsWithMissingContentTypePsr() : void
485
    {
486
        $this->expectException(RequestError::class);
487
        $this->expectExceptionMessage('Missing "Content-Type" header');
488
        $this->parsePsrRequest(null, 'test');
489
    }
490
491
    public function testFailsOnMethodsOtherThanPostOrGetRaw() : void
492
    {
493
        $this->expectException(RequestError::class);
494
        $this->expectExceptionMessage('HTTP Method "PUT" is not supported');
495
        $this->parseRawRequest('application/json', json_encode([]), 'PUT');
496
    }
497
498
    public function testFailsOnMethodsOtherThanPostOrGetPsr() : void
499
    {
500
        $this->expectException(RequestError::class);
501
        $this->expectExceptionMessage('HTTP Method "PUT" is not supported');
502
        $this->parsePsrRequest('application/json', json_encode([]), 'PUT');
503
    }
504
}
505