Followup r83140: FakeMemCachedClient -> EmptyBagOStuff in tests
[mediawiki.git] / tests / phpunit / includes / api / ApiUploadTest.php
blobe468f66f9c7557c494f64be3a21b9b7e657db2c0
1 <?php
3 /**
4 * @group Database
5 * @group Destructive
6 */
8 /**
9 * n.b. Ensure that you can write to the images/ directory as the
10 * user that will run tests.
13 // Note for reviewers: this intentionally duplicates functionality already in "ApiSetup" and so on.
14 // This framework works better IMO and has less strangeness (such as test cases inheriting from "ApiSetup"...)
15 // (and in the case of the other Upload tests, this flat out just actually works... )
17 // TODO: refactor into several files
18 // TODO: port the other Upload tests, and other API tests to this framework
20 /* Wraps the user object, so we can also retain full access to properties like password if we log in via the API */
21 class ApiTestUser {
22 public $username;
23 public $password;
24 public $email;
25 public $groups;
26 public $user;
28 function __construct( $username, $realname = 'Real Name', $email = 'sample@sample.com', $groups = array() ) {
29 $this->username = $username;
30 $this->realname = $realname;
31 $this->email = $email;
32 $this->groups = $groups;
34 // don't allow user to hardcode or select passwords -- people sometimes run tests
35 // on live wikis. Sometimes we create sysop users in these tests. A sysop user with
36 // a known password would be a Bad Thing.
37 $this->password = User::randomPassword();
39 $this->user = User::newFromName( $this->username );
40 $this->user->load();
42 // In an ideal world we'd have a new wiki (or mock data store) for every single test.
43 // But for now, we just need to create or update the user with the desired properties.
44 // we particularly need the new password, since we just generated it randomly.
45 // In core MediaWiki, there is no functionality to delete users, so this is the best we can do.
46 if ( !$this->user->getID() ) {
47 // create the user
48 $this->user = User::createNew(
49 $this->username, array(
50 "email" => $this->email,
51 "real_name" => $this->realname
54 if ( !$this->user ) {
55 throw new Exception( "error creating user" );
59 // update the user to use the new random password and other details
60 $this->user->setPassword( $this->password );
61 $this->user->setEmail( $this->email );
62 $this->user->setRealName( $this->realname );
63 // remove all groups, replace with any groups specified
64 foreach ( $this->user->getGroups() as $group ) {
65 $this->user->removeGroup( $group );
67 if ( count( $this->groups ) ) {
68 foreach ( $this->groups as $group ) {
69 $this->user->addGroup( $group );
72 $this->user->saveSettings();
78 abstract class ApiTestCase extends MediaWikiTestCase {
79 public static $users;
81 function setUp() {
82 global $wgContLang, $wgAuth, $wgMemc, $wgRequest, $wgUser;
84 $wgMemc = new EmptyBagOStuff();
85 $wgContLang = Language::factory( 'en' );
86 $wgAuth = new StubObject( 'wgAuth', 'AuthPlugin' );
87 $wgRequest = new FauxRequest( array() );
89 self::$users = array(
90 'sysop' => new ApiTestUser(
91 'Apitestsysop',
92 'Api Test Sysop',
93 'api_test_sysop@sample.com',
94 array( 'sysop' )
96 'uploader' => new ApiTestUser(
97 'Apitestuser',
98 'Api Test User',
99 'api_test_user@sample.com',
100 array()
104 $wgUser = self::$users['sysop']->user;
108 protected function doApiRequest( $params, $session = null, $appendModule = false ) {
109 if ( is_null( $session ) ) {
110 $session = array();
113 $request = new FauxRequest( $params, true, $session );
114 $module = new ApiMain( $request, true );
115 $module->execute();
117 return array( $module->getResultData(), $request, $request->getSessionArray() );
121 * Add an edit token to the API request
122 * This is cheating a bit -- we grab a token in the correct format and then add it to the pseudo-session and to the
123 * request, without actually requesting a "real" edit token
124 * @param $params: key-value API params
125 * @param $session: session array
127 protected function doApiRequestWithToken( $params, $session ) {
128 if ( $session['wsToken'] ) {
129 // add edit token to fake session
130 $session['wsEditToken'] = $session['wsToken'];
131 // add token to request parameters
132 $params['token'] = md5( $session['wsToken'] ) . User::EDIT_TOKEN_SUFFIX;
133 return $this->doApiRequest( $params, $session );
134 } else {
135 throw new Exception( "request data not in right format" );
142 * @group Database
143 * @group Destructive
145 * This is pretty sucky... needs to be prettified.
147 class ApiUploadTest extends ApiTestCase {
149 * Fixture -- run before every test
151 public function setUp() {
152 global $wgEnableUploads, $wgEnableAPI;
153 parent::setUp();
155 $wgEnableUploads = true;
156 $wgEnableAPI = true;
157 wfSetupSession();
159 ini_set( 'log_errors', 1 );
160 ini_set( 'error_reporting', 1 );
161 ini_set( 'display_errors', 1 );
163 $this->clearFakeUploads();
167 * Fixture -- run after every test
168 * Clean up temporary files etc.
170 function tearDown() {
175 * Testing login
176 * XXX this is a funny way of getting session context
178 function testLogin() {
179 $user = self::$users['uploader'];
181 $params = array(
182 'action' => 'login',
183 'lgname' => $user->username,
184 'lgpassword' => $user->password
186 list( $result, , $session ) = $this->doApiRequest( $params );
187 $this->assertArrayHasKey( "login", $result );
188 $this->assertArrayHasKey( "result", $result['login'] );
189 $this->assertEquals( "NeedToken", $result['login']['result'] );
190 $token = $result['login']['token'];
192 $params = array(
193 'action' => 'login',
194 'lgtoken' => $token,
195 'lgname' => $user->username,
196 'lgpassword' => $user->password
198 list( $result, , $session ) = $this->doApiRequest( $params, $session );
199 $this->assertArrayHasKey( "login", $result );
200 $this->assertArrayHasKey( "result", $result['login'] );
201 $this->assertEquals( "Success", $result['login']['result'] );
202 $this->assertArrayHasKey( 'lgtoken', $result['login'] );
204 return $session;
209 * @depends testLogin
211 public function testUploadRequiresToken( $session ) {
212 $exception = false;
213 try {
214 $this->doApiRequest( array(
215 'action' => 'upload'
216 ) );
217 } catch ( UsageException $e ) {
218 $exception = true;
219 $this->assertEquals( "The token parameter must be set", $e->getMessage() );
221 $this->assertTrue( $exception, "Got exception" );
225 * @depends testLogin
227 public function testUploadMissingParams( $session ) {
228 global $wgUser;
229 $wgUser = self::$users['uploader']->user;
231 $exception = false;
232 try {
233 $this->doApiRequestWithToken( array(
234 'action' => 'upload',
235 ), $session );
236 } catch ( UsageException $e ) {
237 $exception = true;
238 $this->assertEquals( "One of the parameters sessionkey, file, url, statuskey is required",
239 $e->getMessage() );
241 $this->assertTrue( $exception, "Got exception" );
246 * @depends testLogin
248 public function testUpload( $session ) {
249 global $wgUser;
250 $wgUser = self::$users['uploader']->user;
252 $extension = 'png';
253 $mimeType = 'image/png';
255 try {
256 $randomImageGenerator = new RandomImageGenerator();
258 catch ( Exception $e ) {
259 $this->markTestIncomplete( $e->getMessage() );
262 $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() );
263 $filePath = $filePaths[0];
264 $fileSize = filesize( $filePath );
265 $fileName = basename( $filePath );
267 $this->deleteFileByFileName( $fileName );
268 $this->deleteFileByContent( $filePath );
271 if (! $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) {
272 $this->markTestIncomplete( "Couldn't upload file!\n" );
275 $params = array(
276 'action' => 'upload',
277 'filename' => $fileName,
278 'file' => 'dummy content',
279 'comment' => 'dummy comment',
280 'text' => "This is the page text for $fileName",
283 $exception = false;
284 try {
285 list( $result, , ) = $this->doApiRequestWithToken( $params, $session );
286 } catch ( UsageException $e ) {
287 $exception = true;
289 $this->assertTrue( isset( $result['upload'] ) );
290 $this->assertEquals( 'Success', $result['upload']['result'] );
291 $this->assertEquals( $fileSize, ( int )$result['upload']['imageinfo']['size'] );
292 $this->assertEquals( $mimeType, $result['upload']['imageinfo']['mime'] );
293 $this->assertFalse( $exception );
295 // clean up
296 $this->deleteFileByFilename( $fileName );
297 unlink( $filePath );
302 * @depends testLogin
304 public function testUploadZeroLength( $session ) {
305 global $wgUser;
306 $wgUser = self::$users['uploader']->user;
308 $mimeType = 'image/png';
310 $filePath = tempnam( wfTempDir(), "" );
311 $fileName = "apiTestUploadZeroLength.png";
313 $this->deleteFileByFileName( $fileName );
315 if (! $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) {
316 $this->markTestIncomplete( "Couldn't upload file!\n" );
319 $params = array(
320 'action' => 'upload',
321 'filename' => $fileName,
322 'file' => 'dummy content',
323 'comment' => 'dummy comment',
324 'text' => "This is the page text for $fileName",
327 $exception = false;
328 try {
329 $this->doApiRequestWithToken( $params, $session );
330 } catch ( UsageException $e ) {
331 $this->assertContains( 'The file you submitted was empty', $e->getMessage() );
332 $exception = true;
334 $this->assertTrue( $exception );
336 // clean up
337 $this->deleteFileByFilename( $fileName );
338 unlink( $filePath );
343 * @depends testLogin
345 public function testUploadSameFileName( $session ) {
346 global $wgUser;
347 $wgUser = self::$users['uploader']->user;
349 $extension = 'png';
350 $mimeType = 'image/png';
352 try {
353 $randomImageGenerator = new RandomImageGenerator();
355 catch ( Exception $e ) {
356 $this->markTestIncomplete( $e->getMessage() );
359 $filePaths = $randomImageGenerator->writeImages( 2, $extension, wfTempDir() );
360 // we'll reuse this filename
361 $fileName = basename( $filePaths[0] );
363 // clear any other files with the same name
364 $this->deleteFileByFileName( $fileName );
366 // we reuse these params
367 $params = array(
368 'action' => 'upload',
369 'filename' => $fileName,
370 'file' => 'dummy content',
371 'comment' => 'dummy comment',
372 'text' => "This is the page text for $fileName",
375 // first upload .... should succeed
377 if (! $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[0] ) ) {
378 $this->markTestIncomplete( "Couldn't upload file!\n" );
381 $exception = false;
382 try {
383 list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session );
384 } catch ( UsageException $e ) {
385 $exception = true;
387 $this->assertTrue( isset( $result['upload'] ) );
388 $this->assertEquals( 'Success', $result['upload']['result'] );
389 $this->assertFalse( $exception );
391 // second upload with the same name (but different content)
393 if (! $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[1] ) ) {
394 $this->markTestIncomplete( "Couldn't upload file!\n" );
397 $exception = false;
398 try {
399 list( $result, , ) = $this->doApiRequestWithToken( $params, $session );
400 } catch ( UsageException $e ) {
401 $exception = true;
403 $this->assertTrue( isset( $result['upload'] ) );
404 $this->assertEquals( 'Warning', $result['upload']['result'] );
405 $this->assertTrue( isset( $result['upload']['warnings'] ) );
406 $this->assertTrue( isset( $result['upload']['warnings']['exists'] ) );
407 $this->assertFalse( $exception );
409 // clean up
410 $this->deleteFileByFilename( $fileName );
411 unlink( $filePaths[0] );
412 unlink( $filePaths[1] );
417 * @depends testLogin
419 public function testUploadSameContent( $session ) {
420 global $wgUser;
421 $wgUser = self::$users['uploader']->user;
423 $extension = 'png';
424 $mimeType = 'image/png';
426 try {
427 $randomImageGenerator = new RandomImageGenerator();
429 catch ( Exception $e ) {
430 $this->markTestIncomplete( $e->getMessage() );
432 $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() );
433 $fileNames[0] = basename( $filePaths[0] );
434 $fileNames[1] = "SameContentAs" . $fileNames[0];
436 // clear any other files with the same name or content
437 $this->deleteFileByContent( $filePaths[0] );
438 $this->deleteFileByFileName( $fileNames[0] );
439 $this->deleteFileByFileName( $fileNames[1] );
441 // first upload .... should succeed
443 $params = array(
444 'action' => 'upload',
445 'filename' => $fileNames[0],
446 'file' => 'dummy content',
447 'comment' => 'dummy comment',
448 'text' => "This is the page text for " . $fileNames[0],
451 if (! $this->fakeUploadFile( 'file', $fileNames[0], $mimeType, $filePaths[0] ) ) {
452 $this->markTestIncomplete( "Couldn't upload file!\n" );
455 $exception = false;
456 try {
457 list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
458 } catch ( UsageException $e ) {
459 $exception = true;
461 $this->assertTrue( isset( $result['upload'] ) );
462 $this->assertEquals( 'Success', $result['upload']['result'] );
463 $this->assertFalse( $exception );
466 // second upload with the same content (but different name)
468 if (! $this->fakeUploadFile( 'file', $fileNames[1], $mimeType, $filePaths[0] ) ) {
469 $this->markTestIncomplete( "Couldn't upload file!\n" );
472 $params = array(
473 'action' => 'upload',
474 'filename' => $fileNames[1],
475 'file' => 'dummy content',
476 'comment' => 'dummy comment',
477 'text' => "This is the page text for " . $fileNames[1],
480 $exception = false;
481 try {
482 list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
483 } catch ( UsageException $e ) {
484 $exception = true;
486 $this->assertTrue( isset( $result['upload'] ) );
487 $this->assertEquals( 'Warning', $result['upload']['result'] );
488 $this->assertTrue( isset( $result['upload']['warnings'] ) );
489 $this->assertTrue( isset( $result['upload']['warnings']['duplicate'] ) );
490 $this->assertFalse( $exception );
492 // clean up
493 $this->deleteFileByFilename( $fileNames[0] );
494 $this->deleteFileByFilename( $fileNames[1] );
495 unlink( $filePaths[0] );
500 * @depends testLogin
502 public function testUploadStash( $session ) {
503 global $wgUser;
504 $wgUser = self::$users['uploader']->user;
506 $extension = 'png';
507 $mimeType = 'image/png';
509 try {
510 $randomImageGenerator = new RandomImageGenerator();
512 catch ( Exception $e ) {
513 $this->markTestIncomplete( $e->getMessage() );
516 $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() );
517 $filePath = $filePaths[0];
518 $fileSize = filesize( $filePath );
519 $fileName = basename( $filePath );
521 $this->deleteFileByFileName( $fileName );
522 $this->deleteFileByContent( $filePath );
524 if (! $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) {
525 $this->markTestIncomplete( "Couldn't upload file!\n" );
528 $params = array(
529 'action' => 'upload',
530 'stash' => 1,
531 'filename' => $fileName,
532 'file' => 'dummy content',
533 'comment' => 'dummy comment',
534 'text' => "This is the page text for $fileName",
537 $exception = false;
538 try {
539 list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
540 } catch ( UsageException $e ) {
541 $exception = true;
543 $this->assertFalse( $exception );
544 $this->assertTrue( isset( $result['upload'] ) );
545 $this->assertEquals( 'Success', $result['upload']['result'] );
546 $this->assertEquals( $fileSize, ( int )$result['upload']['imageinfo']['size'] );
547 $this->assertEquals( $mimeType, $result['upload']['imageinfo']['mime'] );
548 $this->assertTrue( isset( $result['upload']['sessionkey'] ) );
549 $sessionkey = $result['upload']['sessionkey'];
551 // it should be visible from Special:UploadStash
552 // XXX ...but how to test this, with a fake WebRequest with the session?
554 // now we should try to release the file from stash
555 $params = array(
556 'action' => 'upload',
557 'sessionkey' => $sessionkey,
558 'filename' => $fileName,
559 'comment' => 'dummy comment',
560 'text' => "This is the page text for $fileName, altered",
563 $this->clearFakeUploads();
564 $exception = false;
565 try {
566 list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
567 } catch ( UsageException $e ) {
568 $exception = true;
570 $this->assertTrue( isset( $result['upload'] ) );
571 $this->assertEquals( 'Success', $result['upload']['result'] );
572 $this->assertFalse( $exception );
574 // clean up
575 $this->deleteFileByFilename( $fileName );
576 unlink( $filePath );
582 * Helper function -- remove files and associated articles by Title
583 * @param $title Title: title to be removed
585 public function deleteFileByTitle( $title ) {
586 if ( $title->exists() ) {
587 $file = wfFindFile( $title, array( 'ignoreRedirect' => true ) );
588 $noOldArchive = ""; // yes this really needs to be set this way
589 $comment = "removing for test";
590 $restrictDeletedVersions = false;
591 $status = FileDeleteForm::doDelete( $title, $file, $noOldArchive, $comment, $restrictDeletedVersions );
592 if ( !$status->isGood() ) {
593 return false;
595 $article = new Article( $title );
596 $article->doDeleteArticle( "removing for test" );
598 // see if it now doesn't exist; reload
599 $title = Title::newFromText( $fileName, NS_FILE );
601 return ! ( $title && $title instanceof Title && $title->exists() );
605 * Helper function -- remove files and associated articles with a particular filename
606 * @param $fileName String: filename to be removed
608 public function deleteFileByFileName( $fileName ) {
609 return $this->deleteFileByTitle( Title::newFromText( $fileName, NS_FILE ) );
614 * Helper function -- given a file on the filesystem, find matching content in the db (and associated articles) and remove them.
615 * @param $filePath String: path to file on the filesystem
617 public function deleteFileByContent( $filePath ) {
618 $hash = File::sha1Base36( $filePath );
619 $dupes = RepoGroup::singleton()->findBySha1( $hash );
620 $success = true;
621 foreach ( $dupes as $dupe ) {
622 $success &= $this->deleteFileByTitle( $dupe->getTitle() );
624 return $success;
628 * Fake an upload by dumping the file into temp space, and adding info to $_FILES.
629 * (This is what PHP would normally do).
630 * @param $fieldName String: name this would have in the upload form
631 * @param $fileName String: name to title this
632 * @param $type String: mime type
633 * @param $filePath String: path where to find file contents
635 function fakeUploadFile( $fieldName, $fileName, $type, $filePath ) {
636 $tmpName = tempnam( wfTempDir(), "" );
637 if ( !file_exists( $filePath ) ) {
638 throw new Exception( "$filePath doesn't exist!" );
641 if ( !copy( $filePath, $tmpName ) ) {
642 throw new Exception( "couldn't copy $filePath to $tmpName" );
645 clearstatcache();
646 $size = filesize( $tmpName );
647 if ( $size === false ) {
648 throw new Exception( "couldn't stat $tmpName" );
651 $_FILES[ $fieldName ] = array(
652 'name' => $fileName,
653 'type' => $type,
654 'tmp_name' => $tmpName,
655 'size' => $size,
656 'error' => null
659 return true;
664 * Remove traces of previous fake uploads
666 function clearFakeUploads() {
667 $_FILES = array();