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