Passed
Push — master ( 23bdfa...6b6f8b )
by Adrien
02:53 queued 01:10
created

php$0 ➔ testRequestWithMapThatIsNotArrayShouldThrows()   A

Complexity

Conditions 1

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQLTests\Upload;
6
7
use GraphQL\Error\DebugFlag;
8
use GraphQL\Error\InvariantViolation;
9
use GraphQL\Executor\ExecutionResult;
10
use GraphQL\Server\RequestError;
11
use GraphQL\Server\StandardServer;
12
use GraphQL\Type\Definition\ObjectType;
13
use GraphQL\Type\Definition\Type;
14
use GraphQL\Type\Schema;
15
use GraphQL\Upload\UploadMiddleware;
16
use GraphQL\Upload\UploadType;
17
use GraphQLTests\Upload\Psr7\PsrUploadedFileStub;
18
use Laminas\Diactoros\Response;
19
use Laminas\Diactoros\ServerRequest;
20
use Laminas\Diactoros\UploadedFile;
21
use PHPUnit\Framework\TestCase;
22
use Psr\Http\Message\ResponseInterface;
23
use Psr\Http\Message\ServerRequestInterface;
24
use Psr\Http\Message\UploadedFileInterface;
25
use Psr\Http\Server\RequestHandlerInterface;
26
use stdClass;
27
28
class UploadMiddlewareTest extends TestCase
29
{
30
    /**
31
     * @var UploadMiddleware
32
     */
33
    private $middleware;
34
35
    protected function setUp(): void
36
    {
37
        $this->middleware = new UploadMiddleware();
38
    }
39
40
    public function testProcess(): void
41
    {
42
        $response = new Response();
43
        $handler = new class($response) implements RequestHandlerInterface {
44
            /**
45
             * @var ResponseInterface
46
             */
47
            private $response;
48
49
            public function __construct(ResponseInterface $response)
50
            {
51
                $this->response = $response;
52
            }
53
54
            public function handle(ServerRequestInterface $request): ResponseInterface
55
            {
56
                return $this->response;
57
            }
58
        };
59
60
        $middleware = $this->getMockBuilder(UploadMiddleware::class)
61
            ->onlyMethods(['processRequest'])
62
            ->getMock();
63
64
        // The request should be forward to processRequest()
65
        $request = new ServerRequest();
66
        $middleware->expects(self::once())->method('processRequest')->with($request);
67
68
        $actualResponse = $middleware->process($request, $handler);
69
        self::assertSame($response, $actualResponse, 'should return the mocked response');
70
    }
71
72
    public function testParsesMultipartRequest(): void
73
    {
74
        $query = '{my query}';
75
        $variables = [
76
            'test' => 1,
77
            'test2' => 2,
78
            'uploads' => [
79
                0 => null,
80
                1 => null,
81
            ],
82
        ];
83
        $map = [
84
            1 => ['variables.uploads.0'],
85
            2 => ['variables.uploads.1'],
86
        ];
87
88
        $file1 = new PsrUploadedFileStub('image.jpg', 'image/jpeg');
89
        $file2 = new PsrUploadedFileStub('foo.txt', 'text/plain');
90
        $files = [
91
            1 => $file1,
92
            2 => $file2,
93
        ];
94
95
        $request = $this->createRequest($query, $variables, $map, $files, 'op');
96
        $processedRequest = $this->middleware->processRequest($request);
97
98
        $variables['uploads'] = [
99
            0 => $file1,
100
            1 => $file2,
101
        ];
102
103
        self::assertSame('application/json', $processedRequest->getHeader('content-type')[0], 'request should have been transformed as application/json');
104
        self::assertSame($variables, $processedRequest->getParsedBody()['variables'], 'uploaded files should have been injected into variables');
105
    }
106
107
    public function testEmptyRequestIsValid(): void
108
    {
109
        $request = $this->createRequest('{my query}', [], [], [], 'op');
110
        $processedRequest = $this->middleware->processRequest($request);
111
112
        self::assertSame('application/json', $processedRequest->getHeader('content-type')[0], 'request should have been transformed as application/json');
113
        self::assertSame([], $processedRequest->getParsedBody()['variables'], 'variables should still be empty');
114
    }
115
116
    public function testNonMultipartRequestAreNotTouched(): void
117
    {
118
        $request = new ServerRequest();
119
        $processedRequest = $this->middleware->processRequest($request);
120
121
        self::assertSame($request, $processedRequest, 'request should have been transformed as application/json');
122
    }
123
124
    public function testEmptyRequestShouldThrows(): void
125
    {
126
        $request = new ServerRequest();
127
        $request = $request
128
            ->withHeader('content-type', ['multipart/form-data'])
129
            ->withParsedBody([]);
130
131
        $this->expectException(InvariantViolation::class);
132
        $this->expectExceptionMessage('PSR-7 request is expected to provide parsed body for "multipart/form-data" requests but got empty array');
133
        $this->middleware->processRequest($request);
134
    }
135
136
    public function testNullRequestShouldThrows(): void
137
    {
138
        $request = new ServerRequest();
139
        $request = $request
140
            ->withHeader('content-type', ['multipart/form-data'])
141
            ->withParsedBody(null);
142
143
        $this->expectException(InvariantViolation::class);
144
        $this->expectExceptionMessage('PSR-7 request is expected to provide parsed body for "multipart/form-data" requests but got null');
145
        $this->middleware->processRequest($request);
146
    }
147
148
    public function testInvalidRequestShouldThrows(): void
149
    {
150
        $request = new ServerRequest();
151
        $request = $request
152
            ->withHeader('content-type', ['multipart/form-data'])
153
            ->withParsedBody(new stdClass());
154
155
        $this->expectException(RequestError::class);
156
        $this->expectExceptionMessage('GraphQL Server expects JSON object or array, but got []');
157
        $this->middleware->processRequest($request);
158
    }
159
160
    public function testOtherContentTypeShouldNotBeTouched(): void
161
    {
162
        $request = new ServerRequest();
163
        $request = $request
164
            ->withHeader('content-type', ['application/json'])
165
            ->withParsedBody(new stdClass());
166
167
        $processedRequest = $this->middleware->processRequest($request);
168
        self::assertSame($request, $processedRequest);
169
    }
170
171
    public function testRequestWithoutMapShouldThrows(): void
172
    {
173
        $request = $this->createRequest('{my query}', [], [], [], 'op');
174
175
        // Remove the map
176
        $body = $request->getParsedBody();
177
        unset($body['map']);
178
        $request = $request->withParsedBody($body);
179
180
        $this->expectException(RequestError::class);
181
        $this->expectExceptionMessage('The request must define a `map`');
182
        $this->middleware->processRequest($request);
183
    }
184
185
    public function testRequestWithMapThatIsNotArrayShouldThrows(): void
186
    {
187
        $request = $this->createRequest('{my query}', [], [], [], 'op');
188
189
        // Replace map with json that is valid but no array
190
        $body = $request->getParsedBody();
191
        $body['map'] = json_encode('foo');
192
        $request = $request->withParsedBody($body);
193
194
        $this->expectException(RequestError::class);
195
        $this->expectExceptionMessage('The `map` key must be a JSON encoded array');
196
        $this->middleware->processRequest($request);
197
    }
198
199
    public function testRequestWithMapThatIsNotValidJsonShouldThrows(): void
200
    {
201
        $request = $this->createRequest('{my query}', [], [], [], 'op');
202
203
        // Replace map with invalid json
204
        $body = $request->getParsedBody();
205
        $body['map'] = 'this is not json';
206
        $request = $request->withParsedBody($body);
207
208
        $this->expectException(RequestError::class);
209
        $this->expectExceptionMessage('The `map` key must be a JSON encoded array');
210
        $this->middleware->processRequest($request);
211
    }
212
213
    public function testCanUploadFileWithStandardServer(): void
214
    {
215
        $query = 'mutation TestUpload($text: String, $file: Upload) {
216
    testUpload(text: $text, file: $file)
217
}';
218
        $variables = [
219
            'text' => 'foo bar',
220
            'file' => null,
221
        ];
222
        $map = [
223
            1 => ['variables.file'],
224
        ];
225
        $files = [
226
            1 => new PsrUploadedFileStub('image.jpg', 'image/jpeg'),
227
        ];
228
229
        $request = $this->createRequest($query, $variables, $map, $files, 'TestUpload');
230
231
        $processedRequest = $this->middleware->processRequest($request);
232
233
        $server = $this->createServer();
234
235
        /** @var ExecutionResult $response */
236
        $response = $server->executePsrRequest($processedRequest);
237
238
        $expected = ['testUpload' => 'Uploaded file was image.jpg (image/jpeg) with description: foo bar'];
239
        self::assertSame($expected, $response->data);
240
    }
241
242
    /**
243
     * @param mixed[] $variables
244
     * @param string[][] $map
245
     * @param UploadedFile[] $files
246
     */
247
    private function createRequest(string $query, array $variables, array $map, array $files, string $operation): ServerRequestInterface
248
    {
249
        $request = new ServerRequest();
250
        $request = $request
251
            ->withMethod('POST')
252
            ->withHeader('content-type', ['multipart/form-data; boundary=----WebKitFormBoundarySl4GaqVa1r8GtAbn'])
253
            ->withParsedBody([
254
                'operations' => json_encode([
255
                    'query' => $query,
256
                    'variables' => $variables,
257
                    'operationName' => $operation,
258
                ]),
259
                'map' => json_encode($map),
260
            ])
261
            ->withUploadedFiles($files);
262
263
        return $request;
264
    }
265
266
    private function createServer(): StandardServer
267
    {
268
        $all = DebugFlag::INCLUDE_DEBUG_MESSAGE
269
            | DebugFlag::INCLUDE_TRACE
270
            | DebugFlag::RETHROW_INTERNAL_EXCEPTIONS
271
            | DebugFlag::RETHROW_UNSAFE_EXCEPTIONS;
272
273
        return new StandardServer([
274
            'debugFlag' => $all,
275
            'schema' => new Schema([
276
                'query' => new ObjectType([
277
                    'name' => 'Query',
278
                ]),
279
                'mutation' => new ObjectType([
280
                    'name' => 'Mutation',
281
                    'fields' => [
282
                        'testUpload' => [
283
                            'type' => Type::string(),
284
                            'args' => [
285
                                'text' => Type::string(),
286
                                'file' => new UploadType(),
287
                            ],
288
                            'resolve' => function ($root, array $args): string {
289
                                /** @var UploadedFileInterface $file */
290
                                $file = $args['file'];
291
                                $this->assertInstanceOf(UploadedFileInterface::class, $file);
292
293
                                // Do something more interesting with the file
294
                                // $file->moveTo('some/folder/in/my/project');
295
296
                                return 'Uploaded file was ' . $file->getClientFilename() . ' (' . $file->getClientMediaType() . ') with description: ' . $args['text'];
297
                            },
298
                        ],
299
                    ],
300
                ]),
301
            ]),
302
        ]);
303
    }
304
}
305