follow up r62231, r61779, r62175
authorMark A. Hershberger <mah@users.mediawiki.org>
Mon, 22 Feb 2010 02:15:30 +0000 (02:15 +0000)
committerMark A. Hershberger <mah@users.mediawiki.org>
Mon, 22 Feb 2010 02:15:30 +0000 (02:15 +0000)
* Fix up messages
* For new FileRepo::append(), use flags to determine whether to delete or not
* Add more error checking for appending
* Fix a couple of places in Revision.php and LogPage.php where DB errors were produced when comment was null
* Remove bogus checking for !$comment, etc on the DONE phase of chunked uploading
* Don't pretend to return a value when raising an exception
* Add more tests for chunked uploads
* Verify that Status::getErrorsArray() (at least where it is used in ApiUpload::execute()) returns an array that we can pass to dieUsageMessage()
* Ensure that checkWarnings(), etc work only on the complete file

includes/LogPage.php
includes/Revision.php
includes/api/ApiBase.php
includes/api/ApiUpload.php
includes/filerepo/FSRepo.php
includes/filerepo/FileRepo.php
includes/filerepo/ForeignAPIRepo.php
includes/filerepo/NullRepo.php
includes/upload/UploadFromChunks.php
languages/messages/MessagesEn.php
maintenance/tests/UploadFromChunksTest.php

index 229b3d8..1d8d6c1 100644 (file)
@@ -378,6 +378,8 @@ class LogPage {
                        $params = array( $params );
                }
 
+               if ( $comment === null ) $comment = "";
+
                $this->action = $action;
                $this->target = $target;
                $this->comment = $comment;
index cbf5625..8d2c7e9 100644 (file)
@@ -835,6 +835,8 @@ class Revision {
                        $this->mTextId = $dbw->insertId();
                }
 
+               if ( $this->mComment === null ) $this->mComment = "";
+
                # Record the edit in revisions
                $rev_id = isset( $this->mId )
                        ? $this->mId
index ebf3b25..25b5690 100644 (file)
@@ -921,6 +921,7 @@ abstract class ApiBase {
                'nouploadmodule' => array( 'code' => 'nouploadmodule', 'info' => 'No upload module set' ),
                'uploaddisabled' => array( 'code' => 'uploaddisabled', 'info' => 'Uploads are not enabled.  Make sure $wgEnableUploads is set to true in LocalSettings.php and the PHP ini setting file_uploads is true' ),
                'chunked-error' => array( 'code' => 'chunked-error', 'info' => 'There was a problem initializing the chunked upload.' ),
+               'chunk-init-error' => array( 'code' => 'chunk-init-error', 'info' => 'Insufficient information for initialization.' ),
        );
 
        /**
index ed0df7e..7e5c77d 100644 (file)
@@ -68,7 +68,7 @@ class ApiUpload extends ApiBase {
                        );
 
                        if ( !$this->mUpload->status->isOK() ) {
-                               return $this->dieUsageMsg( $this->mUpload->status->getErrorsArray() );
+                               $this->dieUsageMsg( $this->mUpload->status->getErrorsArray() );
                        }
                } elseif ( $this->mParams['sessionkey'] ) {
                        /**
@@ -76,7 +76,7 @@ class ApiUpload extends ApiBase {
                         */
                        // Check the session key
                        if ( !isset( $_SESSION['wsUploadData'][$this->mParams['sessionkey']] ) )
-                               return $this->dieUsageMsg( array( 'invalid-session-key' ) );
+                               $this->dieUsageMsg( array( 'invalid-session-key' ) );
 
                        $this->mUpload = new UploadFromStash();
                        $this->mUpload->initialize( $this->mParams['filename'],
@@ -109,7 +109,7 @@ class ApiUpload extends ApiBase {
 
                                $status = $this->mUpload->fetchFile();
                                if ( !$status->isOK() ) {
-                                       return $this->dieUsage( $status->getWikiText(),  'fetchfileerror' );
+                                       $this->dieUsage( $status->getWikiText(),  'fetchfileerror' );
                                }
                        }
                } else $this->dieUsageMsg( array( 'missingparam', 'filename' ) );
index 3696e7e..0dd9d0f 100644 (file)
@@ -227,7 +227,7 @@ class FSRepo extends FileRepo {
                return $status;
        }
 
-       function append( $srcPath, $toAppendPath ) {
+       function append( $srcPath, $toAppendPath, $flags = 0 ) {
                $status = $this->newGood();
 
                // Resolve the virtual URL
@@ -236,20 +236,30 @@ class FSRepo extends FileRepo {
                }
                // Make sure the files are there
                if ( !is_file( $srcPath ) )
-                       $status->fatal( 'append-src-filenotfound', $srcPath );
+                       $status->fatal( 'filenotfound', $srcPath );
 
                if ( !is_file( $toAppendPath ) )
-                       $status->fatal( 'append-toappend-filenotfound', $toAppendPath );
+                       $status->fatal( 'filenotfound', $toAppendPath );
+
+               if ( !$status->isOk() ) return $status;
 
                // Do the append
-               if( file_put_contents( $srcPath, file_get_contents( $toAppendPath ), FILE_APPEND ) ) {
-                       $status->value = $srcPath;
-               } else {
-                       $status->fatal( 'fileappenderror', $toAppendPath,  $srcPath);
+               $chunk = file_get_contents( $toAppendPath );
+               if( $chunk === false ) {
+                       $status->fatal( 'fileappenderrorread', $toAppendPath );
                }
 
-               // Remove the source file
-               unlink( $toAppendPath );
+               if( $status->isOk() ) {
+                       if ( file_put_contents( $srcPath, $chunk, FILE_APPEND ) ) {
+                               $status->value = $srcPath;
+                       } else {
+                               $status->fatal( 'fileappenderror', $toAppendPath,  $srcPath);
+                       }
+               }
+
+               if ( $flags & self::DELETE_SOURCE ) {
+                       unlink( $toAppendPath );
+               }
 
                return $status;
        }
index 7ae5db2..3cfbbf9 100644 (file)
@@ -402,9 +402,11 @@ abstract class FileRepo {
         * Append the contents of the source path to the given file.
         * @param $srcPath string location of the source file
         * @param $toAppendPath string path to append to.
+        * @param $flags Bitfield, may be FileRepo::DELETE_SOURCE to indicate
+        *        that the source file should be deleted if possible
         * @return mixed Status or false
         */
-       abstract function append( $srcPath, $toAppendPath );
+       abstract function append( $srcPath, $toAppendPath, $flags );
 
        /**
         * Remove a temporary file or mark it for garbage collection
index 2e50383..264cb92 100644 (file)
@@ -63,7 +63,7 @@ class ForeignAPIRepo extends FileRepo {
        function storeTemp( $originalName, $srcPath ) {
                return false;
        }
-       function append( $srcPath, $toAppendPath ){
+       function append( $srcPath, $toAppendPath, $flags = 0 ){
                return false;
        }
        function publishBatch( $triplets, $flags = 0 ) {
index f578498..2bc61bd 100644 (file)
@@ -14,7 +14,7 @@ class NullRepo extends FileRepo {
        function storeTemp( $originalName, $srcPath ) {
                return false;
        }
-       function append( $srcPath, $toAppendPath ){
+       function append( $srcPath, $toAppendPath, $flags = 0 ){
                return false;
        }
        function publishBatch( $triplets, $flags = 0 ) {
index 2feccb4..4e3e1df 100644 (file)
@@ -37,23 +37,26 @@ class UploadFromChunks extends UploadBase {
        }
 
        public function initialize( $done, $filename, $sessionKey, $path, $fileSize, $sessionData ) {
-               global $wgTmpDirectory;
-               $this->status = new Status;
+               $this->status = Status::newGood();
 
-               $this->initFromSessionKey( $sessionKey, $sessionData );
+               $this->initializePathInfo( $filename, $path, 0, true );
+               if ( $sessionKey !== null ) {
+                       $this->initFromSessionKey( $sessionKey, $sessionData, $fileSize );
 
-               if ( !$this->sessionKey && !$done ) {
+                       if ( $done ) {
+                               $this->chunkMode = self::DONE;
+                       } else {
+                               $this->mTempPath = $path;
+                               $this->chunkMode = self::CHUNK;
+                       }
+               } else {
                        // session key not set, init the chunk upload system:
                        $this->chunkMode = self::INIT;
-                       $this->initializePathInfo( $filename, $path, 0, true);
-               } else if ( $this->sessionKey && !$done ) {
-                       $this->chunkMode = self::CHUNK;
-               } else if ( $this->sessionKey && $done ) {
-                       $this->chunkMode = self::DONE;
                }
-               if ( $this->chunkMode == self::CHUNK || $this->chunkMode == self::DONE ) {
-                       $this->mTempPath = $path;
-                       $this->mFileSize += $fileSize;
+
+               if ( $this->status->isOk()
+                       && ( $this->mDesiredDestName === null || $this->mFileSize === null ) ) {
+                       $this->status = Status::newFatal( 'chunk-init-error' );
                }
        }
 
@@ -66,16 +69,26 @@ class UploadFromChunks extends UploadBase {
         * @returns string the session key for this chunked upload
         */
        protected function setupChunkSession( $comment, $pageText, $watch ) {
-               $this->sessionKey = $this->getSessionKey();
-               $_SESSION['wsUploadData'][$this->sessionKey] = array(
-                       'comment' => $comment,
-                       'pageText' => $pageText,
-                       'watch' => $watch,
-                       'mFilteredName' => $this->mFilteredName,
-                       'repoPath' => null,
-                       'mDesiredDestName' => $this->mDesiredDestName,
-                       'version' => self::SESSION_VERSION,
-               );
+               if ( !isset( $this->sessionKey ) ) {
+                       $this->sessionKey = $this->getSessionKey();
+               }
+               foreach ( array( 'mFilteredName', 'repoPath', 'mFileSize', 'mDesiredDestName' )
+                               as $key ) {
+                       if ( isset( $this->$key ) ) {
+                               $_SESSION['wsUploadData'][$this->sessionKey][$key] = $this->$key;
+                       }
+               }
+               if ( isset( $comment ) ) {
+                       $_SESSION['wsUploadData'][$this->sessionKey]['commment'] = $comment;
+               }
+               if ( isset( $pageText ) ) {
+                       $_SESSION['wsUploadData'][$this->sessionKey]['pageText'] = $pageText;
+               }
+               if ( isset( $watch ) ) {
+                       $_SESSION['wsUploadData'][$this->sessionKey]['watch'] = $watch;
+               }
+               $_SESSION['wsUploadData'][$this->sessionKey]['version'] = self::SESSION_VERSION;
+
                return $this->sessionKey;
        }
 
@@ -83,26 +96,26 @@ class UploadFromChunks extends UploadBase {
         * Initialize a continuation of a chunked upload from a session key
         * @param $sessionKey string
         * @param $request WebRequest
+        * @param $fileSize int Size of this chunk
         *
         * @returns void
         */
-       protected function initFromSessionKey( $sessionKey, $sessionData ) {
+       protected function initFromSessionKey( $sessionKey, $sessionData, $fileSize ) {
                // testing against null because we don't want to cause obscure
                // bugs when $sessionKey is full of "0"
-               if ( $sessionKey === null ) {
-                       return;
-               }
                $this->sessionKey = $sessionKey;
 
                if ( isset( $sessionData[$this->sessionKey]['version'] )
                        && $sessionData[$this->sessionKey]['version'] == self::SESSION_VERSION )
                {
-                       $this->comment = $sessionData[$this->sessionKey]['comment'];
-                       $this->pageText = $sessionData[$this->sessionKey]['pageText'];
-                       $this->watch = $sessionData[$this->sessionKey]['watch'];
-                       $this->mFilteredName = $sessionData[$this->sessionKey]['mFilteredName'];
-                       $this->repoPath = $sessionData[$this->sessionKey]['repoPath'];
-                       $this->mDesiredDestName = $sessionData[$this->sessionKey]['mDesiredDestName'];
+                       foreach ( array( 'comment', 'pageText', 'watch', 'mFilteredName', 'repoPath', 'mFileSize', 'mDesiredDestName' )
+                                       as $key ) {
+                               if ( isset( $sessionData[$this->sessionKey][$key] ) ) {
+                                       $this->$key = $sessionData[$this->sessionKey][$key];
+                               }
+                       }
+
+                       $this->mFileSize += $fileSize;
                } else {
                        $this->status = Status::newFatal( 'invalid-session-key' );
                }
@@ -127,14 +140,17 @@ class UploadFromChunks extends UploadBase {
                        // b) should only happen over POST
                        // c) we need the token to validate chunks are coming from a non-xss request
                        return Status::newGood(
-                               array('uploadUrl' => wfExpandUrl( wfScript( 'api' ) ) . "?" .
-                                         wfArrayToCGI(array('action' => 'upload',
-                                                                                'token' => $wgUser->editToken(),
-                                                                                'format' => 'json',
-                                                                                'filename' => $pageText,
-                                                                                'enablechunks' => 'true',
-                                                                                'chunksession' => $this->setupChunkSession( $comment, $pageText, $watch ) ) ) ) );
+                               array( 'uploadUrl' => wfExpandUrl( wfScript( 'api' ) ) . "?" .
+                                       wfArrayToCGI( array(
+                                               'action' => 'upload',
+                                               'token' => $wgUser->editToken(),
+                                               'format' => 'json',
+                                               'filename' => $this->mDesiredDestName,
+                                               'enablechunks' => 'true',
+                                               'chunksession' =>
+                                               $this->setupChunkSession( $comment, $pageText, $watch ) ) ) ) );
                } else if ( $this->chunkMode == self::CHUNK ) {
+                       $this->setupChunkSession();
                        $this->appendChunk();
                        if ( !$this->status->isOK() ) {
                                return $this->status;
@@ -146,16 +162,10 @@ class UploadFromChunks extends UploadBase {
                                array( 'result' => 1, 'filesize' => $this->mFileSize )
                        );
                } else if ( $this->chunkMode == self::DONE ) {
-                       if ( !$comment )
-                               $comment = $this->comment;
+                       $this->finalizeFile();
+                       // We ignore the passed-in parameters because these were set on the first contact.
+                       $status = parent::performUpload( $this->comment, $this->pageText, $this->watch, $user );
 
-                       if ( !$pageText )
-                               $pageText = $this->pageText;
-
-                       if ( !$watch )
-                               $watch = $this->watch;
-
-                       $status = parent::performUpload( $comment, $pageText, $watch, $user );
                        if ( !$status->isGood() ) {
                                return $status;
                        }
@@ -164,7 +174,7 @@ class UploadFromChunks extends UploadBase {
                        // firefogg expects a specific result
                        // http://www.firefogg.org/dev/chunk_post.html
                        return Status::newGood(
-                               array('result' => 1, 'done' => 1, 'resultUrl' => wfExpandUrl( $file->getDescriptionUrl() ) )
+                               array( 'result' => 1, 'done' => 1, 'resultUrl' => wfExpandUrl( $file->getDescriptionUrl() ) )
                        );
                }
 
@@ -203,16 +213,27 @@ class UploadFromChunks extends UploadBase {
                }
                if ( $this->getRealPath( $this->repoPath ) ) {
                        $this->status = $this->appendToUploadFile( $this->repoPath, $this->mTempPath );
+
+                       if ( $this->mFileSize > $wgMaxUploadSize )
+                               $this->status = Status::newFatal( 'largefileserver' );
+
                } else {
                        $this->status = Status::newFatal( 'filenotfound', $this->repoPath );
                }
-               if ( $this->mFileSize > $wgMaxUploadSize )
-                       $this->status = Status::newFatal( 'largefileserver' );
+       }
+
+       /**
+        * Append the final chunk and ready file for parent::performUpload()
+        * @return void
+        */
+       protected function finalizeFile() {
+               $this->appendChunk();
+               $this->mTempPath = $this->getRealPath( $this->repoPath );
        }
 
        public function verifyUpload() {
                if ( $this->chunkMode != self::DONE ) {
-                       return array('status' => UploadBase::OK);
+                       return array( 'status' => UploadBase::OK );
                }
                return parent::verifyUpload();
        }
index 93fcaae..ea64f7e 100644 (file)
@@ -979,6 +979,8 @@ Please report this to an [[Special:ListUsers/sysop|administrator]], making note
 'readonly_lag'         => 'The database has been automatically locked while the slave database servers catch up to the master',
 'internalerror'        => 'Internal error',
 'internalerror_info'   => 'Internal error: $1',
+'fileappenderrorread'  => 'Could not read "$1" during append.',
+'fileappenderror'      => 'Could not append "$1" to "$2".',
 'filecopyerror'        => 'Could not copy file "$1" to "$2".',
 'filerenameerror'      => 'Could not rename file "$1" to "$2".',
 'filedeleteerror'      => 'Could not delete file "$1".',
index b0decd6..51fe979 100644 (file)
@@ -1,27 +1,30 @@
 <?php
 
-require_once("ApiSetup.php");
+require_once( "ApiSetup.php" );
 
 class UploadFromChunksTest extends ApiSetup {
 
        function setUp() {
                global $wgEnableUploads;
 
-               $wgEnableUploads=true;
-               ini_set('file_loads', true);
+               $wgEnableUploads = true;
                parent::setup();
 
+               ini_set( 'file_loads', 1 );
+               ini_set( 'log_errors', 1 );
+               ini_set( 'error_reporting', 1 );
+               ini_set( 'display_errors', 1 );
        }
 
-       function makeChunk() {
+       function makeChunk( $content ) {
                $file = tempnam( wfTempDir(), "" );
-               $fh = fopen($file, "w");
-               if($fh == false) {
-                       $this->markTestIncomplete("Couldn't open $file!\n");
+               $fh = fopen( $file, "w" );
+               if ( $fh == false ) {
+                       $this->markTestIncomplete( "Couldn't open $file!\n" );
                        return;
                }
-               fwrite($fh, "123");
-               fclose($fh);
+               fwrite( $fh, $content );
+               fclose( $fh );
 
                $_FILES['chunk']['tmp_name'] = $file;
                $_FILES['chunk']['size'] = 3;
@@ -30,133 +33,286 @@ class UploadFromChunksTest extends ApiSetup {
        }
 
        function cleanChunk() {
-               if(file_exists($_FILES['chunk']['tmp_name']))
-                  unlink($_FILES['chunk']['tmp_name']);
+               if ( file_exists( $_FILES['chunk']['tmp_name'] ) )
+                  unlink( $_FILES['chunk']['tmp_name'] );
        }
 
-       function doApiRequest($params) {
-               $session = isset( $_SESSION ) ? $_SESSION : array();
-               $req = new FauxRequest($params, true, $session);
-               $module = new ApiMain($req, true);
+       function doApiRequest( $params, $data = null ) {
+               $session = isset( $data[2] ) ? $data[2] : array();
+               $_SESSION = $session;
+
+               $req = new FauxRequest( $params, true, $session );
+               $module = new ApiMain( $req, true );
                $module->execute();
 
-               return $module->getResultData();
+               return array( $module->getResultData(), $req, $_SESSION );
        }
 
        function testGetTitle() {
                $filename = tempnam( wfTempDir(), "" );
                $c = new UploadFromChunks();
-               $c->initialize(false, "temp.txt", null, $filename, 0, null);
-               $this->assertEquals(null, $c->getTitle());
+               $c->initialize( false, "temp.txt", null, $filename, 0, null );
+               $this->assertEquals( null, $c->getTitle() );
 
                $c = new UploadFromChunks();
-               $c->initialize(false, "temp.png", null, $filename, 0, null);
-               $this->assertEquals(Title::makeTitleSafe(NS_FILE, "Temp.png"), $c->getTitle());
+               $c->initialize( false, "temp.png", null, $filename, 0, null );
+               $this->assertEquals( Title::makeTitleSafe( NS_FILE, "Temp.png" ), $c->getTitle() );
        }
 
        function testLogin() {
-               $data = $this->doApiRequest(array('action' => 'login',
-                                                                                 "lgname" => self::$userName,
-                                                                                 "lgpassword" => self::$passWord ) );
-               $this->assertArrayHasKey("login", $data);
-               $this->assertArrayHasKey("result", $data['login']);
-               $this->assertEquals("Success", $data['login']['result']);
+               $data = $this->doApiRequest( array(
+                       'action' => 'login',
+                       'lgname' => self::$userName,
+                       'lgpassword' => self::$passWord ) );
+               $this->assertArrayHasKey( "login", $data[0] );
+               $this->assertArrayHasKey( "result", $data[0]['login'] );
+               $this->assertEquals( "Success", $data[0]['login']['result'] );
+               $this->assertArrayHasKey( 'lgtoken', $data[0]['login'] );
 
                return $data;
        }
 
-
        /**
         * @depends testLogin
         */
-       function testGetEditToken($data) {
+       function testSetupChunkSession( $data ) {
                global $wgUser;
-               $wgUser = User::newFromName(self::$userName);
+               $wgUser = User::newFromName( self::$userName );
                $wgUser->load();
+               $data[2]['wsEditToken'] = $data[2]['wsToken'];
+               $token = md5( $data[2]['wsToken'] ) . EDIT_TOKEN_SUFFIX;
+               $exception = false;
+
+               $data = $this->doApiRequest( array(
+                       'filename' => 'tmp.txt',
+                       'action' => 'upload',
+                       'enablechunks' => true,
+                       'token' => $token ), $data );
+               $this->assertArrayHasKey( 'uploadUrl', $data[0] );
+               $this->assertRegexp( '/action=upload/', $data[0]['uploadUrl'] );
+               $this->assertRegexp( '/enablechunks=true/', $data[0]['uploadUrl'] );
+               $this->assertRegexp( '/format=json/', $data[0]['uploadUrl'] );
+               $this->assertRegexp( '/chunksession=/', $data[0]['uploadUrl'] );
+               $this->assertRegexp( '/token=/', $data[0]['uploadUrl'] );
 
-               $data = $this->doApiRequest(array('action' => 'query',
-                                                                                 'prop' => 'info',
-                                                                                 'intoken' => 'edit'));
+               return $data;
        }
 
-       function testSetupChunkSession() {
-       }
+       /**
+        * @depends testSetupChunkSession
+        */
+       function testAppendChunkTypeBanned( $data ) {
+               global $wgUser;
+               $wgUser = User::newFromName( self::$userName );
 
+               $url = $data[0]['uploadUrl'];
+               $params = wfCgiToArray( substr( $url, strpos( $url, "?" ) ) );
 
-    /**
-     * @expectedException UsageException
-     */
-       function testPerformUploadInitError() {
-               global $wgUser;
-               $wgUser = User::newFromId(1);
+               $size = 0;
+               for ( $i = 0; $i < 4; $i++ ) {
+                       $this->makeChunk( "123" );
+                       $size += $_FILES['chunk']['size'];
 
-               $req = new FauxRequest(
-                       array('action' => 'upload',
-                                 'enablechunks' => '1',
-                                 'filename' => 'test.png',
-                       ));
-               $module = new ApiMain($req, true);
-               $module->execute();
+                       $data = $this->doApiRequest( $params, $data );
+                       $this->assertArrayHasKey( "result", $data[0] );
+                       $this->assertTrue( (bool)$data[0]["result"] );
+
+                       $this->assertArrayHasKey( "filesize", $data[0] );
+                       $this->assertEquals( $size, $data[0]['filesize'] );
+
+                       $this->cleanChunk();
+               }
+
+               $data['param'] = $params;
+               return $data;
        }
 
        /**
         * @depends testLogin
         */
-       function testPerformUploadInitSuccess($login) {
+       function testInvalidSessionKey( $data ) {
                global $wgUser;
+               $wgUser = User::newFromName( self::$userName );
+               $wgUser->load();
+               $data[2]['wsEditToken'] = $data[2]['wsToken'];
+               $token = md5( $data[2]['wsToken'] ) . EDIT_TOKEN_SUFFIX;
+               $exception = false;
+
+               try {
+                       $this->doApiRequest( array(
+                               'action' => 'upload',
+                               'enablechunks' => true,
+                               'token' => $token,
+                               'chunksession' => 'bogus' ), $data );
+               } catch ( UsageException $e ) {
+                       $exception = true;
+                       $this->assertEquals( "Not a valid session key", $e->getMessage() );
+               }
 
-               $wgUser = User::newFromName(self::$userName);
-               $token = $wgUser->editToken();
+               $this->assertTrue( $exception, "Got exception" );
+       }
 
-               $data = $this->doApiRequest(
-                       array('action' => 'upload',
-                                 'enablechunks' => '1',
-                                 'filename' => 'test.png',
-                                 'token' => $token,
-                       ));
+       function testPerformUploadInitError() {
+               global $wgUser;
+               $wgUser = User::newFromId( 1 );
 
-               $this->assertArrayHasKey("uploadUrl", $data);
+               $req = new FauxRequest(
+                       array(
+                               'action' => 'upload',
+                               'enablechunks' => 'false',
+                               'sessionkey' => '1',
+                               'filename' => 'test.png',
+                       ) );
+               $module = new ApiMain( $req, true );
+               $gotException = false;
+               try {
+                       $module->execute();
+               } catch ( UsageException $e ) {
+                       $this->assertEquals( "The token parameter must be set", $e->getMessage() );
+                       $gotException = true;
+               }
 
-               return array('data' => $data, 'session' => $_SESSION, 'token' => $token);
+               $this->assertTrue( $gotException );
        }
 
        /**
-        * @depends testPerformUploadInitSuccess
+        * @depends testAppendChunkTypeBanned
         */
-       function testAppendChunk($combo) {
+       function testUploadChunkDoneTypeBanned( $data ) {
                global $wgUser;
-               $data = $combo['data'];
-               $_SESSION = $combo['session'];
-               $wgUser = User::newFromName(self::$userName);
+               $wgUser = User::newFromName( self::$userName );
                $token = $wgUser->editToken();
+               $params = $data['param'];
+               $params['done'] = 1;
 
-               $url = $data['uploadUrl'];
-               $params = wfCgiToArray(substr($url, strpos($url, "?")));
+               $this->makeChunk( "123" );
+
+               $gotException = false;
+               try {
+                       $data = $this->doApiRequest( $params, $data );
+               } catch ( UsageException $e ) {
+                       $this->assertEquals( "This type of file is banned",
+                               $e->getMessage() );
+                       $gotException = true;
+               }
+               $this->cleanChunk();
+               $this->assertTrue( $gotException );
+       }
+
+       /**
+        * @depends testLogin
+        */
+       function testUploadChunkDoneDuplicate( $data ) {
+               global $wgUser, $wgVerifyMimeType;
+
+               $wgVerifyMimeType = false;
+               $wgUser = User::newFromName( self::$userName );
+               $data[2]['wsEditToken'] = $data[2]['wsToken'];
+               $token = md5( $data[2]['wsToken'] ) . EDIT_TOKEN_SUFFIX;
+               $data = $this->doApiRequest( array(
+                       'filename' => 'tmp.png',
+                       'action' => 'upload',
+                       'enablechunks' => true,
+                       'token' => $token ), $data );
+
+               $url = $data[0]['uploadUrl'];
+               $params = wfCgiToArray( substr( $url, strpos( $url, "?" ) ) );
+               $size = 0;
+               for ( $i = 0; $i < 4; $i++ ) {
+                       $this->makeChunk( "123" );
+                       $size += $_FILES['chunk']['size'];
+
+                       $data = $this->doApiRequest( $params, $data );
+                       $this->assertArrayHasKey( "result", $data[0] );
+                       $this->assertTrue( (bool)$data[0]["result"] );
+
+                       $this->assertArrayHasKey( "filesize", $data[0] );
+                       $this->assertEquals( $size, $data[0]['filesize'] );
 
-               for($i=0;$i<10;$i++) {
-                       $this->makeChunk();
-                       $data = $this->doApiRequest($params);
                        $this->cleanChunk();
                }
 
-               return array('data' => $data, 'session' => $_SESSION, 'token' => $token, 'params' => $params);
+               $params['done'] = true;
+
+               $this->makeChunk( "123" );
+               $data = $this->doApiRequest( $params, $data );
+               $this->cleanChunk();
+
+               $this->assertArrayHasKey( 'upload', $data[0] );
+               $this->assertArrayHasKey( 'result', $data[0]['upload'] );
+               $this->assertEquals( 'Warning', $data[0]['upload']['result'] );
+
+               $this->assertArrayHasKey( 'warnings', $data[0]['upload'] );
+               $this->assertArrayHasKey( 'exists',
+                                                                $data[0]['upload']['warnings'] );
+               $this->assertEquals( 'Tmp.png',
+                                                       $data[0]['upload']['warnings']['exists'] );
+
        }
 
        /**
-        * @depends testAppendChunk
+        * @depends testLogin
         */
-       function testUploadChunkDone($combo) {
-               global $wgUser;
-               $data = $combo['data'];
-               $params = $combo['params'];
-               $_SESSION = $combo['session'];
-               $wgUser = User::newFromName(self::$userName);
-               $token = $wgUser->editToken();
+       function testUploadChunkDoneGood( $data ) {
+               global $wgUser, $wgVerifyMimeType;
+               $wgVerifyMimeType = false;
 
-               $params['done'] = 1;
+               $id = Title::newFromText( "Twar.png", NS_FILE )->getArticleID();
+
+               $oldFile = Article::newFromID( $id );
+               if ( $oldFile ) {
+                       $oldFile->doDeleteArticle();
+                       $oldFile->doPurge();
+               }
+               $oldFile = wfFindFile( "Twar.png" );
+               if ( $oldFile ) {
+                       $oldFile->delete();
+               }
+
+               $wgUser = User::newFromName( self::$userName );
+               $data[2]['wsEditToken'] = $data[2]['wsToken'];
+               $token = md5( $data[2]['wsToken'] ) . EDIT_TOKEN_SUFFIX;
+               $data = $this->doApiRequest( array(
+                       'filename' => 'twar.png',
+                       'action' => 'upload',
+                       'enablechunks' => true,
+                       'token' => $token ), $data );
+
+               $url = $data[0]['uploadUrl'];
+               $params = wfCgiToArray( substr( $url, strpos( $url, "?" ) ) );
+               $size = 0;
+               for ( $i = 0; $i < 5; $i++ ) {
+                       $this->makeChunk( "123" );
+                       $size += $_FILES['chunk']['size'];
+
+                       $data = $this->doApiRequest( $params, $data );
+                       $this->assertArrayHasKey( "result", $data[0] );
+                       $this->assertTrue( (bool)$data[0]["result"] );
+
+                       $this->assertArrayHasKey( "filesize", $data[0] );
+                       $this->assertEquals( $size, $data[0]['filesize'] );
+
+                       $this->cleanChunk();
+               }
+
+               $params['done'] = true;
+
+               $this->makeChunk( "456" );
+               $data = $this->doApiRequest( $params, $data );
 
-               $this->makeChunk();
-               $data = $this->doApiRequest($params);
                $this->cleanChunk();
+
+               if ( isset( $data[0]['upload'] ) ) {
+                       $this->markTestSkipped( "Please run 'php maintenance/deleteArchivedFiles.php --delete --force' and 'php maintenance/deleteArchivedRevisions.php --delete'" );
+               }
+
+               $this->assertArrayHasKey( 'result', $data[0] );
+               $this->assertEquals( 1, $data[0]['result'] );
+
+               $this->assertArrayHasKey( 'done', $data[0] );
+               $this->assertEquals( 1, $data[0]['done'] );
+
+               $this->assertArrayHasKey( 'resultUrl', $data[0] );
+               $this->assertRegExp( '/File:Twar.png/', $data[0]['resultUrl'] );
        }
 }