Passed
Pull Request — master (#413)
by
unknown
05:29
created

testParsesApolloPersistedQueryJSONRequest()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 19
rs 9.7998
c 0
b 0
f 0
cc 2
nc 2
nop 0
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 testParsesVariablesAsJSON() : void
297
    {
298
        $query     = '{my query}';
299
        $variables = ['test' => 1, 'test2' => 2];
300
        $operation = 'op';
301
302
        $body   = [
303
            'query'         => $query,
304
            'variables'     => json_encode($variables),
305
            'operationName' => $operation,
306
        ];
307
        $parsed = [
308
            'raw' => $this->parseRawRequest('application/json', json_encode($body)),
309
            'psr' => $this->parsePsrRequest('application/json', json_encode($body)),
310
        ];
311
        foreach ($parsed as $method => $parsedBody) {
312
            self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method);
313
            self::assertFalse($parsedBody->isReadOnly(), $method);
314
        }
315
    }
316
317
    public function testIgnoresInvalidVariablesJson() : void
318
    {
319
        $query     = '{my query}';
320
        $variables = '"some invalid json';
321
        $operation = 'op';
322
323
        $body   = [
324
            'query'         => $query,
325
            'variables'     => $variables,
326
            'operationName' => $operation,
327
        ];
328
        $parsed = [
329
            'raw' => $this->parseRawRequest('application/json', json_encode($body)),
330
            'psr' => $this->parsePsrRequest('application/json', json_encode($body)),
331
        ];
332
        foreach ($parsed as $method => $parsedBody) {
333
            self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method);
334
            self::assertFalse($parsedBody->isReadOnly(), $method);
335
        }
336
    }
337
338
    public function testParsesApolloPersistedQueryJSONRequest() : void
339
    {
340
        $queryId    = 'my-query-id';
341
        $extensions = ['persistedQuery' => ['sha256Hash' => $queryId]];
342
        $variables  = ['test' => 1, 'test2' => 2];
343
        $operation  = 'op';
344
345
        $body   = [
346
            'extensions'    => $extensions,
347
            'variables'     => $variables,
348
            'operationName' => $operation,
349
        ];
350
        $parsed = [
351
            'raw' => $this->parseRawRequest('application/json', json_encode($body)),
352
            'psr' => $this->parsePsrRequest('application/json', json_encode($body)),
353
        ];
354
        foreach ($parsed as $method => $parsedBody) {
355
            self::assertValidOperationParams($parsedBody, null, $queryId, $variables, $operation, $extensions, $method);
356
            self::assertFalse($parsedBody->isReadOnly(), $method);
357
        }
358
    }
359
360
    public function testParsesBatchJSONRequest() : void
361
    {
362
        $body   = [
363
            [
364
                'query'         => '{my query}',
365
                'variables'     => ['test' => 1, 'test2' => 2],
366
                'operationName' => 'op',
367
            ],
368
            [
369
                'queryId'       => 'my-query-id',
370
                'variables'     => ['test' => 1, 'test2' => 2],
371
                'operationName' => 'op2',
372
            ],
373
        ];
374
        $parsed = [
375
            'raw' => $this->parseRawRequest('application/json', json_encode($body)),
376
            'psr' => $this->parsePsrRequest('application/json', json_encode($body)),
377
        ];
378
        foreach ($parsed as $method => $parsedBody) {
379
            self::assertInternalType('array', $parsedBody, $method);
380
            self::assertCount(2, $parsedBody, $method);
381
            self::assertValidOperationParams(
382
                $parsedBody[0],
383
                $body[0]['query'],
384
                null,
385
                $body[0]['variables'],
386
                $body[0]['operationName'],
387
                null,
388
                $method
389
            );
390
            self::assertValidOperationParams(
391
                $parsedBody[1],
392
                null,
393
                $body[1]['queryId'],
394
                $body[1]['variables'],
395
                $body[1]['operationName'],
396
                null,
397
                $method
398
            );
399
        }
400
    }
401
402
    public function testFailsParsingInvalidRawJsonRequestRaw() : void
403
    {
404
        $body = 'not really{} a json';
405
406
        $this->expectException(RequestError::class);
407
        $this->expectExceptionMessage('Could not parse JSON: Syntax error');
408
        $this->parseRawRequest('application/json', $body);
409
    }
410
411
    public function testFailsParsingInvalidRawJsonRequestPsr() : void
412
    {
413
        $body = 'not really{} a json';
414
415
        $this->expectException(InvariantViolation::class);
416
        $this->expectExceptionMessage('PSR-7 request is expected to provide parsed body for "application/json" requests but got null');
417
        $this->parsePsrRequest('application/json', $body);
418
    }
419
420
    public function testFailsParsingNonPreParsedPsrRequest() : void
421
    {
422
        try {
423
            $this->parsePsrRequest('application/json', json_encode(null));
424
            self::fail('Expected exception not thrown');
425
        } catch (InvariantViolation $e) {
426
            // Expecting parsing exception to be thrown somewhere else:
427
            self::assertEquals(
428
                'PSR-7 request is expected to provide parsed body for "application/json" requests but got null',
429
                $e->getMessage()
430
            );
431
        }
432
    }
433
434
    /**
435
     * There is no equivalent for psr request, because it should throw
436
     */
437
    public function testFailsParsingNonArrayOrObjectJsonRequestRaw() : void
438
    {
439
        $body = '"str"';
440
441
        $this->expectException(RequestError::class);
442
        $this->expectExceptionMessage('GraphQL Server expects JSON object or array, but got "str"');
443
        $this->parseRawRequest('application/json', $body);
444
    }
445
446
    public function testFailsParsingNonArrayOrObjectJsonRequestPsr() : void
447
    {
448
        $body = '"str"';
449
450
        $this->expectException(RequestError::class);
451
        $this->expectExceptionMessage('GraphQL Server expects JSON object or array, but got "str"');
452
        $this->parsePsrRequest('application/json', $body);
453
    }
454
455
    public function testFailsParsingInvalidContentTypeRaw() : void
456
    {
457
        $contentType = 'not-supported-content-type';
458
        $body        = 'test';
459
460
        $this->expectException(RequestError::class);
461
        $this->expectExceptionMessage('Unexpected content type: "not-supported-content-type"');
462
        $this->parseRawRequest($contentType, $body);
463
    }
464
465
    public function testFailsParsingInvalidContentTypePsr() : void
466
    {
467
        $contentType = 'not-supported-content-type';
468
        $body        = 'test';
469
470
        $this->expectException(RequestError::class);
471
        $this->expectExceptionMessage('Unexpected content type: "not-supported-content-type"');
472
        $this->parseRawRequest($contentType, $body);
473
    }
474
475
    public function testFailsWithMissingContentTypeRaw() : void
476
    {
477
        $this->expectException(RequestError::class);
478
        $this->expectExceptionMessage('Missing "Content-Type" header');
479
        $this->parseRawRequest(null, 'test');
480
    }
481
482
    public function testFailsWithMissingContentTypePsr() : void
483
    {
484
        $this->expectException(RequestError::class);
485
        $this->expectExceptionMessage('Missing "Content-Type" header');
486
        $this->parsePsrRequest(null, 'test');
487
    }
488
489
    public function testFailsOnMethodsOtherThanPostOrGetRaw() : void
490
    {
491
        $this->expectException(RequestError::class);
492
        $this->expectExceptionMessage('HTTP Method "PUT" is not supported');
493
        $this->parseRawRequest('application/json', json_encode([]), 'PUT');
494
    }
495
496
    public function testFailsOnMethodsOtherThanPostOrGetPsr() : void
497
    {
498
        $this->expectException(RequestError::class);
499
        $this->expectExceptionMessage('HTTP Method "PUT" is not supported');
500
        $this->parsePsrRequest('application/json', json_encode([]), 'PUT');
501
    }
502
}
503