Merge "Chinese Conversion Table Update 2017-6"
[lhc/web/wiklou.git] / tests / phpunit / includes / api / ApiUploadTest.php
1 <?php
2 /**
3 * n.b. Ensure that you can write to the images/ directory as the
4 * user that will run tests.
5 *
6 * Note for reviewers: this intentionally duplicates functionality already in
7 * "ApiSetup" and so on. This framework works better IMO and has less
8 * strangeness (such as test cases inheriting from "ApiSetup"...) (and in the
9 * case of the other Upload tests, this flat out just actually works... )
10 *
11 * @todo Port the other Upload tests, and other API tests to this framework
12 *
13 * @todo Broken test, reports false errors from time to time.
14 * See https://phabricator.wikimedia.org/T28169
15 *
16 * @todo This is pretty sucky... needs to be prettified.
17 *
18 * @group API
19 * @group Database
20 * @group medium
21 * @group Broken
22 *
23 * @covers ApiUpload
24 */
25 class ApiUploadTest extends ApiTestCaseUpload {
26 /**
27 * Testing login
28 * XXX this is a funny way of getting session context
29 */
30 public function testLogin() {
31 $user = self::$users['uploader'];
32 $userName = $user->getUser()->getName();
33 $password = $user->getPassword();
34
35 $params = [
36 'action' => 'login',
37 'lgname' => $userName,
38 'lgpassword' => $password
39 ];
40 list( $result, , $session ) = $this->doApiRequest( $params );
41 $this->assertArrayHasKey( "login", $result );
42 $this->assertArrayHasKey( "result", $result['login'] );
43 $this->assertEquals( "NeedToken", $result['login']['result'] );
44 $token = $result['login']['token'];
45
46 $params = [
47 'action' => 'login',
48 'lgtoken' => $token,
49 'lgname' => $userName,
50 'lgpassword' => $password
51 ];
52 list( $result, , $session ) = $this->doApiRequest( $params, $session );
53 $this->assertArrayHasKey( "login", $result );
54 $this->assertArrayHasKey( "result", $result['login'] );
55 $this->assertEquals( "Success", $result['login']['result'] );
56
57 $this->assertNotEmpty( $session, 'API Login must return a session' );
58
59 return $session;
60 }
61
62 /**
63 * @depends testLogin
64 */
65 public function testUploadRequiresToken( $session ) {
66 $exception = false;
67 try {
68 $this->doApiRequest( [
69 'action' => 'upload'
70 ] );
71 } catch ( ApiUsageException $e ) {
72 $exception = true;
73 $this->assertContains( 'The "token" parameter must be set', $e->getMessage() );
74 }
75 $this->assertTrue( $exception, "Got exception" );
76 }
77
78 /**
79 * @depends testLogin
80 */
81 public function testUploadMissingParams( $session ) {
82 $exception = false;
83 try {
84 $this->doApiRequestWithToken( [
85 'action' => 'upload',
86 ], $session, self::$users['uploader']->getUser() );
87 } catch ( ApiUsageException $e ) {
88 $exception = true;
89 $this->assertEquals(
90 'One of the parameters "filekey", "file" and "url" is required.',
91 $e->getMessage()
92 );
93 }
94 $this->assertTrue( $exception, "Got exception" );
95 }
96
97 /**
98 * @depends testLogin
99 */
100 public function testUpload( $session ) {
101 $extension = 'png';
102 $mimeType = 'image/png';
103
104 try {
105 $randomImageGenerator = new RandomImageGenerator();
106 $filePaths = $randomImageGenerator->writeImages( 1, $extension, $this->getNewTempDirectory() );
107 } catch ( Exception $e ) {
108 $this->markTestIncomplete( $e->getMessage() );
109 }
110
111 /** @var array $filePaths */
112 $filePath = $filePaths[0];
113 $fileSize = filesize( $filePath );
114 $fileName = basename( $filePath );
115
116 $this->deleteFileByFileName( $fileName );
117 $this->deleteFileByContent( $filePath );
118
119 if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) {
120 $this->markTestIncomplete( "Couldn't upload file!\n" );
121 }
122
123 $params = [
124 'action' => 'upload',
125 'filename' => $fileName,
126 'file' => 'dummy content',
127 'comment' => 'dummy comment',
128 'text' => "This is the page text for $fileName",
129 ];
130
131 $exception = false;
132 try {
133 list( $result, , ) = $this->doApiRequestWithToken( $params, $session,
134 self::$users['uploader']->getUser() );
135 } catch ( ApiUsageException $e ) {
136 $exception = true;
137 }
138 $this->assertTrue( isset( $result['upload'] ) );
139 $this->assertEquals( 'Success', $result['upload']['result'] );
140 $this->assertEquals( $fileSize, (int)$result['upload']['imageinfo']['size'] );
141 $this->assertEquals( $mimeType, $result['upload']['imageinfo']['mime'] );
142 $this->assertFalse( $exception );
143
144 // clean up
145 $this->deleteFileByFileName( $fileName );
146 }
147
148 /**
149 * @depends testLogin
150 */
151 public function testUploadZeroLength( $session ) {
152 $mimeType = 'image/png';
153
154 $filePath = $this->getNewTempFile();
155 $fileName = "apiTestUploadZeroLength.png";
156
157 $this->deleteFileByFileName( $fileName );
158
159 if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) {
160 $this->markTestIncomplete( "Couldn't upload file!\n" );
161 }
162
163 $params = [
164 'action' => 'upload',
165 'filename' => $fileName,
166 'file' => 'dummy content',
167 'comment' => 'dummy comment',
168 'text' => "This is the page text for $fileName",
169 ];
170
171 $exception = false;
172 try {
173 $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->getUser() );
174 } catch ( ApiUsageException $e ) {
175 $this->assertContains( 'The file you submitted was empty', $e->getMessage() );
176 $exception = true;
177 }
178 $this->assertTrue( $exception );
179
180 // clean up
181 $this->deleteFileByFileName( $fileName );
182 }
183
184 /**
185 * @depends testLogin
186 */
187 public function testUploadSameFileName( $session ) {
188 $extension = 'png';
189 $mimeType = 'image/png';
190
191 try {
192 $randomImageGenerator = new RandomImageGenerator();
193 $filePaths = $randomImageGenerator->writeImages( 2, $extension, $this->getNewTempDirectory() );
194 } catch ( Exception $e ) {
195 $this->markTestIncomplete( $e->getMessage() );
196 }
197
198 // we'll reuse this filename
199 /** @var array $filePaths */
200 $fileName = basename( $filePaths[0] );
201
202 // clear any other files with the same name
203 $this->deleteFileByFileName( $fileName );
204
205 // we reuse these params
206 $params = [
207 'action' => 'upload',
208 'filename' => $fileName,
209 'file' => 'dummy content',
210 'comment' => 'dummy comment',
211 'text' => "This is the page text for $fileName",
212 ];
213
214 // first upload .... should succeed
215
216 if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[0] ) ) {
217 $this->markTestIncomplete( "Couldn't upload file!\n" );
218 }
219
220 $exception = false;
221 try {
222 list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
223 self::$users['uploader']->getUser() );
224 } catch ( ApiUsageException $e ) {
225 $exception = true;
226 }
227 $this->assertTrue( isset( $result['upload'] ) );
228 $this->assertEquals( 'Success', $result['upload']['result'] );
229 $this->assertFalse( $exception );
230
231 // second upload with the same name (but different content)
232
233 if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[1] ) ) {
234 $this->markTestIncomplete( "Couldn't upload file!\n" );
235 }
236
237 $exception = false;
238 try {
239 list( $result, , ) = $this->doApiRequestWithToken( $params, $session,
240 self::$users['uploader']->getUser() ); // FIXME: leaks a temporary file
241 } catch ( ApiUsageException $e ) {
242 $exception = true;
243 }
244 $this->assertTrue( isset( $result['upload'] ) );
245 $this->assertEquals( 'Warning', $result['upload']['result'] );
246 $this->assertTrue( isset( $result['upload']['warnings'] ) );
247 $this->assertTrue( isset( $result['upload']['warnings']['exists'] ) );
248 $this->assertFalse( $exception );
249
250 // clean up
251 $this->deleteFileByFileName( $fileName );
252 }
253
254 /**
255 * @depends testLogin
256 */
257 public function testUploadSameContent( $session ) {
258 $extension = 'png';
259 $mimeType = 'image/png';
260
261 try {
262 $randomImageGenerator = new RandomImageGenerator();
263 $filePaths = $randomImageGenerator->writeImages( 1, $extension, $this->getNewTempDirectory() );
264 } catch ( Exception $e ) {
265 $this->markTestIncomplete( $e->getMessage() );
266 }
267
268 /** @var array $filePaths */
269 $fileNames[0] = basename( $filePaths[0] );
270 $fileNames[1] = "SameContentAs" . $fileNames[0];
271
272 // clear any other files with the same name or content
273 $this->deleteFileByContent( $filePaths[0] );
274 $this->deleteFileByFileName( $fileNames[0] );
275 $this->deleteFileByFileName( $fileNames[1] );
276
277 // first upload .... should succeed
278
279 $params = [
280 'action' => 'upload',
281 'filename' => $fileNames[0],
282 'file' => 'dummy content',
283 'comment' => 'dummy comment',
284 'text' => "This is the page text for " . $fileNames[0],
285 ];
286
287 if ( !$this->fakeUploadFile( 'file', $fileNames[0], $mimeType, $filePaths[0] ) ) {
288 $this->markTestIncomplete( "Couldn't upload file!\n" );
289 }
290
291 $exception = false;
292 try {
293 list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
294 self::$users['uploader']->getUser() );
295 } catch ( ApiUsageException $e ) {
296 $exception = true;
297 }
298 $this->assertTrue( isset( $result['upload'] ) );
299 $this->assertEquals( 'Success', $result['upload']['result'] );
300 $this->assertFalse( $exception );
301
302 // second upload with the same content (but different name)
303
304 if ( !$this->fakeUploadFile( 'file', $fileNames[1], $mimeType, $filePaths[0] ) ) {
305 $this->markTestIncomplete( "Couldn't upload file!\n" );
306 }
307
308 $params = [
309 'action' => 'upload',
310 'filename' => $fileNames[1],
311 'file' => 'dummy content',
312 'comment' => 'dummy comment',
313 'text' => "This is the page text for " . $fileNames[1],
314 ];
315
316 $exception = false;
317 try {
318 list( $result ) = $this->doApiRequestWithToken( $params, $session,
319 self::$users['uploader']->getUser() ); // FIXME: leaks a temporary file
320 } catch ( ApiUsageException $e ) {
321 $exception = true;
322 }
323 $this->assertTrue( isset( $result['upload'] ) );
324 $this->assertEquals( 'Warning', $result['upload']['result'] );
325 $this->assertTrue( isset( $result['upload']['warnings'] ) );
326 $this->assertTrue( isset( $result['upload']['warnings']['duplicate'] ) );
327 $this->assertFalse( $exception );
328
329 // clean up
330 $this->deleteFileByFileName( $fileNames[0] );
331 $this->deleteFileByFileName( $fileNames[1] );
332 }
333
334 /**
335 * @depends testLogin
336 */
337 public function testUploadStash( $session ) {
338 $this->setMwGlobals( [
339 'wgUser' => self::$users['uploader']->getUser(), // @todo FIXME: still used somewhere
340 ] );
341
342 $extension = 'png';
343 $mimeType = 'image/png';
344
345 try {
346 $randomImageGenerator = new RandomImageGenerator();
347 $filePaths = $randomImageGenerator->writeImages( 1, $extension, $this->getNewTempDirectory() );
348 } catch ( Exception $e ) {
349 $this->markTestIncomplete( $e->getMessage() );
350 }
351
352 /** @var array $filePaths */
353 $filePath = $filePaths[0];
354 $fileSize = filesize( $filePath );
355 $fileName = basename( $filePath );
356
357 $this->deleteFileByFileName( $fileName );
358 $this->deleteFileByContent( $filePath );
359
360 if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) {
361 $this->markTestIncomplete( "Couldn't upload file!\n" );
362 }
363
364 $params = [
365 'action' => 'upload',
366 'stash' => 1,
367 'filename' => $fileName,
368 'file' => 'dummy content',
369 'comment' => 'dummy comment',
370 'text' => "This is the page text for $fileName",
371 ];
372
373 $exception = false;
374 try {
375 list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
376 self::$users['uploader']->getUser() ); // FIXME: leaks a temporary file
377 } catch ( ApiUsageException $e ) {
378 $exception = true;
379 }
380 $this->assertFalse( $exception );
381 $this->assertTrue( isset( $result['upload'] ) );
382 $this->assertEquals( 'Success', $result['upload']['result'] );
383 $this->assertEquals( $fileSize, (int)$result['upload']['imageinfo']['size'] );
384 $this->assertEquals( $mimeType, $result['upload']['imageinfo']['mime'] );
385 $this->assertTrue( isset( $result['upload']['filekey'] ) );
386 $this->assertEquals( $result['upload']['sessionkey'], $result['upload']['filekey'] );
387 $filekey = $result['upload']['filekey'];
388
389 // it should be visible from Special:UploadStash
390 // XXX ...but how to test this, with a fake WebRequest with the session?
391
392 // now we should try to release the file from stash
393 $params = [
394 'action' => 'upload',
395 'filekey' => $filekey,
396 'filename' => $fileName,
397 'comment' => 'dummy comment',
398 'text' => "This is the page text for $fileName, altered",
399 ];
400
401 $this->clearFakeUploads();
402 $exception = false;
403 try {
404 list( $result ) = $this->doApiRequestWithToken( $params, $session,
405 self::$users['uploader']->getUser() );
406 } catch ( ApiUsageException $e ) {
407 $exception = true;
408 }
409 $this->assertTrue( isset( $result['upload'] ) );
410 $this->assertEquals( 'Success', $result['upload']['result'] );
411 $this->assertFalse( $exception, "No ApiUsageException exception." );
412
413 // clean up
414 $this->deleteFileByFileName( $fileName );
415 }
416
417 /**
418 * @depends testLogin
419 */
420 public function testUploadChunks( $session ) {
421 $this->setMwGlobals( [
422 // @todo FIXME: still used somewhere
423 'wgUser' => self::$users['uploader']->getUser(),
424 ] );
425
426 $chunkSize = 1048576;
427 // Download a large image file
428 // (using RandomImageGenerator for large files is not stable)
429 // @todo Don't download files from wikimedia.org
430 $mimeType = 'image/jpeg';
431 $url = 'http://upload.wikimedia.org/wikipedia/commons/'
432 . 'e/ed/Oberaargletscher_from_Oberaar%2C_2010_07.JPG';
433 $filePath = $this->getNewTempDirectory() . '/Oberaargletscher_from_Oberaar.jpg';
434 try {
435 copy( $url, $filePath );
436 } catch ( Exception $e ) {
437 $this->markTestIncomplete( $e->getMessage() );
438 }
439
440 $fileSize = filesize( $filePath );
441 $fileName = basename( $filePath );
442
443 $this->deleteFileByFileName( $fileName );
444 $this->deleteFileByContent( $filePath );
445
446 // Base upload params:
447 $params = [
448 'action' => 'upload',
449 'stash' => 1,
450 'filename' => $fileName,
451 'filesize' => $fileSize,
452 'offset' => 0,
453 ];
454
455 // Upload chunks
456 $chunkSessionKey = false;
457 $resultOffset = 0;
458 // Open the file:
459 MediaWiki\suppressWarnings();
460 $handle = fopen( $filePath, "r" );
461 MediaWiki\restoreWarnings();
462
463 if ( $handle === false ) {
464 $this->markTestIncomplete( "could not open file: $filePath" );
465 }
466
467 while ( !feof( $handle ) ) {
468 // Get the current chunk
469 MediaWiki\suppressWarnings();
470 $chunkData = fread( $handle, $chunkSize );
471 MediaWiki\restoreWarnings();
472
473 // Upload the current chunk into the $_FILE object:
474 $this->fakeUploadChunk( 'chunk', 'blob', $mimeType, $chunkData );
475
476 // Check for chunkSessionKey
477 if ( !$chunkSessionKey ) {
478 // Upload fist chunk ( and get the session key )
479 try {
480 list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
481 self::$users['uploader']->getUser() );
482 } catch ( ApiUsageException $e ) {
483 $this->markTestIncomplete( $e->getMessage() );
484 }
485 // Make sure we got a valid chunk continue:
486 $this->assertTrue( isset( $result['upload'] ) );
487 $this->assertTrue( isset( $result['upload']['filekey'] ) );
488 // If we don't get a session key mark test incomplete.
489 if ( !isset( $result['upload']['filekey'] ) ) {
490 $this->markTestIncomplete( "no filekey provided" );
491 }
492 $chunkSessionKey = $result['upload']['filekey'];
493 $this->assertEquals( 'Continue', $result['upload']['result'] );
494 // First chunk should have chunkSize == offset
495 $this->assertEquals( $chunkSize, $result['upload']['offset'] );
496 $resultOffset = $result['upload']['offset'];
497 continue;
498 }
499 // Filekey set to chunk session
500 $params['filekey'] = $chunkSessionKey;
501 // Update the offset ( always add chunkSize for subquent chunks
502 // should be in-sync with $result['upload']['offset'] )
503 $params['offset'] += $chunkSize;
504 // Make sure param offset is insync with resultOffset:
505 $this->assertEquals( $resultOffset, $params['offset'] );
506 // Upload current chunk
507 try {
508 list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
509 self::$users['uploader']->getUser() );
510 } catch ( ApiUsageException $e ) {
511 $this->markTestIncomplete( $e->getMessage() );
512 }
513 // Make sure we got a valid chunk continue:
514 $this->assertTrue( isset( $result['upload'] ) );
515 $this->assertTrue( isset( $result['upload']['filekey'] ) );
516
517 // Check if we were on the last chunk:
518 if ( $params['offset'] + $chunkSize >= $fileSize ) {
519 $this->assertEquals( 'Success', $result['upload']['result'] );
520 break;
521 } else {
522 $this->assertEquals( 'Continue', $result['upload']['result'] );
523 // update $resultOffset
524 $resultOffset = $result['upload']['offset'];
525 }
526 }
527 fclose( $handle );
528
529 // Check that we got a valid file result:
530 wfDebug( __METHOD__
531 . " hohoh filesize {$fileSize} info {$result['upload']['imageinfo']['size']}\n\n" );
532 $this->assertEquals( $fileSize, $result['upload']['imageinfo']['size'] );
533 $this->assertEquals( $mimeType, $result['upload']['imageinfo']['mime'] );
534 $this->assertTrue( isset( $result['upload']['filekey'] ) );
535 $filekey = $result['upload']['filekey'];
536
537 // Now we should try to release the file from stash
538 $params = [
539 'action' => 'upload',
540 'filekey' => $filekey,
541 'filename' => $fileName,
542 'comment' => 'dummy comment',
543 'text' => "This is the page text for $fileName, altered",
544 ];
545 $this->clearFakeUploads();
546 $exception = false;
547 try {
548 list( $result ) = $this->doApiRequestWithToken( $params, $session,
549 self::$users['uploader']->getUser() );
550 } catch ( ApiUsageException $e ) {
551 $exception = true;
552 }
553 $this->assertTrue( isset( $result['upload'] ) );
554 $this->assertEquals( 'Success', $result['upload']['result'] );
555 $this->assertFalse( $exception );
556
557 // clean up
558 $this->deleteFileByFileName( $fileName );
559 }
560 }