r75503 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r75502‎ | r75503 | r75504 >
Date:03:38, 27 October 2010
Author:neilk
Status:deferred
Tags:
Comment:
a class that actually works for testing the basic upload API, plus helper class for generating files for such test
Modified paths:
  • /branches/uploadwizard/phase3/maintenance/tests/phpunit/includes/api/ApiUploadTest.php (added) (history)
  • /branches/uploadwizard/phase3/maintenance/tests/phpunit/includes/api/RandomImageGenerator.php (added) (history)

Diff [purge]

Index: branches/uploadwizard/phase3/maintenance/tests/phpunit/includes/api/RandomImageGenerator.php
@@ -0,0 +1,289 @@
 2+<?php
 3+
 4+/*
 5+ * RandomImageGenerator -- does what it says on the tin.
 6+ * Requires Imagick, the ImageMagick library for PHP, or the command line equivalent (usually 'convert').
 7+ *
 8+ * Because MediaWiki tests the uniqueness of media upload content, and filenames, it is sometimes useful to generate
 9+ * files that are guaranteed (or at least very likely) to be unique in both those ways.
 10+ * This generates a number of filenames with random names and random content (colored circles)
 11+ *
 12+ * It is also useful to have fresh content because our tests currently run in a "destructive" mode, and don't create a fresh new wiki for each
 13+ * test run.
 14+ * Consequently, if we just had a few static files we kept re-uploading, we'd get lots of warnings about matching content or filenames,
 15+ * and even if we deleted those files, we'd get warnings about archived files.
 16+ *
 17+ * This can also be used with a cronjob to generate random files all the time -- I use it to have a constant, never ending supply when I'm
 18+ * testing interactively.
 19+ *
 20+ * @file
 21+ * @author Neil Kandalgaonkar <neilk@wikimedia.org>
 22+ */
 23+
 24+/**
 25+ * RandomImageGenerator: does what it says on the tin.
 26+ * Can fetch a random image, or also write a number of them to disk with random filenames.
 27+ */
 28+class RandomImageGenerator {
 29+
 30+ private $dictionaryFile;
 31+ private $minWidth = 400;
 32+ private $maxWidth = 800;
 33+ private $minHeight = 400;
 34+ private $maxHeight = 800;
 35+ private $circlesToDraw = 5;
 36+ private $imageWriteMethod;
 37+
 38+ public function __construct( $options ) {
 39+ global $wgUseImageMagick, $wgImageMagickConvertCommand;
 40+ foreach ( array( 'dictionaryFile', 'minWidth', 'minHeight', 'maxHeight', 'circlesToDraw' ) as $property ) {
 41+ if ( isset( $options[$property] ) ) {
 42+ $this->$property = $options[$property];
 43+ }
 44+ }
 45+
 46+ // find the dictionary file, to generate random names
 47+ if ( !isset( $this->dictionaryFile ) ) {
 48+ foreach ( array( '/usr/share/dict/words', '/usr/dict/words' ) as $dictionaryFile ) {
 49+ if ( is_file( $dictionaryFile ) and is_readable( $dictionaryFile ) ) {
 50+ $this->dictionaryFile = $dictionaryFile;
 51+ break;
 52+ }
 53+ }
 54+ }
 55+ if ( !isset( $this->dictionaryFile ) ) {
 56+ throw new Exception( "RandomImageGenerator: dictionary file not found or not specified properly" );
 57+ }
 58+
 59+ // figure out how to write images
 60+ if ( class_exists( 'Imagick' ) ) {
 61+ $this->imageWriteMethod = 'writeImageWithApi';
 62+ } elseif ( $wgUseImageMagick && $wgImageMagickConvertCommand && is_executable( $wgImageMagickConvertCommand ) ) {
 63+ $this->imageWriteMethod = 'writeImageWithCommandLine';
 64+ } else {
 65+ throw new Exception( "RandomImageGenerator: could not find a suitable method to write images" );
 66+ }
 67+ }
 68+
 69+ /**
 70+ * Writes random images with random filenames to disk in the directory you specify, or current working directory
 71+ *
 72+ * @param {Integer} number of filenames to write
 73+ * @param {String} format, optional, must be understood by ImageMagick, such as 'jpg' or 'gif'
 74+ * @param {String} directory, optional (will default to current working directory)
 75+ * @return {Array} filenames we just wrote
 76+ */
 77+ function writeImages( $number, $format = 'jpg', $dir = null ) {
 78+ $filenames = $this->getRandomFilenames( $number, $format, $dir );
 79+ foreach( $filenames as $filename ) {
 80+ $this->{$this->imageWriteMethod}( $this->getImageSpec(), $format, $filename );
 81+ }
 82+ return $filenames;
 83+ }
 84+
 85+ /**
 86+ * Return a number of randomly-generated filenames
 87+ * Each filename uses two words randomly drawn from the dictionary, like elephantine_spatula.jpg
 88+ *
 89+ * @param {Integer} number of filenames to generate
 90+ * @param {String} extension, optional, defaults to 'jpg'
 91+ * @param {String} directory, optional, defaults to current working directory
 92+ * @return {Array} of filenames
 93+ */
 94+ private function getRandomFilenames( $number, $extension = 'jpg', $dir = null ) {
 95+ if ( is_null( $dir ) ) {
 96+ $dir = getcwd();
 97+ }
 98+ $filenames = array();
 99+ foreach( $this->getRandomWordPairs( $number ) as $pair ) {
 100+ $basename = $pair[0] . '_' . $pair[1];
 101+ if ( !is_null( $extension ) ) {
 102+ $basename .= '.' . $extension;
 103+ }
 104+ $basename = preg_replace( '/\s+/', '', $basename );
 105+ $filenames[] = "$dir/$basename";
 106+ }
 107+
 108+ return $filenames;
 109+
 110+ }
 111+
 112+
 113+ /**
 114+ * Generate data representing an image of random size (within limits),
 115+ * consisting of randomly colored and sized circles against a random background color
 116+ * (This data is used in the writeImage* methods).
 117+ * @return {Mixed}
 118+ */
 119+ public function getImageSpec() {
 120+ $spec = array();
 121+
 122+ $spec['width'] = mt_rand( $this->minWidth, $this->maxWidth );
 123+ $spec['height'] = mt_rand( $this->minHeight, $this->maxHeight );
 124+ $spec['fill'] = $this->getRandomColor();
 125+
 126+ $diagonalLength = sqrt( pow( $spec['width'], 2 ) + pow( $spec['height'], 2 ) );
 127+
 128+ $draws = array();
 129+ for ( $i = 0; $i <= $this->circlesToDraw; $i++ ) {
 130+ $radius = mt_rand( 0, $diagonalLength / 4 );
 131+ $originX = mt_rand( -1 * $radius, $spec['width'] + $radius );
 132+ $originY = mt_rand( -1 * $radius, $spec['height'] + $radius );
 133+ $perimeterX = $originX + $radius;
 134+ $perimeterY = $originY + $radius;
 135+
 136+ $draw = array();
 137+ $draw['fill'] = $this->getRandomColor();
 138+ $draw['circle'] = array(
 139+ 'originX' => $originX,
 140+ 'originY' => $originY,
 141+ 'perimeterX' => $perimeterX,
 142+ 'perimeterY' => $perimeterY
 143+ );
 144+ $draws[] = $draw;
 145+
 146+ }
 147+
 148+ $spec['draws'] = $draws;
 149+
 150+ return $spec;
 151+ }
 152+
 153+
 154+ /**
 155+ * Based on an image specification, write such an image to disk, using Imagick PHP extension
 156+ * @param $spec: spec describing background and circles to draw
 157+ * @param $format: file format to write
 158+ * @param $filename: filename to write to
 159+ */
 160+ public function writeImageWithApi( $spec, $format, $filename ) {
 161+ $image = new Imagick();
 162+ $image->newImage( $spec['width'], $spec['height'], new ImagickPixel( $spec['fill'] ) );
 163+
 164+ foreach ( $spec['draws'] as $drawSpec ) {
 165+ $draw = new ImagickDraw();
 166+ $draw->setFillColor( $drawSpec['fill'] );
 167+ $circle = $drawSpec['circle'];
 168+ $draw->circle( $circle['originX'], $circle['originY'], $circle['perimeterX'], $circle['perimeterY'] );
 169+ $image->drawImage( $draw );
 170+ }
 171+
 172+ $image->setImageFormat( $format );
 173+ $image->writeImage( $filename );
 174+ }
 175+
 176+
 177+ /**
 178+ * Based on an image specification, write such an image to disk, using the command line ImageMagick program ('convert').
 179+ *
 180+ * Sample command line:
 181+ * $ convert -size 100x60 xc:rgb(90,87,45) \
 182+ * -draw 'fill rgb(12,34,56) circle 41,39 44,57' \
 183+ * -draw 'fill rgb(99,123,231) circle 59,39 56,57' \
 184+ * -draw 'fill rgb(240,12,32) circle 50,21 50,3' filename.png
 185+ *
 186+ * @param $spec: spec describing background and circles to draw
 187+ * @param $format: file format to write (unused by this method but kept so it has the same signature as writeImageWithApi)
 188+ * @param $filename: filename to write to
 189+ */
 190+ public function writeImageWithCommandLine( $spec, $format, $filename ) {
 191+ global $wgImageMagickConvertCommand;
 192+ $args = array();
 193+ $args[] = "-size " . wfEscapeShellArg( $spec['width'] . 'x' . $spec['height'] );
 194+ $args[] = wfEscapeShellArg( "xc:" . $spec['fill'] );
 195+ foreach( $spec['draws'] as $draw ) {
 196+ $fill = $draw['fill'];
 197+ $originX = $draw['circle']['originX'];
 198+ $originY = $draw['circle']['originY'];
 199+ $perimeterX = $draw['circle']['perimeterX'];
 200+ $perimeterY = $draw['circle']['perimeterY'];
 201+ $drawCommand = "fill $fill circle $originX,$originY $perimeterX,$perimeterY";
 202+ $args[] = '-draw ' . wfEscapeShellArg( $drawCommand );
 203+ }
 204+ $args[] = $filename;
 205+
 206+ $command = wfEscapeShellArg( $wgImageMagickConvertCommand ) . " " . implode( " ", $args );
 207+ $output = wfShellExec( $command, $retval );
 208+ return ( $retval === 0 );
 209+ }
 210+
 211+
 212+
 213+ /**
 214+ * Generate a string of random colors for ImageMagick, like "rgb(12, 37, 98)"
 215+ *
 216+ * @return {String}
 217+ */
 218+ public function getRandomColor() {
 219+ $components = array();
 220+ for ($i = 0; $i <= 2; $i++ ) {
 221+ $components[] = mt_rand( 0, 255 );
 222+ }
 223+ return 'rgb(' . join(', ', $components) . ')';
 224+ }
 225+
 226+ /**
 227+ * Get an array of random pairs of random words, like array( array( 'foo', 'bar' ), array( 'quux', 'baz' ) );
 228+ *
 229+ * @param {Integer} number of pairs
 230+ * @return {Array} of two-element arrays
 231+ */
 232+ private function getRandomWordPairs( $number ) {
 233+ $lines = $this->getRandomLines( $number * 2 );
 234+ // construct pairs of words
 235+ $pairs = array();
 236+ $count = count( $lines );
 237+ for( $i = 0; $i < $count; $i += 2 ) {
 238+ $pairs[] = array( $lines[$i], $lines[$i+1] );
 239+ }
 240+ return $pairs;
 241+ }
 242+
 243+
 244+ /**
 245+ * Return N random lines from a file
 246+ *
 247+ * Will throw exception if the file could not be read or if it had fewer lines than requested.
 248+ *
 249+ * @param {Integer} number of lines desired
 250+ * @string {String} path to file
 251+ * @return {Array} of exactly n elements, drawn randomly from lines the file
 252+ */
 253+ private function getRandomLines( $number_desired ) {
 254+ $filepath = $this->dictionaryFile;
 255+
 256+ // initialize array of lines
 257+ $lines = array();
 258+ for ( $i = 0; $i < $number_desired; $i++ ) {
 259+ $lines[] = null;
 260+ }
 261+
 262+ /*
 263+ * This algorithm obtains N random lines from a file in one single pass. It does this by replacing elements of
 264+ * a fixed-size array of lines, less and less frequently as it reads the file.
 265+ */
 266+ $fh = fopen( $filepath, "r" );
 267+ if ( !$fh ) {
 268+ throw new Exception( "couldn't open $filepath" );
 269+ }
 270+ $line_number = 0;
 271+ $max_index = $number_desired - 1;
 272+ while( !feof( $fh ) ) {
 273+ $line = fgets( $fh );
 274+ if ( $line !== false ) {
 275+ $line_number++;
 276+ $line = trim( $line );
 277+ if ( mt_rand( 0, $line_number ) <= $max_index ) {
 278+ $lines[ mt_rand( 0, $max_index ) ] = $line;
 279+ }
 280+ }
 281+ }
 282+ fclose( $fh );
 283+ if ( $line_number < $number_desired ) {
 284+ throw new Exception( "not enough lines in $filepath" );
 285+ }
 286+
 287+ return $lines;
 288+ }
 289+
 290+}
Property changes on: branches/uploadwizard/phase3/maintenance/tests/phpunit/includes/api/RandomImageGenerator.php
___________________________________________________________________
Added: svn:eol-style
1291 + native
Index: branches/uploadwizard/phase3/maintenance/tests/phpunit/includes/api/ApiUploadTest.php
@@ -0,0 +1,623 @@
 2+<?php
 3+
 4+/**
 5+ * n.b. Ensure that you can write to the images/ directory as the
 6+ * user that will run tests.
 7+ */
 8+
 9+// Note for reviewers: this intentionally duplicates functionality already in "ApiSetup" and so on.
 10+// This framework works better IMO and has less strangeness (such as test cases inheriting from "ApiSetup"...)
 11+// (and in the case of the other Upload tests, this flat out just actually works... )
 12+
 13+// TODO: refactor into several files
 14+// TODO: port the other Upload tests, and other API tests to this framework
 15+
 16+require_once( dirname( __FILE__ ) . '/RandomImageGenerator.php' );
 17+
 18+abstract class ApiTestCase extends PHPUnit_Framework_TestCase {
 19+ public static $apiUrl;
 20+ public static $users;
 21+
 22+ function setUp() {
 23+ global $wgServer, $wgContLang, $wgAuth, $wgMemc, $wgRequest, $wgUser;
 24+
 25+ self::$apiUrl = $wgServer . wfScript( 'api' );
 26+
 27+ $wgMemc = new FakeMemCachedClient();
 28+ $wgContLang = Language::factory( 'en' );
 29+ $wgAuth = new StubObject( 'wgAuth', 'AuthPlugin' );
 30+ $wgRequest = new FauxRequest( array() );
 31+
 32+ self::$users = array(
 33+ 'sysop' => new ApiTestUser(
 34+ 'Apitestsysop',
 35+ 'Api Test Sysop',
 36+ 'testpass',
 37+ 'api_test_sysop@sample.com',
 38+ array( 'sysop' )
 39+ ),
 40+ 'uploader' => new ApiTestUser(
 41+ 'Apitestuser',
 42+ 'Api Test User',
 43+ 'testpass',
 44+ 'api_test_user@sample.com',
 45+ array()
 46+ )
 47+ );
 48+
 49+ $wgUser = self::$users['sysop']->user;
 50+
 51+ }
 52+
 53+ protected function doApiRequest( $params, $session = null ) {
 54+ $_SESSION = isset( $session ) ? $session : array();
 55+
 56+ $request = new FauxRequest( $params, true, $_SESSION );
 57+ $module = new ApiMain( $request, true );
 58+ $module->execute();
 59+
 60+ return array( $module->getResultData(), $request, $_SESSION );
 61+ }
 62+
 63+ /**
 64+ * Add an edit token to the API request
 65+ * This is cheating a bit -- we grab a token in the correct format and then add it to the pseudo-session and to the
 66+ * request, without actually requesting a "real" edit token
 67+ * @param $params: key-value API params
 68+ * @param $data: a structure which also contains the session
 69+ */
 70+ protected function doApiRequestWithToken( $params, $session ) {
 71+ if ( $session['wsToken'] ) {
 72+ // add edit token to fake session
 73+ $session['wsEditToken'] = $session['wsToken'];
 74+ // add token to request parameters
 75+ $params['token'] = md5( $session['wsToken'] ) . EDIT_TOKEN_SUFFIX;
 76+ return $this->doApiRequest( $params, $session );
 77+ } else {
 78+ throw new Exception( "request data not in right format" );
 79+ }
 80+ }
 81+
 82+ function tearDown() {
 83+ global $wgMemc;
 84+ $wgMemc = null;
 85+ }
 86+
 87+}
 88+
 89+class ApiUploadTest extends ApiTestCase {
 90+ /**
 91+ * Fixture -- run before every test
 92+ */
 93+ public function setUp() {
 94+ global $wgEnableUploads, $wgEnableAPI, $wgDebugLogFile;
 95+ parent::setUp();
 96+
 97+ $wgEnableUploads = true;
 98+ $wgEnableAPI = true;
 99+ wfSetupSession();
 100+
 101+ $wgDebugLogFile = '/private/tmp/mwtestdebug.log';
 102+ ini_set( 'log_errors', 1 );
 103+ ini_set( 'error_reporting', 1 );
 104+ ini_set( 'display_errors', 1 );
 105+
 106+ $this->clearFakeUploads();
 107+ }
 108+
 109+ /**
 110+ * Fixture -- run after every test
 111+ * Clean up temporary files etc.
 112+ */
 113+ function tearDown() {
 114+ }
 115+
 116+
 117+ /**
 118+ * Testing login
 119+ * XXX this is a funny way of getting session context
 120+ */
 121+ function testLogin() {
 122+ $user = self::$users['uploader'];
 123+ $params = array(
 124+ 'action' => 'login',
 125+ 'lgname' => $user->username,
 126+ 'lgpassword' => $user->password
 127+ );
 128+ list( $result, $request, $session ) = $this->doApiRequest( $params );
 129+ $this->assertArrayHasKey( "login", $result );
 130+ $this->assertArrayHasKey( "result", $result['login'] );
 131+ $this->assertEquals( "NeedToken", $result['login']['result'] );
 132+ $token = $result['login']['token'];
 133+
 134+ $params = array(
 135+ 'action' => 'login',
 136+ 'lgtoken' => $token,
 137+ 'lgname' => $user->username,
 138+ 'lgpassword' => $user->password
 139+ );
 140+ list( $result, $request, $session ) = $this->doApiRequest( $params );
 141+ $this->assertArrayHasKey( "login", $result );
 142+ $this->assertArrayHasKey( "result", $result['login'] );
 143+ $this->assertEquals( "Success", $result['login']['result'] );
 144+ $this->assertArrayHasKey( 'lgtoken', $result['login'] );
 145+
 146+ return $session;
 147+
 148+ }
 149+
 150+ /**
 151+ * @depends testLogin
 152+ */
 153+ public function testUploadRequiresToken( $session ) {
 154+ $exception = false;
 155+ try {
 156+ $this->doApiRequest( array(
 157+ 'action' => 'upload'
 158+ ) );
 159+ } catch ( UsageException $e ) {
 160+ $exception = true;
 161+ $this->assertEquals( "The token parameter must be set", $e->getMessage() );
 162+ }
 163+ $this->assertTrue( $exception, "Got exception" );
 164+ }
 165+
 166+ /**
 167+ * @depends testLogin
 168+ */
 169+ public function testUploadMissingParams( $session ) {
 170+ global $wgUser;
 171+ $wgUser = self::$users['uploader']->user;
 172+
 173+ $exception = false;
 174+ try {
 175+ $this->doApiRequestWithToken( array(
 176+ 'action' => 'upload',
 177+ ), $session );
 178+ } catch ( UsageException $e ) {
 179+ $exception = true;
 180+ $this->assertEquals( "One of the parameters sessionkey, file, url, statuskey is required",
 181+ $e->getMessage() );
 182+ }
 183+ $this->assertTrue( $exception, "Got exception" );
 184+ }
 185+
 186+
 187+ /**
 188+ * @depends testLogin
 189+ */
 190+ public function testUpload( $session ) {
 191+ global $wgUser;
 192+ $wgUser = self::$users['uploader']->user;
 193+
 194+ $extension = 'png';
 195+ $mimeType = 'image/png';
 196+
 197+ $randomImageGenerator = new RandomImageGenerator();
 198+ $filePaths = $randomImageGenerator->writeImages( 1, $extension, dirname( wfTempDir() ) );
 199+ $filePath = $filePaths[0];
 200+ $fileName = basename( $filePath );
 201+
 202+ $this->deleteFileByFileName( $fileName );
 203+ $this->deleteFileByContent( $filePath );
 204+
 205+ if (! $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) {
 206+ $this->markTestIncomplete( "Couldn't upload file!\n" );
 207+ }
 208+
 209+ $params = array(
 210+ 'action' => 'upload',
 211+ 'filename' => $fileName,
 212+ 'file' => 'dummy content',
 213+ 'comment' => 'dummy comment',
 214+ 'text' => "This is the page text for $fileName",
 215+ );
 216+
 217+ $exception = false;
 218+ try {
 219+ list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
 220+ } catch ( UsageException $e ) {
 221+ $exception = true;
 222+ }
 223+ $this->assertTrue( isset( $result['upload'] ) );
 224+ $this->assertEquals( 'Success', $result['upload']['result'] );
 225+ $this->assertFalse( $exception );
 226+
 227+ // clean up
 228+ $this->deleteFileByFilename( $fileName );
 229+ unlink( $filePath );
 230+ }
 231+
 232+
 233+ /**
 234+ * @depends testLogin
 235+ */
 236+ public function testUploadZeroLength( $session ) {
 237+ global $wgUser;
 238+ $wgUser = self::$users['uploader']->user;
 239+
 240+ $extension = 'png';
 241+ $mimeType = 'image/png';
 242+
 243+ $filePath = tempnam( wfTempDir(), "" );
 244+ $fileName = "apiTestUploadZeroLength.png";
 245+
 246+ $this->deleteFileByFileName( $fileName );
 247+
 248+ if (! $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) {
 249+ $this->markTestIncomplete( "Couldn't upload file!\n" );
 250+ }
 251+
 252+ $params = array(
 253+ 'action' => 'upload',
 254+ 'filename' => $fileName,
 255+ 'file' => 'dummy content',
 256+ 'comment' => 'dummy comment',
 257+ 'text' => "This is the page text for $fileName",
 258+ );
 259+
 260+ $exception = false;
 261+ try {
 262+ list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
 263+ } catch ( UsageException $e ) {
 264+ $this->assertContains( 'The file you submitted was empty', $e->getMessage() );
 265+ $exception = true;
 266+ }
 267+ $this->assertTrue( $exception );
 268+
 269+ // clean up
 270+ $this->deleteFileByFilename( $fileName );
 271+ unlink( $filePath );
 272+ }
 273+
 274+
 275+ /**
 276+ * @depends testLogin
 277+ */
 278+ public function testUploadSameFileName( $session ) {
 279+ global $wgUser;
 280+ $wgUser = self::$users['uploader']->user;
 281+
 282+ $extension = 'png';
 283+ $mimeType = 'image/png';
 284+
 285+ $randomImageGenerator = new RandomImageGenerator();
 286+ $filePaths = $randomImageGenerator->writeImages( 2, $extension, dirname( wfTempDir() ) );
 287+ // we'll reuse this filename
 288+ $fileName = basename( $filePaths[0] );
 289+
 290+ // clear any other files with the same name
 291+ $this->deleteFileByFileName( $fileName );
 292+
 293+ // we reuse these params
 294+ $params = array(
 295+ 'action' => 'upload',
 296+ 'filename' => $fileName,
 297+ 'file' => 'dummy content',
 298+ 'comment' => 'dummy comment',
 299+ 'text' => "This is the page text for $fileName",
 300+ );
 301+
 302+ // first upload .... should succeed
 303+
 304+ if (! $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[0] ) ) {
 305+ $this->markTestIncomplete( "Couldn't upload file!\n" );
 306+ }
 307+
 308+ $exception = false;
 309+ try {
 310+ list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
 311+ } catch ( UsageException $e ) {
 312+ $exception = true;
 313+ }
 314+ $this->assertTrue( isset( $result['upload'] ) );
 315+ $this->assertEquals( 'Success', $result['upload']['result'] );
 316+ $this->assertFalse( $exception );
 317+
 318+ // second upload with the same name (but different content)
 319+
 320+ if (! $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[1] ) ) {
 321+ $this->markTestIncomplete( "Couldn't upload file!\n" );
 322+ }
 323+
 324+ $exception = false;
 325+ try {
 326+ list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
 327+ } catch ( UsageException $e ) {
 328+ $exception = true;
 329+ }
 330+ $this->assertTrue( isset( $result['upload'] ) );
 331+ $this->assertEquals( 'Warning', $result['upload']['result'] );
 332+ $this->assertTrue( isset( $result['upload']['warnings'] ) );
 333+ $this->assertTrue( isset( $result['upload']['warnings']['exists'] ) );
 334+ $this->assertFalse( $exception );
 335+
 336+ // clean up
 337+ $this->deleteFileByFilename( $fileName );
 338+ unlink( $filePaths[0] );
 339+ unlink( $filePaths[1] );
 340+ }
 341+
 342+
 343+ /**
 344+ * @depends testLogin
 345+ */
 346+ public function testUploadSameContent( $session ) {
 347+ global $wgUser;
 348+ $wgUser = self::$users['uploader']->user;
 349+
 350+ $extension = 'png';
 351+ $mimeType = 'image/png';
 352+
 353+ $randomImageGenerator = new RandomImageGenerator();
 354+ $filePaths = $randomImageGenerator->writeImages( 1, $extension, dirname( wfTempDir() ) );
 355+ $fileNames[0] = basename( $filePaths[0] );
 356+ $fileNames[1] = "SameContentAs" . $fileNames[0];
 357+
 358+ // clear any other files with the same name or content
 359+ $this->deleteFileByContent( $filePaths[0] );
 360+ $this->deleteFileByFileName( $fileNames[0] );
 361+ $this->deleteFileByFileName( $fileNames[1] );
 362+
 363+ // first upload .... should succeed
 364+
 365+ $params = array(
 366+ 'action' => 'upload',
 367+ 'filename' => $fileNames[0],
 368+ 'file' => 'dummy content',
 369+ 'comment' => 'dummy comment',
 370+ 'text' => "This is the page text for " . $fileNames[0],
 371+ );
 372+
 373+ if (! $this->fakeUploadFile( 'file', $fileNames[0], $mimeType, $filePaths[0] ) ) {
 374+ $this->markTestIncomplete( "Couldn't upload file!\n" );
 375+ }
 376+
 377+ $exception = false;
 378+ try {
 379+ list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
 380+ } catch ( UsageException $e ) {
 381+ $exception = true;
 382+ }
 383+ $this->assertTrue( isset( $result['upload'] ) );
 384+ $this->assertEquals( 'Success', $result['upload']['result'] );
 385+ $this->assertFalse( $exception );
 386+
 387+
 388+ // second upload with the same content (but different name)
 389+
 390+ if (! $this->fakeUploadFile( 'file', $fileNames[1], $mimeType, $filePaths[0] ) ) {
 391+ $this->markTestIncomplete( "Couldn't upload file!\n" );
 392+ }
 393+
 394+ $params = array(
 395+ 'action' => 'upload',
 396+ 'filename' => $fileNames[1],
 397+ 'file' => 'dummy content',
 398+ 'comment' => 'dummy comment',
 399+ 'text' => "This is the page text for " . $fileNames[1],
 400+ );
 401+
 402+ $exception = false;
 403+ try {
 404+ list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
 405+ } catch ( UsageException $e ) {
 406+ $exception = true;
 407+ }
 408+ $this->assertTrue( isset( $result['upload'] ) );
 409+ $this->assertEquals( 'Warning', $result['upload']['result'] );
 410+ $this->assertTrue( isset( $result['upload']['warnings'] ) );
 411+ $this->assertTrue( isset( $result['upload']['warnings']['duplicate'] ) );
 412+ $this->assertFalse( $exception );
 413+
 414+ // clean up
 415+ $this->deleteFileByFilename( $fileNames[0] );
 416+ $this->deleteFileByFilename( $fileNames[1] );
 417+ unlink( $filePaths[0] );
 418+ }
 419+
 420+
 421+ /**
 422+ * @depends testLogin
 423+ */
 424+ public function testUploadStash( $session ) {
 425+ global $wgUser;
 426+ $wgUser = self::$users['uploader']->user;
 427+
 428+ $extension = 'png';
 429+ $mimeType = 'image/png';
 430+
 431+ $randomImageGenerator = new RandomImageGenerator();
 432+ $filePaths = $randomImageGenerator->writeImages( 1, $extension, dirname( wfTempDir() ) );
 433+ $filePath = $filePaths[0];
 434+ $fileName = basename( $filePath );
 435+
 436+ $this->deleteFileByFileName( $fileName );
 437+ $this->deleteFileByContent( $filePath );
 438+
 439+ if (! $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) {
 440+ $this->markTestIncomplete( "Couldn't upload file!\n" );
 441+ }
 442+
 443+ $params = array(
 444+ 'action' => 'upload',
 445+ 'stash' => 1,
 446+ 'filename' => $fileName,
 447+ 'file' => 'dummy content',
 448+ 'comment' => 'dummy comment',
 449+ 'text' => "This is the page text for $fileName",
 450+ );
 451+
 452+ $exception = false;
 453+ try {
 454+ list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
 455+ } catch ( UsageException $e ) {
 456+ $exception = true;
 457+ }
 458+ $this->assertFalse( $exception );
 459+ $this->assertTrue( isset( $result['upload'] ) );
 460+ $this->assertEquals( 'Success', $result['upload']['result'] );
 461+ $this->assertTrue( isset( $result['upload']['sessionkey'] ) );
 462+ $sessionkey = $result['upload']['sessionkey'];
 463+
 464+ // it should be visible from Special:SessionStash
 465+ // XXX ...but how to test this, with a fake WebRequest with the session?
 466+
 467+ // now we should try to release the file from stash
 468+ $params = array(
 469+ 'action' => 'upload',
 470+ 'sessionkey' => $sessionkey,
 471+ 'filename' => $fileName,
 472+ 'comment' => 'dummy comment',
 473+ 'text' => "This is the page text for $fileName, altered",
 474+ );
 475+
 476+ $this->clearFakeUploads();
 477+ $exception = false;
 478+ try {
 479+ list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
 480+ } catch ( UsageException $e ) {
 481+ $exception = true;
 482+ }
 483+ $this->assertTrue( isset( $result['upload'] ) );
 484+ $this->assertEquals( 'Success', $result['upload']['result'] );
 485+ $this->assertFalse( $exception );
 486+
 487+ // clean up
 488+ $this->deleteFileByFilename( $fileName );
 489+ unlink( $filePath );
 490+ }
 491+
 492+
 493+
 494+ /**
 495+ * Helper function -- remove files and associated articles by Title
 496+ * @param {Title} title to be removed
 497+ */
 498+ public function deleteFileByTitle( $title ) {
 499+ if ( $title->exists() ) {
 500+ $file = wfFindFile( $title, array( 'ignoreRedirect' => true ) );
 501+ $noOldArchive = ""; // yes this really needs to be set this way
 502+ $comment = "removing for test";
 503+ $restrictDeletedVersions = false;
 504+ $status = FileDeleteForm::doDelete( $title, $file, $noOldArchive, $comment, $restrictDeletedVersions );
 505+ if ( !$status->isGood() ) {
 506+ return false;
 507+ }
 508+ $article = new Article( $title );
 509+ $article->doDeleteArticle( "removing for test" );
 510+
 511+ // see if it now doesn't exist; reload
 512+ $title = Title::newFromText( $fileName, NS_FILE );
 513+ }
 514+ return ! ( $title && is_a( $title, 'Title' ) && $title->exists() );
 515+ }
 516+
 517+ /**
 518+ * Helper function -- remove files and associated articles with a particular filename
 519+ * @param {String} filename to be removed
 520+ */
 521+ public function deleteFileByFileName( $fileName ) {
 522+ return $this->deleteFileByTitle( Title::newFromText( $fileName, NS_FILE ) );
 523+ }
 524+
 525+
 526+ /**
 527+ * Helper function -- given a file on the filesystem, find matching content in the db (and associated articles) and remove them.
 528+ * @param {String} path to file on the filesystem
 529+ */
 530+ public function deleteFileByContent( $filePath ) {
 531+ $hash = File::sha1Base36( $filePath );
 532+ $dupes = RepoGroup::singleton()->findBySha1( $hash );
 533+ $success = true;
 534+ foreach ( $dupes as $key => $dupe ) {
 535+ $success &= $this->deleteFileByTitle( $dupe->getTitle() );
 536+ }
 537+ return $success;
 538+ }
 539+
 540+ /**
 541+ * Fake an upload by dumping the file into temp space, and adding info to $_FILES.
 542+ * (This is what PHP would normally do).
 543+ * @param {String}: fieldname - name this would have in the upload form
 544+ * @param {String}: fileName - name to title this
 545+ * @param {String}: mime type
 546+ * @param {String}: filePath - path where to find file contents
 547+ */
 548+ function fakeUploadFile( $fieldName, $fileName, $type, $filePath ) {
 549+ $tmpName = tempnam( wfTempDir(), "" );
 550+ if ( !file_exists( $filePath ) ) {
 551+ throw new Exception( "$filePath doesn't exist!" );
 552+ };
 553+
 554+ if ( !copy( $filePath, $tmpName ) ) {
 555+ throw new Exception( "couldn't copy $filePath to $tmpName" );
 556+ }
 557+
 558+ clearstatcache();
 559+ $size = filesize( $tmpName );
 560+ if ( $size === false ) {
 561+ throw new Exception( "couldn't stat $tmpName" );
 562+ }
 563+
 564+ $_FILES[ $fieldName ] = array(
 565+ 'name' => $fileName,
 566+ 'type' => $type,
 567+ 'tmp_name' => $tmpName,
 568+ 'size' => $size,
 569+ 'error' => null
 570+ );
 571+
 572+ return true;
 573+
 574+ }
 575+
 576+ /**
 577+ * Remove traces of previous fake uploads
 578+ */
 579+ function clearFakeUploads() {
 580+ $_FILES = array();
 581+ }
 582+
 583+
 584+}
 585+
 586+/* Wraps the user object, so we can also retain full access to properties like password if we log in via the API */
 587+class ApiTestUser {
 588+ public $username;
 589+ public $password;
 590+ public $email;
 591+ public $groups;
 592+ public $user;
 593+
 594+ function __construct( $username, $realname = 'Real Name', $password = 'testpass', $email = 'sample@sample.com', $groups = array() ) {
 595+ $this->username = $username;
 596+ $this->realname = $realname;
 597+ $this->password = $password;
 598+ $this->email = $email;
 599+ $this->groups = $groups;
 600+
 601+ $this->user = User::newFromName( $this->username );
 602+ $this->user->load();
 603+ if ( !$this->user->getID() ) {
 604+ $this->user = User::createNew(
 605+ $this->username, array(
 606+ "email" => $this->email,
 607+ "real_name" => $this->realname
 608+ )
 609+ );
 610+ if ( !$this->user ) {
 611+ throw new Exception( "error creating user" );
 612+ }
 613+ $this->user->setPassword( $this->password );
 614+ if ( count( $this->groups ) ) {
 615+ foreach ( $this->groups as $group ) {
 616+ $this->user->addGroup( $group );
 617+ }
 618+ }
 619+ $this->user->saveSettings();
 620+ }
 621+ }
 622+
 623+
 624+ }
Property changes on: branches/uploadwizard/phase3/maintenance/tests/phpunit/includes/api/ApiUploadTest.php
___________________________________________________________________
Added: svn:eol-style
1625 + native

Status & tagging log