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