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