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 require_once( dirname( __FILE__
) . '/RandomImageGenerator.php' );
21 require_once( dirname( __FILE__
) . '/../../../../../includes/User.php' );
23 /* Wraps the user object, so we can also retain full access to properties like password if we log in via the API */
31 function __construct( $username, $realname = 'Real Name', $email = 'sample@sample.com', $groups = array() ) {
32 $this->username
= $username;
33 $this->realname
= $realname;
34 $this->email
= $email;
35 $this->groups
= $groups;
37 // don't allow user to hardcode or select passwords -- people sometimes run tests
38 // on live wikis. Sometimes we create sysop users in these tests. A sysop user with
39 // a known password would be a Bad Thing.
40 $this->password
= User
::randomPassword();
42 $this->user
= User
::newFromName( $this->username
);
45 // In an ideal world we'd have a new wiki (or mock data store) for every single test.
46 // But for now, we just need to create or update the user with the desired properties.
47 // we particularly need the new password, since we just generated it randomly.
48 // In core MediaWiki, there is no functionality to delete users, so this is the best we can do.
49 if ( !$this->user
->getID() ) {
51 $this->user
= User
::createNew(
52 $this->username
, array(
53 "email" => $this->email
,
54 "real_name" => $this->realname
58 throw new Exception( "error creating user" );
62 // update the user to use the new random password and other details
63 $this->user
->setPassword( $this->password
);
64 $this->user
->setEmail( $this->email
);
65 $this->user
->setRealName( $this->realname
);
66 // remove all groups, replace with any groups specified
67 foreach ( $this->user
->getGroups() as $group ) {
68 $this->user
->removeGroup( $group );
70 if ( count( $this->groups
) ) {
71 foreach ( $this->groups
as $group ) {
72 $this->user
->addGroup( $group );
75 $this->user
->saveSettings();
81 abstract class ApiTestCase
extends PHPUnit_Framework_TestCase
{
85 global $wgContLang, $wgAuth, $wgMemc, $wgRequest, $wgUser;
87 $wgMemc = new FakeMemCachedClient();
88 $wgContLang = Language
::factory( 'en' );
89 $wgAuth = new StubObject( 'wgAuth', 'AuthPlugin' );
90 $wgRequest = new FauxRequest( array() );
93 'sysop' => new ApiTestUser(
96 'api_test_sysop@sample.com',
99 'uploader' => new ApiTestUser(
102 'api_test_user@sample.com',
107 $wgUser = self
::$users['sysop']->user
;
111 function tearDown() {
116 protected function doApiRequest( $params, $session = null ) {
117 $_SESSION = isset( $session ) ?
$session : array();
119 $request = new FauxRequest( $params, true, $_SESSION );
120 $module = new ApiMain( $request, true );
123 return array( $module->getResultData(), $request, $_SESSION );
127 * Add an edit token to the API request
128 * This is cheating a bit -- we grab a token in the correct format and then add it to the pseudo-session and to the
129 * request, without actually requesting a "real" edit token
130 * @param $params: key-value API params
131 * @param $data: a structure which also contains the session
133 protected function doApiRequestWithToken( $params, $session ) {
134 if ( $session['wsToken'] ) {
135 // add edit token to fake session
136 $session['wsEditToken'] = $session['wsToken'];
137 // add token to request parameters
138 $params['token'] = md5( $session['wsToken'] ) . EDIT_TOKEN_SUFFIX
;
139 return $this->doApiRequest( $params, $session );
141 throw new Exception( "request data not in right format" );
151 class ApiUploadTest
extends ApiTestCase
{
153 * Fixture -- run before every test
155 public function setUp() {
156 global $wgEnableUploads, $wgEnableAPI, $wgDebugLogFile;
159 $wgEnableUploads = true;
163 $wgDebugLogFile = '/private/tmp/mwtestdebug.log';
164 ini_set( 'log_errors', 1 );
165 ini_set( 'error_reporting', 1 );
166 ini_set( 'display_errors', 1 );
168 $this->clearFakeUploads();
172 * Fixture -- run after every test
173 * Clean up temporary files etc.
175 function tearDown() {
181 * XXX this is a funny way of getting session context
183 function testLogin() {
184 $user = self
::$users['uploader'];
188 'lgname' => $user->username
,
189 'lgpassword' => $user->password
191 list( $result, $request, $session ) = $this->doApiRequest( $params );
192 $this->assertArrayHasKey( "login", $result );
193 $this->assertArrayHasKey( "result", $result['login'] );
194 $this->assertEquals( "NeedToken", $result['login']['result'] );
195 $token = $result['login']['token'];
200 'lgname' => $user->username
,
201 'lgpassword' => $user->password
203 list( $result, $request, $session ) = $this->doApiRequest( $params );
204 $this->assertArrayHasKey( "login", $result );
205 $this->assertArrayHasKey( "result", $result['login'] );
206 $this->assertEquals( "Success", $result['login']['result'] );
207 $this->assertArrayHasKey( 'lgtoken', $result['login'] );
216 public function testUploadRequiresToken( $session ) {
219 $this->doApiRequest( array(
222 } catch ( UsageException
$e ) {
224 $this->assertEquals( "The token parameter must be set", $e->getMessage() );
226 $this->assertTrue( $exception, "Got exception" );
232 public function testUploadMissingParams( $session ) {
234 $wgUser = self
::$users['uploader']->user
;
238 $this->doApiRequestWithToken( array(
239 'action' => 'upload',
241 } catch ( UsageException
$e ) {
243 $this->assertEquals( "One of the parameters sessionkey, file, url, statuskey is required",
246 $this->assertTrue( $exception, "Got exception" );
253 public function testUpload( $session ) {
255 $wgUser = self
::$users['uploader']->user
;
258 $mimeType = 'image/png';
261 $randomImageGenerator = new RandomImageGenerator();
263 catch ( Exception
$e ) {
264 $this->markTestIncomplete( $e->getMessage() );
267 $filePaths = $randomImageGenerator->writeImages( 1, $extension, dirname( wfTempDir() ) );
268 $filePath = $filePaths[0];
269 $fileName = basename( $filePath );
271 $this->deleteFileByFileName( $fileName );
272 $this->deleteFileByContent( $filePath );
274 if (! $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) {
275 $this->markTestIncomplete( "Couldn't upload file!\n" );
279 'action' => 'upload',
280 'filename' => $fileName,
281 'file' => 'dummy content',
282 'comment' => 'dummy comment',
283 'text' => "This is the page text for $fileName",
288 list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
289 } catch ( UsageException
$e ) {
292 $this->assertTrue( isset( $result['upload'] ) );
293 $this->assertEquals( 'Success', $result['upload']['result'] );
294 $this->assertFalse( $exception );
297 $this->deleteFileByFilename( $fileName );
305 public function testUploadZeroLength( $session ) {
307 $wgUser = self
::$users['uploader']->user
;
309 $mimeType = 'image/png';
311 $filePath = tempnam( wfTempDir(), "" );
312 $fileName = "apiTestUploadZeroLength.png";
314 $this->deleteFileByFileName( $fileName );
316 if (! $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) {
317 $this->markTestIncomplete( "Couldn't upload file!\n" );
321 'action' => 'upload',
322 'filename' => $fileName,
323 'file' => 'dummy content',
324 'comment' => 'dummy comment',
325 'text' => "This is the page text for $fileName",
330 list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
331 } catch ( UsageException
$e ) {
332 $this->assertContains( 'The file you submitted was empty', $e->getMessage() );
335 $this->assertTrue( $exception );
338 $this->deleteFileByFilename( $fileName );
346 public function testUploadSameFileName( $session ) {
348 $wgUser = self
::$users['uploader']->user
;
351 $mimeType = 'image/png';
354 $randomImageGenerator = new RandomImageGenerator();
356 catch ( Exception
$e ) {
357 $this->markTestIncomplete( $e->getMessage() );
360 $filePaths = $randomImageGenerator->writeImages( 2, $extension, dirname( wfTempDir() ) );
361 // we'll reuse this filename
362 $fileName = basename( $filePaths[0] );
364 // clear any other files with the same name
365 $this->deleteFileByFileName( $fileName );
367 // we reuse these params
369 'action' => 'upload',
370 'filename' => $fileName,
371 'file' => 'dummy content',
372 'comment' => 'dummy comment',
373 'text' => "This is the page text for $fileName",
376 // first upload .... should succeed
378 if (! $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[0] ) ) {
379 $this->markTestIncomplete( "Couldn't upload file!\n" );
384 list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
385 } catch ( UsageException
$e ) {
388 $this->assertTrue( isset( $result['upload'] ) );
389 $this->assertEquals( 'Success', $result['upload']['result'] );
390 $this->assertFalse( $exception );
392 // second upload with the same name (but different content)
394 if (! $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[1] ) ) {
395 $this->markTestIncomplete( "Couldn't upload file!\n" );
400 list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
401 } catch ( UsageException
$e ) {
404 $this->assertTrue( isset( $result['upload'] ) );
405 $this->assertEquals( 'Warning', $result['upload']['result'] );
406 $this->assertTrue( isset( $result['upload']['warnings'] ) );
407 $this->assertTrue( isset( $result['upload']['warnings']['exists'] ) );
408 $this->assertFalse( $exception );
411 $this->deleteFileByFilename( $fileName );
412 unlink( $filePaths[0] );
413 unlink( $filePaths[1] );
420 public function testUploadSameContent( $session ) {
422 $wgUser = self
::$users['uploader']->user
;
425 $mimeType = 'image/png';
428 $randomImageGenerator = new RandomImageGenerator();
430 catch ( Exception
$e ) {
431 $this->markTestIncomplete( $e->getMessage() );
433 $filePaths = $randomImageGenerator->writeImages( 1, $extension, dirname( wfTempDir() ) );
434 $fileNames[0] = basename( $filePaths[0] );
435 $fileNames[1] = "SameContentAs" . $fileNames[0];
437 // clear any other files with the same name or content
438 $this->deleteFileByContent( $filePaths[0] );
439 $this->deleteFileByFileName( $fileNames[0] );
440 $this->deleteFileByFileName( $fileNames[1] );
442 // first upload .... should succeed
445 'action' => 'upload',
446 'filename' => $fileNames[0],
447 'file' => 'dummy content',
448 'comment' => 'dummy comment',
449 'text' => "This is the page text for " . $fileNames[0],
452 if (! $this->fakeUploadFile( 'file', $fileNames[0], $mimeType, $filePaths[0] ) ) {
453 $this->markTestIncomplete( "Couldn't upload file!\n" );
458 list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
459 } catch ( UsageException
$e ) {
462 $this->assertTrue( isset( $result['upload'] ) );
463 $this->assertEquals( 'Success', $result['upload']['result'] );
464 $this->assertFalse( $exception );
467 // second upload with the same content (but different name)
469 if (! $this->fakeUploadFile( 'file', $fileNames[1], $mimeType, $filePaths[0] ) ) {
470 $this->markTestIncomplete( "Couldn't upload file!\n" );
474 'action' => 'upload',
475 'filename' => $fileNames[1],
476 'file' => 'dummy content',
477 'comment' => 'dummy comment',
478 'text' => "This is the page text for " . $fileNames[1],
483 list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
484 } catch ( UsageException
$e ) {
487 $this->assertTrue( isset( $result['upload'] ) );
488 $this->assertEquals( 'Warning', $result['upload']['result'] );
489 $this->assertTrue( isset( $result['upload']['warnings'] ) );
490 $this->assertTrue( isset( $result['upload']['warnings']['duplicate'] ) );
491 $this->assertFalse( $exception );
494 $this->deleteFileByFilename( $fileNames[0] );
495 $this->deleteFileByFilename( $fileNames[1] );
496 unlink( $filePaths[0] );
503 public function testUploadStash( $session ) {
505 $wgUser = self
::$users['uploader']->user
;
508 $mimeType = 'image/png';
511 $randomImageGenerator = new RandomImageGenerator();
513 catch ( Exception
$e ) {
514 $this->markTestIncomplete( $e->getMessage() );
517 $filePaths = $randomImageGenerator->writeImages( 1, $extension, dirname( wfTempDir() ) );
518 $filePath = $filePaths[0];
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" );
529 'action' => 'upload',
531 'filename' => $fileName,
532 'file' => 'dummy content',
533 'comment' => 'dummy comment',
534 'text' => "This is the page text for $fileName",
539 list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
540 } catch ( UsageException
$e ) {
543 $this->assertFalse( $exception );
544 $this->assertTrue( isset( $result['upload'] ) );
545 $this->assertEquals( 'Success', $result['upload']['result'] );
546 $this->assertTrue( isset( $result['upload']['sessionkey'] ) );
547 $sessionkey = $result['upload']['sessionkey'];
549 // it should be visible from Special:UploadStash
550 // XXX ...but how to test this, with a fake WebRequest with the session?
552 // now we should try to release the file from stash
554 'action' => 'upload',
555 'sessionkey' => $sessionkey,
556 'filename' => $fileName,
557 'comment' => 'dummy comment',
558 'text' => "This is the page text for $fileName, altered",
561 $this->clearFakeUploads();
564 list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
565 } catch ( UsageException
$e ) {
568 $this->assertTrue( isset( $result['upload'] ) );
569 $this->assertEquals( 'Success', $result['upload']['result'] );
570 $this->assertFalse( $exception );
573 $this->deleteFileByFilename( $fileName );
580 * Helper function -- remove files and associated articles by Title
581 * @param {Title} title to be removed
583 public function deleteFileByTitle( $title ) {
584 if ( $title->exists() ) {
585 $file = wfFindFile( $title, array( 'ignoreRedirect' => true ) );
586 $noOldArchive = ""; // yes this really needs to be set this way
587 $comment = "removing for test";
588 $restrictDeletedVersions = false;
589 $status = FileDeleteForm
::doDelete( $title, $file, $noOldArchive, $comment, $restrictDeletedVersions );
590 if ( !$status->isGood() ) {
593 $article = new Article( $title );
594 $article->doDeleteArticle( "removing for test" );
596 // see if it now doesn't exist; reload
597 $title = Title
::newFromText( $fileName, NS_FILE
);
599 return ! ( $title && is_a( $title, 'Title' ) && $title->exists() );
603 * Helper function -- remove files and associated articles with a particular filename
604 * @param {String} filename to be removed
606 public function deleteFileByFileName( $fileName ) {
607 return $this->deleteFileByTitle( Title
::newFromText( $fileName, NS_FILE
) );
612 * Helper function -- given a file on the filesystem, find matching content in the db (and associated articles) and remove them.
613 * @param {String} path to file on the filesystem
615 public function deleteFileByContent( $filePath ) {
616 $hash = File
::sha1Base36( $filePath );
617 $dupes = RepoGroup
::singleton()->findBySha1( $hash );
619 foreach ( $dupes as $dupe ) {
620 $success &= $this->deleteFileByTitle( $dupe->getTitle() );
626 * Fake an upload by dumping the file into temp space, and adding info to $_FILES.
627 * (This is what PHP would normally do).
628 * @param {String}: fieldname - name this would have in the upload form
629 * @param {String}: fileName - name to title this
630 * @param {String}: mime type
631 * @param {String}: filePath - path where to find file contents
633 function fakeUploadFile( $fieldName, $fileName, $type, $filePath ) {
634 $tmpName = tempnam( wfTempDir(), "" );
635 if ( !file_exists( $filePath ) ) {
636 throw new Exception( "$filePath doesn't exist!" );
639 if ( !copy( $filePath, $tmpName ) ) {
640 throw new Exception( "couldn't copy $filePath to $tmpName" );
644 $size = filesize( $tmpName );
645 if ( $size === false ) {
646 throw new Exception( "couldn't stat $tmpName" );
649 $_FILES[ $fieldName ] = array(
652 'tmp_name' => $tmpName,
662 * Remove traces of previous fake uploads
664 function clearFakeUploads() {