diff options
Diffstat (limited to 'tests/phpunit/includes/api')
28 files changed, 3062 insertions, 208 deletions
diff --git a/tests/phpunit/includes/api/ApiContinuationManagerTest.php b/tests/phpunit/includes/api/ApiContinuationManagerTest.php new file mode 100644 index 00000000..2edf0c6f --- /dev/null +++ b/tests/phpunit/includes/api/ApiContinuationManagerTest.php @@ -0,0 +1,195 @@ +<?php + +/** + * @covers ApiContinuationManager + * @group API + */ +class ApiContinuationManagerTest extends MediaWikiTestCase { + + private static function getManager( $continue, $allModules, $generatedModules ) { + $context = new DerivativeContext( RequestContext::getMain() ); + $context->setRequest( new FauxRequest( array( 'continue' => $continue ) ) ); + $main = new ApiMain( $context ); + return new ApiContinuationManager( $main, $allModules, $generatedModules ); + } + + public function testContinuation() { + $allModules = array( + new MockApiQueryBase( 'mock1' ), + new MockApiQueryBase( 'mock2' ), + new MockApiQueryBase( 'mocklist' ), + ); + $generator = new MockApiQueryBase( 'generator' ); + + $manager = self::getManager( '', $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( 'ApiMain', $manager->getSource() ); + $this->assertSame( false, $manager->isGeneratorDone() ); + $this->assertSame( $allModules, $manager->getRunModules() ); + $manager->addContinueParam( $allModules[0], 'm1continue', array( 1, 2 ) ); + $manager->addContinueParam( $allModules[2], 'mlcontinue', 2 ); + $manager->addGeneratorContinueParam( $generator, 'gcontinue', 3 ); + $this->assertSame( array( array( + 'mlcontinue' => 2, + 'm1continue' => '1|2', + 'continue' => '||mock2', + ), false ), $manager->getContinuation() ); + $this->assertSame( array( + 'mock1' => array( 'm1continue' => '1|2' ), + 'mocklist' => array( 'mlcontinue' => 2 ), + 'generator' => array( 'gcontinue' => 3 ), + ), $manager->getRawContinuation() ); + + $result = new ApiResult( 0 ); + $manager->setContinuationIntoResult( $result ); + $this->assertSame( array( + 'mlcontinue' => 2, + 'm1continue' => '1|2', + 'continue' => '||mock2', + ), $result->getResultData( 'continue' ) ); + $this->assertSame( null, $result->getResultData( 'batchcomplete' ) ); + + $manager = self::getManager( '', $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( false, $manager->isGeneratorDone() ); + $this->assertSame( $allModules, $manager->getRunModules() ); + $manager->addContinueParam( $allModules[0], 'm1continue', array( 1, 2 ) ); + $manager->addGeneratorContinueParam( $generator, 'gcontinue', array( 3, 4 ) ); + $this->assertSame( array( array( + 'm1continue' => '1|2', + 'continue' => '||mock2|mocklist', + ), false ), $manager->getContinuation() ); + $this->assertSame( array( + 'mock1' => array( 'm1continue' => '1|2' ), + 'generator' => array( 'gcontinue' => '3|4' ), + ), $manager->getRawContinuation() ); + + $manager = self::getManager( '', $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( false, $manager->isGeneratorDone() ); + $this->assertSame( $allModules, $manager->getRunModules() ); + $manager->addContinueParam( $allModules[2], 'mlcontinue', 2 ); + $manager->addGeneratorContinueParam( $generator, 'gcontinue', 3 ); + $this->assertSame( array( array( + 'mlcontinue' => 2, + 'gcontinue' => 3, + 'continue' => 'gcontinue||', + ), true ), $manager->getContinuation() ); + $this->assertSame( array( + 'mocklist' => array( 'mlcontinue' => 2 ), + 'generator' => array( 'gcontinue' => 3 ), + ), $manager->getRawContinuation() ); + + $result = new ApiResult( 0 ); + $manager->setContinuationIntoResult( $result ); + $this->assertSame( array( + 'mlcontinue' => 2, + 'gcontinue' => 3, + 'continue' => 'gcontinue||', + ), $result->getResultData( 'continue' ) ); + $this->assertSame( true, $result->getResultData( 'batchcomplete' ) ); + + $manager = self::getManager( '', $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( false, $manager->isGeneratorDone() ); + $this->assertSame( $allModules, $manager->getRunModules() ); + $manager->addGeneratorContinueParam( $generator, 'gcontinue', 3 ); + $this->assertSame( array( array( + 'gcontinue' => 3, + 'continue' => 'gcontinue||mocklist', + ), true ), $manager->getContinuation() ); + $this->assertSame( array( + 'generator' => array( 'gcontinue' => 3 ), + ), $manager->getRawContinuation() ); + + $manager = self::getManager( '', $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( false, $manager->isGeneratorDone() ); + $this->assertSame( $allModules, $manager->getRunModules() ); + $manager->addContinueParam( $allModules[0], 'm1continue', array( 1, 2 ) ); + $manager->addContinueParam( $allModules[2], 'mlcontinue', 2 ); + $this->assertSame( array( array( + 'mlcontinue' => 2, + 'm1continue' => '1|2', + 'continue' => '||mock2', + ), false ), $manager->getContinuation() ); + $this->assertSame( array( + 'mock1' => array( 'm1continue' => '1|2' ), + 'mocklist' => array( 'mlcontinue' => 2 ), + ), $manager->getRawContinuation() ); + + $manager = self::getManager( '', $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( false, $manager->isGeneratorDone() ); + $this->assertSame( $allModules, $manager->getRunModules() ); + $manager->addContinueParam( $allModules[0], 'm1continue', array( 1, 2 ) ); + $this->assertSame( array( array( + 'm1continue' => '1|2', + 'continue' => '||mock2|mocklist', + ), false ), $manager->getContinuation() ); + $this->assertSame( array( + 'mock1' => array( 'm1continue' => '1|2' ), + ), $manager->getRawContinuation() ); + + $manager = self::getManager( '', $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( false, $manager->isGeneratorDone() ); + $this->assertSame( $allModules, $manager->getRunModules() ); + $manager->addContinueParam( $allModules[2], 'mlcontinue', 2 ); + $this->assertSame( array( array( + 'mlcontinue' => 2, + 'continue' => '-||mock1|mock2', + ), true ), $manager->getContinuation() ); + $this->assertSame( array( + 'mocklist' => array( 'mlcontinue' => 2 ), + ), $manager->getRawContinuation() ); + + $manager = self::getManager( '', $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( false, $manager->isGeneratorDone() ); + $this->assertSame( $allModules, $manager->getRunModules() ); + $this->assertSame( array( array(), true ), $manager->getContinuation() ); + $this->assertSame( array(), $manager->getRawContinuation() ); + + $manager = self::getManager( '||mock2', $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( false, $manager->isGeneratorDone() ); + $this->assertSame( + array_values( array_diff_key( $allModules, array( 1 => 1 ) ) ), + $manager->getRunModules() + ); + + $manager = self::getManager( '-||', $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( true, $manager->isGeneratorDone() ); + $this->assertSame( + array_values( array_diff_key( $allModules, array( 0 => 0, 1 => 1 ) ) ), + $manager->getRunModules() + ); + + try { + self::getManager( 'foo', $allModules, array( 'mock1', 'mock2' ) ); + $this->fail( 'Expected exception not thrown' ); + } catch ( UsageException $ex ) { + $this->assertSame( + 'Invalid continue param. You should pass the original value returned by the previous query', + $ex->getMessage(), + 'Expected exception' + ); + } + + $manager = self::getManager( '||mock2', array_slice( $allModules, 0, 2 ), array( 'mock1', 'mock2' ) ); + try { + $manager->addContinueParam( $allModules[1], 'm2continue', 1 ); + $this->fail( 'Expected exception not thrown' ); + } catch ( UnexpectedValueException $ex ) { + $this->assertSame( + 'Module \'mock2\' was not supposed to have been executed, but it was executed anyway', + $ex->getMessage(), + 'Expected exception' + ); + } + try { + $manager->addContinueParam( $allModules[2], 'mlcontinue', 1 ); + $this->fail( 'Expected exception not thrown' ); + } catch ( UnexpectedValueException $ex ) { + $this->assertSame( + 'Module \'mocklist\' called ApiContinuationManager::addContinueParam but was not passed to ApiContinuationManager::__construct', + $ex->getMessage(), + 'Expected exception' + ); + } + + } + +} diff --git a/tests/phpunit/includes/api/ApiErrorFormatterTest.php b/tests/phpunit/includes/api/ApiErrorFormatterTest.php new file mode 100644 index 00000000..8ebdf60f --- /dev/null +++ b/tests/phpunit/includes/api/ApiErrorFormatterTest.php @@ -0,0 +1,351 @@ +<?php + +/** + * @group API + */ +class ApiErrorFormatterTest extends MediaWikiTestCase { + + /** + * @covers ApiErrorFormatter + * @dataProvider provideErrorFormatter + */ + public function testErrorFormatter( $format, $lang, $useDB, + $expect1, $expect2, $expect3 + ) { + $result = new ApiResult( 8388608 ); + $formatter = new ApiErrorFormatter( $result, Language::factory( $lang ), $format, $useDB ); + + // Add default type + $expect1[ApiResult::META_TYPE] = 'assoc'; + $expect2[ApiResult::META_TYPE] = 'assoc'; + $expect3[ApiResult::META_TYPE] = 'assoc'; + + $formatter->addWarning( 'string', 'mainpage' ); + $formatter->addError( 'err', 'mainpage' ); + $this->assertSame( $expect1, $result->getResultData(), 'Simple test' ); + + $result->reset(); + $formatter->addWarning( 'foo', 'mainpage' ); + $formatter->addWarning( 'foo', 'mainpage' ); + $formatter->addWarning( 'foo', array( 'parentheses', 'foobar' ) ); + $msg1 = wfMessage( 'mainpage' ); + $formatter->addWarning( 'message', $msg1 ); + $msg2 = new ApiMessage( 'mainpage', 'overriddenCode', array( 'overriddenData' => true ) ); + $formatter->addWarning( 'messageWithData', $msg2 ); + $formatter->addError( 'errWithData', $msg2 ); + $this->assertSame( $expect2, $result->getResultData(), 'Complex test' ); + + $result->reset(); + $status = Status::newGood(); + $status->warning( 'mainpage' ); + $status->warning( 'parentheses', 'foobar' ); + $status->warning( $msg1 ); + $status->warning( $msg2 ); + $status->error( 'mainpage' ); + $status->error( 'parentheses', 'foobar' ); + $formatter->addMessagesFromStatus( 'status', $status ); + $this->assertSame( $expect3, $result->getResultData(), 'Status test' ); + + $this->assertSame( + $expect3['errors']['status'], + $formatter->arrayFromStatus( $status, 'error' ), + 'arrayFromStatus test for error' + ); + $this->assertSame( + $expect3['warnings']['status'], + $formatter->arrayFromStatus( $status, 'warning' ), + 'arrayFromStatus test for warning' + ); + } + + public static function provideErrorFormatter() { + $mainpagePlain = wfMessage( 'mainpage' )->useDatabase( false )->plain(); + $parensPlain = wfMessage( 'parentheses', 'foobar' )->useDatabase( false )->plain(); + $mainpageText = wfMessage( 'mainpage' )->inLanguage( 'de' )->text(); + $parensText = wfMessage( 'parentheses', 'foobar' )->inLanguage( 'de' )->text(); + $C = ApiResult::META_CONTENT; + $I = ApiResult::META_INDEXED_TAG_NAME; + + return array( + array( 'wikitext', 'de', true, + array( + 'errors' => array( + 'err' => array( + array( 'code' => 'mainpage', 'text' => $mainpageText, $C => 'text' ), + $I => 'error', + ), + ), + 'warnings' => array( + 'string' => array( + array( 'code' => 'mainpage', 'text' => $mainpageText, $C => 'text' ), + $I => 'warning', + ), + ), + ), + array( + 'errors' => array( + 'errWithData' => array( + array( 'code' => 'overriddenCode', 'text' => $mainpageText, + 'overriddenData' => true, $C => 'text' ), + $I => 'error', + ), + ), + 'warnings' => array( + 'messageWithData' => array( + array( 'code' => 'overriddenCode', 'text' => $mainpageText, + 'overriddenData' => true, $C => 'text' ), + $I => 'warning', + ), + 'message' => array( + array( 'code' => 'mainpage', 'text' => $mainpageText, $C => 'text' ), + $I => 'warning', + ), + 'foo' => array( + array( 'code' => 'mainpage', 'text' => $mainpageText, $C => 'text' ), + array( 'code' => 'parentheses', 'text' => $parensText, $C => 'text' ), + $I => 'warning', + ), + ), + ), + array( + 'errors' => array( + 'status' => array( + array( 'code' => 'mainpage', 'text' => $mainpageText, $C => 'text' ), + array( 'code' => 'parentheses', 'text' => $parensText, $C => 'text' ), + $I => 'error', + ), + ), + 'warnings' => array( + 'status' => array( + array( 'code' => 'mainpage', 'text' => $mainpageText, $C => 'text' ), + array( 'code' => 'parentheses', 'text' => $parensText, $C => 'text' ), + array( 'code' => 'overriddenCode', 'text' => $mainpageText, + 'overriddenData' => true, $C => 'text' ), + $I => 'warning', + ), + ), + ), + ), + array( 'raw', 'fr', true, + array( + 'errors' => array( + 'err' => array( + array( 'code' => 'mainpage', 'message' => 'mainpage', 'params' => array( $I => 'param' ) ), + $I => 'error', + ), + ), + 'warnings' => array( + 'string' => array( + array( 'code' => 'mainpage', 'message' => 'mainpage', 'params' => array( $I => 'param' ) ), + $I => 'warning', + ), + ), + ), + array( + 'errors' => array( + 'errWithData' => array( + array( 'code' => 'overriddenCode', 'message' => 'mainpage', 'params' => array( $I => 'param' ), + 'overriddenData' => true ), + $I => 'error', + ), + ), + 'warnings' => array( + 'messageWithData' => array( + array( 'code' => 'overriddenCode', 'message' => 'mainpage', 'params' => array( $I => 'param' ), + 'overriddenData' => true ), + $I => 'warning', + ), + 'message' => array( + array( 'code' => 'mainpage', 'message' => 'mainpage', 'params' => array( $I => 'param' ) ), + $I => 'warning', + ), + 'foo' => array( + array( 'code' => 'mainpage', 'message' => 'mainpage', 'params' => array( $I => 'param' ) ), + array( 'code' => 'parentheses', 'message' => 'parentheses', 'params' => array( 'foobar', $I => 'param' ) ), + $I => 'warning', + ), + ), + ), + array( + 'errors' => array( + 'status' => array( + array( 'code' => 'mainpage', 'message' => 'mainpage', 'params' => array( $I => 'param' ) ), + array( 'code' => 'parentheses', 'message' => 'parentheses', 'params' => array( 'foobar', $I => 'param' ) ), + $I => 'error', + ), + ), + 'warnings' => array( + 'status' => array( + array( 'code' => 'mainpage', 'message' => 'mainpage', 'params' => array( $I => 'param' ) ), + array( 'code' => 'parentheses', 'message' => 'parentheses', 'params' => array( 'foobar', $I => 'param' ) ), + array( 'code' => 'overriddenCode', 'message' => 'mainpage', 'params' => array( $I => 'param' ), + 'overriddenData' => true ), + $I => 'warning', + ), + ), + ), + ), + array( 'none', 'fr', true, + array( + 'errors' => array( + 'err' => array( + array( 'code' => 'mainpage' ), + $I => 'error', + ), + ), + 'warnings' => array( + 'string' => array( + array( 'code' => 'mainpage' ), + $I => 'warning', + ), + ), + ), + array( + 'errors' => array( + 'errWithData' => array( + array( 'code' => 'overriddenCode', 'overriddenData' => true ), + $I => 'error', + ), + ), + 'warnings' => array( + 'messageWithData' => array( + array( 'code' => 'overriddenCode', 'overriddenData' => true ), + $I => 'warning', + ), + 'message' => array( + array( 'code' => 'mainpage' ), + $I => 'warning', + ), + 'foo' => array( + array( 'code' => 'mainpage' ), + array( 'code' => 'parentheses' ), + $I => 'warning', + ), + ), + ), + array( + 'errors' => array( + 'status' => array( + array( 'code' => 'mainpage' ), + array( 'code' => 'parentheses' ), + $I => 'error', + ), + ), + 'warnings' => array( + 'status' => array( + array( 'code' => 'mainpage' ), + array( 'code' => 'parentheses' ), + array( 'code' => 'overriddenCode', 'overriddenData' => true ), + $I => 'warning', + ), + ), + ), + ), + ); + } + + /** + * @covers ApiErrorFormatter_BackCompat + */ + public function testErrorFormatterBC() { + $mainpagePlain = wfMessage( 'mainpage' )->useDatabase( false )->plain(); + $parensPlain = wfMessage( 'parentheses', 'foobar' )->useDatabase( false )->plain(); + + $result = new ApiResult( 8388608 ); + $formatter = new ApiErrorFormatter_BackCompat( $result ); + + $formatter->addWarning( 'string', 'mainpage' ); + $formatter->addError( 'err', 'mainpage' ); + $this->assertSame( array( + 'error' => array( + 'code' => 'mainpage', + 'info' => $mainpagePlain, + ), + 'warnings' => array( + 'string' => array( + 'warnings' => $mainpagePlain, + ApiResult::META_CONTENT => 'warnings', + ), + ), + ApiResult::META_TYPE => 'assoc', + ), $result->getResultData(), 'Simple test' ); + + $result->reset(); + $formatter->addWarning( 'foo', 'mainpage' ); + $formatter->addWarning( 'foo', 'mainpage' ); + $formatter->addWarning( 'foo', array( 'parentheses', 'foobar' ) ); + $msg1 = wfMessage( 'mainpage' ); + $formatter->addWarning( 'message', $msg1 ); + $msg2 = new ApiMessage( 'mainpage', 'overriddenCode', array( 'overriddenData' => true ) ); + $formatter->addWarning( 'messageWithData', $msg2 ); + $formatter->addError( 'errWithData', $msg2 ); + $this->assertSame( array( + 'error' => array( + 'code' => 'overriddenCode', + 'info' => $mainpagePlain, + 'overriddenData' => true, + ), + 'warnings' => array( + 'messageWithData' => array( + 'warnings' => $mainpagePlain, + ApiResult::META_CONTENT => 'warnings', + ), + 'message' => array( + 'warnings' => $mainpagePlain, + ApiResult::META_CONTENT => 'warnings', + ), + 'foo' => array( + 'warnings' => "$mainpagePlain\n$parensPlain", + ApiResult::META_CONTENT => 'warnings', + ), + ), + ApiResult::META_TYPE => 'assoc', + ), $result->getResultData(), 'Complex test' ); + + $result->reset(); + $status = Status::newGood(); + $status->warning( 'mainpage' ); + $status->warning( 'parentheses', 'foobar' ); + $status->warning( $msg1 ); + $status->warning( $msg2 ); + $status->error( 'mainpage' ); + $status->error( 'parentheses', 'foobar' ); + $formatter->addMessagesFromStatus( 'status', $status ); + $this->assertSame( array( + 'error' => array( + 'code' => 'parentheses', + 'info' => $parensPlain, + ), + 'warnings' => array( + 'status' => array( + 'warnings' => "$mainpagePlain\n$parensPlain", + ApiResult::META_CONTENT => 'warnings', + ), + ), + ApiResult::META_TYPE => 'assoc', + ), $result->getResultData(), 'Status test' ); + + $I = ApiResult::META_INDEXED_TAG_NAME; + $this->assertSame( + array( + array( 'type' => 'error', 'message' => 'mainpage', 'params' => array( $I => 'param' ) ), + array( 'type' => 'error', 'message' => 'parentheses', 'params' => array( 'foobar', $I => 'param' ) ), + $I => 'error', + ), + $formatter->arrayFromStatus( $status, 'error' ), + 'arrayFromStatus test for error' + ); + $this->assertSame( + array( + array( 'type' => 'warning', 'message' => 'mainpage', 'params' => array( $I => 'param' ) ), + array( 'type' => 'warning', 'message' => 'parentheses', 'params' => array( 'foobar', $I => 'param' ) ), + array( 'message' => 'mainpage', 'params' => array( $I => 'param' ), 'type' => 'warning' ), + array( 'message' => 'mainpage', 'params' => array( $I => 'param' ), 'type' => 'warning' ), + $I => 'warning', + ), + $formatter->arrayFromStatus( $status, 'warning' ), + 'arrayFromStatus test for warning' + ); + } + +} diff --git a/tests/phpunit/includes/api/ApiLoginTest.php b/tests/phpunit/includes/api/ApiLoginTest.php index 67a75f36..88a99e9b 100644 --- a/tests/phpunit/includes/api/ApiLoginTest.php +++ b/tests/phpunit/includes/api/ApiLoginTest.php @@ -123,7 +123,8 @@ class ApiLoginTest extends ApiTestCase { "lgname" => $user->username, "lgpassword" => $user->password ) - ) + ), + __METHOD__ ); $req->execute(); diff --git a/tests/phpunit/includes/api/ApiMainTest.php b/tests/phpunit/includes/api/ApiMainTest.php index 780cf9ed..e8ef1804 100644 --- a/tests/phpunit/includes/api/ApiMainTest.php +++ b/tests/phpunit/includes/api/ApiMainTest.php @@ -2,7 +2,6 @@ /** * @group API - * @group Database * @group medium * * @covers ApiMain @@ -10,41 +9,25 @@ class ApiMainTest extends ApiTestCase { /** - * Test that the API will accept a FauxRequest and execute. The help action - * (default) throws a UsageException. Just validate we're getting proper XML - * - * @expectedException UsageException + * Test that the API will accept a FauxRequest and execute. */ public function testApi() { $api = new ApiMain( - new FauxRequest( array( 'action' => 'help', 'format' => 'xml' ) ) + new FauxRequest( array( 'action' => 'query', 'meta' => 'siteinfo' ) ) ); $api->execute(); - $api->getPrinter()->setBufferResult( true ); - $api->printResult( false ); - $resp = $api->getPrinter()->getBuffer(); - - libxml_use_internal_errors( true ); - $sxe = simplexml_load_string( $resp ); - $this->assertNotInternalType( "bool", $sxe ); - $this->assertThat( $sxe, $this->isInstanceOf( "SimpleXMLElement" ) ); + $data = $api->getResult()->getResultData(); + $this->assertInternalType( 'array', $data ); + $this->assertArrayHasKey( 'query', $data ); } public static function provideAssert() { - $anon = new User(); - $bot = new User(); - $bot->setName( 'Bot' ); - $bot->addToDatabase(); - $bot->addGroup( 'bot' ); - $user = new User(); - $user->setName( 'User' ); - $user->addToDatabase(); return array( - array( $anon, 'user', 'assertuserfailed' ), - array( $user, 'user', false ), - array( $user, 'bot', 'assertbotfailed' ), - array( $bot, 'user', false ), - array( $bot, 'bot', false ), + array( false, array(), 'user', 'assertuserfailed' ), + array( true, array(), 'user', false ), + array( true, array(), 'bot', 'assertbotfailed' ), + array( true, array( 'bot' ), 'user', false ), + array( true, array( 'bot' ), 'bot', false ), ); } @@ -53,11 +36,17 @@ class ApiMainTest extends ApiTestCase { * * @covers ApiMain::checkAsserts * @dataProvider provideAssert - * @param User $user + * @param bool $registered + * @param array $rights * @param string $assert * @param string|bool $error False if no error expected */ - public function testAssert( $user, $assert, $error ) { + public function testAssert( $registered, $rights, $assert, $error ) { + $user = new User(); + if ( $registered ) { + $user->setId( 1 ); + } + $user->mRights = $rights; try { $this->doApiRequest( array( 'action' => 'query', @@ -69,4 +58,25 @@ class ApiMainTest extends ApiTestCase { } } + /** + * Test if all classes in the main module manager exists + */ + public function testClassNamesInModuleManager() { + global $wgAutoloadLocalClasses, $wgAutoloadClasses; + + // wgAutoloadLocalClasses has precedence, just like in includes/AutoLoader.php + $classes = $wgAutoloadLocalClasses + $wgAutoloadClasses; + + $api = new ApiMain( + new FauxRequest( array( 'action' => 'query', 'meta' => 'siteinfo' ) ) + ); + $modules = $api->getModuleManager()->getNamesWithClasses(); + foreach( $modules as $name => $class ) { + $this->assertArrayHasKey( + $class, + $classes, + 'Class ' . $class . ' for api module ' . $name . ' not in autoloader (with exact case)' + ); + } + } } diff --git a/tests/phpunit/includes/api/ApiMessageTest.php b/tests/phpunit/includes/api/ApiMessageTest.php new file mode 100644 index 00000000..6c3ce60d --- /dev/null +++ b/tests/phpunit/includes/api/ApiMessageTest.php @@ -0,0 +1,103 @@ +<?php + +/** + * @group API + */ +class ApiMessageTest extends MediaWikiTestCase { + + private function compareMessages( $msg, $msg2 ) { + $this->assertSame( $msg->getKey(), $msg2->getKey(), 'getKey' ); + $this->assertSame( $msg->getKeysToTry(), $msg2->getKeysToTry(), 'getKeysToTry' ); + $this->assertSame( $msg->getParams(), $msg2->getParams(), 'getParams' ); + $this->assertSame( $msg->getFormat(), $msg2->getFormat(), 'getFormat' ); + $this->assertSame( $msg->getLanguage(), $msg2->getLanguage(), 'getLanguage' ); + + $msg = TestingAccessWrapper::newFromObject( $msg ); + $msg2 = TestingAccessWrapper::newFromObject( $msg2 ); + foreach ( array( 'interface', 'useDatabase', 'title' ) as $key ) { + $this->assertSame( $msg->$key, $msg2->$key, $key ); + } + } + + /** + * @covers ApiMessage + */ + public function testApiMessage() { + $msg = new Message( array( 'foo', 'bar' ), array( 'baz' ) ); + $msg->inLanguage( 'de' )->title( Title::newMainPage() ); + $msg2 = new ApiMessage( $msg, 'code', array( 'data' ) ); + $this->compareMessages( $msg, $msg2 ); + $this->assertEquals( 'code', $msg2->getApiCode() ); + $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + + $msg = new Message( array( 'foo', 'bar' ), array( 'baz' ) ); + $msg2 = new ApiMessage( array( array( 'foo', 'bar' ), 'baz' ), 'code', array( 'data' ) ); + $this->compareMessages( $msg, $msg2 ); + $this->assertEquals( 'code', $msg2->getApiCode() ); + $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + + $msg = new Message( 'foo' ); + $msg2 = new ApiMessage( 'foo' ); + $this->compareMessages( $msg, $msg2 ); + $this->assertEquals( 'foo', $msg2->getApiCode() ); + $this->assertEquals( array(), $msg2->getApiData() ); + + $msg2->setApiCode( 'code', array( 'data' ) ); + $this->assertEquals( 'code', $msg2->getApiCode() ); + $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + $msg2->setApiCode( null ); + $this->assertEquals( 'foo', $msg2->getApiCode() ); + $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + $msg2->setApiData( array( 'data2' ) ); + $this->assertEquals( array( 'data2' ), $msg2->getApiData() ); + } + + /** + * @covers ApiRawMessage + */ + public function testApiRawMessage() { + $msg = new RawMessage( 'foo', array( 'baz' ) ); + $msg->inLanguage( 'de' )->title( Title::newMainPage() ); + $msg2 = new ApiRawMessage( $msg, 'code', array( 'data' ) ); + $this->compareMessages( $msg, $msg2 ); + $this->assertEquals( 'code', $msg2->getApiCode() ); + $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + + $msg = new RawMessage( 'foo', array( 'baz' ) ); + $msg2 = new ApiRawMessage( array( 'foo', 'baz' ), 'code', array( 'data' ) ); + $this->compareMessages( $msg, $msg2 ); + $this->assertEquals( 'code', $msg2->getApiCode() ); + $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + + $msg = new RawMessage( 'foo' ); + $msg2 = new ApiRawMessage( 'foo', 'code', array( 'data' ) ); + $this->compareMessages( $msg, $msg2 ); + $this->assertEquals( 'code', $msg2->getApiCode() ); + $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + + $msg2->setApiCode( 'code', array( 'data' ) ); + $this->assertEquals( 'code', $msg2->getApiCode() ); + $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + $msg2->setApiCode( null ); + $this->assertEquals( 'foo', $msg2->getApiCode() ); + $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + $msg2->setApiData( array( 'data2' ) ); + $this->assertEquals( array( 'data2' ), $msg2->getApiData() ); + } + + /** + * @covers ApiMessage::create + */ + public function testApiMessageCreate() { + $this->assertInstanceOf( 'ApiMessage', ApiMessage::create( new Message( 'mainpage' ) ) ); + $this->assertInstanceOf( 'ApiRawMessage', ApiMessage::create( new RawMessage( 'mainpage' ) ) ); + $this->assertInstanceOf( 'ApiMessage', ApiMessage::create( 'mainpage' ) ); + + $msg = new ApiMessage( 'mainpage' ); + $this->assertSame( $msg, ApiMessage::create( $msg ) ); + + $msg = new ApiRawMessage( 'mainpage' ); + $this->assertSame( $msg, ApiMessage::create( $msg ) ); + } + +} diff --git a/tests/phpunit/includes/api/ApiOptionsTest.php b/tests/phpunit/includes/api/ApiOptionsTest.php index 5f955bbc..51154ae3 100644 --- a/tests/phpunit/includes/api/ApiOptionsTest.php +++ b/tests/phpunit/includes/api/ApiOptionsTest.php @@ -17,8 +17,6 @@ class ApiOptionsTest extends MediaWikiLangTestCase { /** @var DerivativeContext */ private $mContext; - private $mOldGetPreferencesHooks; - private static $Success = array( 'options' => 'success' ); protected function setUp() { @@ -50,21 +48,11 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $this->mTested = new ApiOptions( $main, 'options' ); - global $wgHooks; - if ( !isset( $wgHooks['GetPreferences'] ) ) { - $wgHooks['GetPreferences'] = array(); - } - $this->mOldGetPreferencesHooks = $wgHooks['GetPreferences']; - $wgHooks['GetPreferences'][] = array( $this, 'hookGetPreferences' ); - } - - protected function tearDown() { - global $wgHooks; - - $wgHooks['GetPreferences'] = $this->mOldGetPreferencesHooks; - $this->mOldGetPreferencesHooks = false; - - parent::tearDown(); + $this->mergeMwGlobalArrayValue( 'wgHooks', array( + 'GetPreferences' => array( + array( $this, 'hookGetPreferences' ) + ) + ) ); } public function hookGetPreferences( $user, &$preferences ) { @@ -150,7 +138,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $this->mContext->setRequest( new FauxRequest( $request, true, $this->mSession ) ); $this->mTested->execute(); - return $this->mTested->getResult()->getData(); + return $this->mTested->getResult()->getResultData( null, array( 'Strip' => 'all' ) ); } /** @@ -408,7 +396,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase { 'options' => 'success', 'warnings' => array( 'options' => array( - '*' => "Validation error for 'special': cannot be set by this module" + 'warnings' => "Validation error for 'special': cannot be set by this module" ) ) ), $response ); @@ -431,7 +419,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase { 'options' => 'success', 'warnings' => array( 'options' => array( - '*' => "Validation error for 'unknownOption': not a valid preference" + 'warnings' => "Validation error for 'unknownOption': not a valid preference" ) ) ), $response ); diff --git a/tests/phpunit/includes/api/ApiResultTest.php b/tests/phpunit/includes/api/ApiResultTest.php new file mode 100644 index 00000000..f0d84552 --- /dev/null +++ b/tests/phpunit/includes/api/ApiResultTest.php @@ -0,0 +1,1563 @@ +<?php + +/** + * @covers ApiResult + * @group API + */ +class ApiResultTest extends MediaWikiTestCase { + + /** + * @covers ApiResult + */ + public function testStaticDataMethods() { + $arr = array(); + + ApiResult::setValue( $arr, 'setValue', '1' ); + + ApiResult::setValue( $arr, null, 'unnamed 1' ); + ApiResult::setValue( $arr, null, 'unnamed 2' ); + + ApiResult::setValue( $arr, 'deleteValue', '2' ); + ApiResult::unsetValue( $arr, 'deleteValue' ); + + ApiResult::setContentValue( $arr, 'setContentValue', '3' ); + + $this->assertSame( array( + 'setValue' => '1', + 'unnamed 1', + 'unnamed 2', + ApiResult::META_CONTENT => 'setContentValue', + 'setContentValue' => '3', + ), $arr ); + + try { + ApiResult::setValue( $arr, 'setValue', '99' ); + $this->fail( 'Expected exception not thrown' ); + } catch ( RuntimeException $ex ) { + $this->assertSame( + 'Attempting to add element setValue=99, existing value is 1', + $ex->getMessage(), + 'Expected exception' + ); + } + + try { + ApiResult::setContentValue( $arr, 'setContentValue2', '99' ); + $this->fail( 'Expected exception not thrown' ); + } catch ( RuntimeException $ex ) { + $this->assertSame( + 'Attempting to set content element as setContentValue2 when setContentValue ' . + 'is already set as the content element', + $ex->getMessage(), + 'Expected exception' + ); + } + + ApiResult::setValue( $arr, 'setValue', '99', ApiResult::OVERRIDE ); + $this->assertSame( '99', $arr['setValue'] ); + + ApiResult::setContentValue( $arr, 'setContentValue2', '99', ApiResult::OVERRIDE ); + $this->assertSame( 'setContentValue2', $arr[ApiResult::META_CONTENT] ); + + $arr = array( 'foo' => 1, 'bar' => 1 ); + ApiResult::setValue( $arr, 'top', '2', ApiResult::ADD_ON_TOP ); + ApiResult::setValue( $arr, null, '2', ApiResult::ADD_ON_TOP ); + ApiResult::setValue( $arr, 'bottom', '2' ); + ApiResult::setValue( $arr, 'foo', '2', ApiResult::OVERRIDE ); + ApiResult::setValue( $arr, 'bar', '2', ApiResult::OVERRIDE | ApiResult::ADD_ON_TOP ); + $this->assertSame( array( 0, 'top', 'foo', 'bar', 'bottom' ), array_keys( $arr ) ); + + $arr = array(); + ApiResult::setValue( $arr, 'sub', array( 'foo' => 1 ) ); + ApiResult::setValue( $arr, 'sub', array( 'bar' => 1 ) ); + $this->assertSame( array( 'sub' => array( 'foo' => 1, 'bar' => 1 ) ), $arr ); + + try { + ApiResult::setValue( $arr, 'sub', array( 'foo' => 2, 'baz' => 2 ) ); + $this->fail( 'Expected exception not thrown' ); + } catch ( RuntimeException $ex ) { + $this->assertSame( + 'Conflicting keys (foo) when attempting to merge element sub', + $ex->getMessage(), + 'Expected exception' + ); + } + + $arr = array(); + $title = Title::newFromText( "MediaWiki:Foobar" ); + $obj = new stdClass; + $obj->foo = 1; + $obj->bar = 2; + ApiResult::setValue( $arr, 'title', $title ); + ApiResult::setValue( $arr, 'obj', $obj ); + $this->assertSame( array( + 'title' => (string)$title, + 'obj' => array( 'foo' => 1, 'bar' => 2, ApiResult::META_TYPE => 'assoc' ), + ), $arr ); + + $fh = tmpfile(); + try { + ApiResult::setValue( $arr, 'file', $fh ); + $this->fail( 'Expected exception not thrown' ); + } catch ( InvalidArgumentException $ex ) { + $this->assertSame( + 'Cannot add resource(stream) to ApiResult', + $ex->getMessage(), + 'Expected exception' + ); + } + try { + ApiResult::setValue( $arr, null, $fh ); + $this->fail( 'Expected exception not thrown' ); + } catch ( InvalidArgumentException $ex ) { + $this->assertSame( + 'Cannot add resource(stream) to ApiResult', + $ex->getMessage(), + 'Expected exception' + ); + } + try { + $obj->file = $fh; + ApiResult::setValue( $arr, 'sub', $obj ); + $this->fail( 'Expected exception not thrown' ); + } catch ( InvalidArgumentException $ex ) { + $this->assertSame( + 'Cannot add resource(stream) to ApiResult', + $ex->getMessage(), + 'Expected exception' + ); + } + try { + $obj->file = $fh; + ApiResult::setValue( $arr, null, $obj ); + $this->fail( 'Expected exception not thrown' ); + } catch ( InvalidArgumentException $ex ) { + $this->assertSame( + 'Cannot add resource(stream) to ApiResult', + $ex->getMessage(), + 'Expected exception' + ); + } + fclose( $fh ); + + try { + ApiResult::setValue( $arr, 'inf', INF ); + $this->fail( 'Expected exception not thrown' ); + } catch ( InvalidArgumentException $ex ) { + $this->assertSame( + 'Cannot add non-finite floats to ApiResult', + $ex->getMessage(), + 'Expected exception' + ); + } + try { + ApiResult::setValue( $arr, null, INF ); + $this->fail( 'Expected exception not thrown' ); + } catch ( InvalidArgumentException $ex ) { + $this->assertSame( + 'Cannot add non-finite floats to ApiResult', + $ex->getMessage(), + 'Expected exception' + ); + } + try { + ApiResult::setValue( $arr, 'nan', NAN ); + $this->fail( 'Expected exception not thrown' ); + } catch ( InvalidArgumentException $ex ) { + $this->assertSame( + 'Cannot add non-finite floats to ApiResult', + $ex->getMessage(), + 'Expected exception' + ); + } + try { + ApiResult::setValue( $arr, null, NAN ); + $this->fail( 'Expected exception not thrown' ); + } catch ( InvalidArgumentException $ex ) { + $this->assertSame( + 'Cannot add non-finite floats to ApiResult', + $ex->getMessage(), + 'Expected exception' + ); + } + + $arr = array(); + $result2 = new ApiResult( 8388608 ); + $result2->addValue( null, 'foo', 'bar' ); + ApiResult::setValue( $arr, 'baz', $result2 ); + $this->assertSame( array( + 'baz' => array( + ApiResult::META_TYPE => 'assoc', + 'foo' => 'bar', + ) + ), $arr ); + + $arr = array(); + ApiResult::setValue( $arr, 'foo', "foo\x80bar" ); + ApiResult::setValue( $arr, 'bar', "a\xcc\x81" ); + ApiResult::setValue( $arr, 'baz', 74 ); + ApiResult::setValue( $arr, null, "foo\x80bar" ); + ApiResult::setValue( $arr, null, "a\xcc\x81" ); + $this->assertSame( array( + 'foo' => "foo\xef\xbf\xbdbar", + 'bar' => "\xc3\xa1", + 'baz' => 74, + 0 => "foo\xef\xbf\xbdbar", + 1 => "\xc3\xa1", + ), $arr ); + } + + /** + * @covers ApiResult + */ + public function testInstanceDataMethods() { + $result = new ApiResult( 8388608 ); + + $result->addValue( null, 'setValue', '1' ); + + $result->addValue( null, null, 'unnamed 1' ); + $result->addValue( null, null, 'unnamed 2' ); + + $result->addValue( null, 'deleteValue', '2' ); + $result->removeValue( null, 'deleteValue' ); + + $result->addValue( array( 'a', 'b' ), 'deleteValue', '3' ); + $result->removeValue( array( 'a', 'b', 'deleteValue' ), null, '3' ); + + $result->addContentValue( null, 'setContentValue', '3' ); + + $this->assertSame( array( + 'setValue' => '1', + 'unnamed 1', + 'unnamed 2', + 'a' => array( 'b' => array() ), + 'setContentValue' => '3', + ApiResult::META_TYPE => 'assoc', + ApiResult::META_CONTENT => 'setContentValue', + ), $result->getResultData() ); + $this->assertSame( 20, $result->getSize() ); + + try { + $result->addValue( null, 'setValue', '99' ); + $this->fail( 'Expected exception not thrown' ); + } catch ( RuntimeException $ex ) { + $this->assertSame( + 'Attempting to add element setValue=99, existing value is 1', + $ex->getMessage(), + 'Expected exception' + ); + } + + try { + $result->addContentValue( null, 'setContentValue2', '99' ); + $this->fail( 'Expected exception not thrown' ); + } catch ( RuntimeException $ex ) { + $this->assertSame( + 'Attempting to set content element as setContentValue2 when setContentValue ' . + 'is already set as the content element', + $ex->getMessage(), + 'Expected exception' + ); + } + + $result->addValue( null, 'setValue', '99', ApiResult::OVERRIDE ); + $this->assertSame( '99', $result->getResultData( array( 'setValue' ) ) ); + + $result->addContentValue( null, 'setContentValue2', '99', ApiResult::OVERRIDE ); + $this->assertSame( 'setContentValue2', + $result->getResultData( array( ApiResult::META_CONTENT ) ) ); + + $result->reset(); + $this->assertSame( array( + ApiResult::META_TYPE => 'assoc', + ), $result->getResultData() ); + $this->assertSame( 0, $result->getSize() ); + + $result->addValue( null, 'foo', 1 ); + $result->addValue( null, 'bar', 1 ); + $result->addValue( null, 'top', '2', ApiResult::ADD_ON_TOP ); + $result->addValue( null, null, '2', ApiResult::ADD_ON_TOP ); + $result->addValue( null, 'bottom', '2' ); + $result->addValue( null, 'foo', '2', ApiResult::OVERRIDE ); + $result->addValue( null, 'bar', '2', ApiResult::OVERRIDE | ApiResult::ADD_ON_TOP ); + $this->assertSame( array( 0, 'top', 'foo', 'bar', 'bottom', ApiResult::META_TYPE ), + array_keys( $result->getResultData() ) ); + + $result->reset(); + $result->addValue( null, 'foo', array( 'bar' => 1 ) ); + $result->addValue( array( 'foo', 'top' ), 'x', 2, ApiResult::ADD_ON_TOP ); + $result->addValue( array( 'foo', 'bottom' ), 'x', 2 ); + $this->assertSame( array( 'top', 'bar', 'bottom' ), + array_keys( $result->getResultData( array( 'foo' ) ) ) ); + + $result->reset(); + $result->addValue( null, 'sub', array( 'foo' => 1 ) ); + $result->addValue( null, 'sub', array( 'bar' => 1 ) ); + $this->assertSame( array( + 'sub' => array( 'foo' => 1, 'bar' => 1 ), + ApiResult::META_TYPE => 'assoc', + ), $result->getResultData() ); + + try { + $result->addValue( null, 'sub', array( 'foo' => 2, 'baz' => 2 ) ); + $this->fail( 'Expected exception not thrown' ); + } catch ( RuntimeException $ex ) { + $this->assertSame( + 'Conflicting keys (foo) when attempting to merge element sub', + $ex->getMessage(), + 'Expected exception' + ); + } + + $result->reset(); + $title = Title::newFromText( "MediaWiki:Foobar" ); + $obj = new stdClass; + $obj->foo = 1; + $obj->bar = 2; + $result->addValue( null, 'title', $title ); + $result->addValue( null, 'obj', $obj ); + $this->assertSame( array( + 'title' => (string)$title, + 'obj' => array( 'foo' => 1, 'bar' => 2, ApiResult::META_TYPE => 'assoc' ), + ApiResult::META_TYPE => 'assoc', + ), $result->getResultData() ); + + $fh = tmpfile(); + try { + $result->addValue( null, 'file', $fh ); + $this->fail( 'Expected exception not thrown' ); + } catch ( InvalidArgumentException $ex ) { + $this->assertSame( + 'Cannot add resource(stream) to ApiResult', + $ex->getMessage(), + 'Expected exception' + ); + } + try { + $result->addValue( null, null, $fh ); + $this->fail( 'Expected exception not thrown' ); + } catch ( InvalidArgumentException $ex ) { + $this->assertSame( + 'Cannot add resource(stream) to ApiResult', + $ex->getMessage(), + 'Expected exception' + ); + } + try { + $obj->file = $fh; + $result->addValue( null, 'sub', $obj ); + $this->fail( 'Expected exception not thrown' ); + } catch ( InvalidArgumentException $ex ) { + $this->assertSame( + 'Cannot add resource(stream) to ApiResult', + $ex->getMessage(), + 'Expected exception' + ); + } + try { + $obj->file = $fh; + $result->addValue( null, null, $obj ); + $this->fail( 'Expected exception not thrown' ); + } catch ( InvalidArgumentException $ex ) { + $this->assertSame( + 'Cannot add resource(stream) to ApiResult', + $ex->getMessage(), + 'Expected exception' + ); + } + fclose( $fh ); + + try { + $result->addValue( null, 'inf', INF ); + $this->fail( 'Expected exception not thrown' ); + } catch ( InvalidArgumentException $ex ) { + $this->assertSame( + 'Cannot add non-finite floats to ApiResult', + $ex->getMessage(), + 'Expected exception' + ); + } + try { + $result->addValue( null, null, INF ); + $this->fail( 'Expected exception not thrown' ); + } catch ( InvalidArgumentException $ex ) { + $this->assertSame( + 'Cannot add non-finite floats to ApiResult', + $ex->getMessage(), + 'Expected exception' + ); + } + try { + $result->addValue( null, 'nan', NAN ); + $this->fail( 'Expected exception not thrown' ); + } catch ( InvalidArgumentException $ex ) { + $this->assertSame( + 'Cannot add non-finite floats to ApiResult', + $ex->getMessage(), + 'Expected exception' + ); + } + try { + $result->addValue( null, null, NAN ); + $this->fail( 'Expected exception not thrown' ); + } catch ( InvalidArgumentException $ex ) { + $this->assertSame( + 'Cannot add non-finite floats to ApiResult', + $ex->getMessage(), + 'Expected exception' + ); + } + + $result->reset(); + $result->addParsedLimit( 'foo', 12 ); + $this->assertSame( array( + 'limits' => array( 'foo' => 12 ), + ApiResult::META_TYPE => 'assoc', + ), $result->getResultData() ); + $result->addParsedLimit( 'foo', 13 ); + $this->assertSame( array( + 'limits' => array( 'foo' => 13 ), + ApiResult::META_TYPE => 'assoc', + ), $result->getResultData() ); + $this->assertSame( null, $result->getResultData( array( 'foo', 'bar', 'baz' ) ) ); + $this->assertSame( 13, $result->getResultData( array( 'limits', 'foo' ) ) ); + try { + $result->getResultData( array( 'limits', 'foo', 'bar' ) ); + $this->fail( 'Expected exception not thrown' ); + } catch ( InvalidArgumentException $ex ) { + $this->assertSame( + 'Path limits.foo is not an array', + $ex->getMessage(), + 'Expected exception' + ); + } + + $result = new ApiResult( 10 ); + $formatter = new ApiErrorFormatter( $result, Language::factory( 'en' ), 'none', false ); + $result->setErrorFormatter( $formatter ); + $this->assertFalse( $result->addValue( null, 'foo', '12345678901' ) ); + $this->assertTrue( $result->addValue( null, 'foo', '12345678901', ApiResult::NO_SIZE_CHECK ) ); + $this->assertSame( 0, $result->getSize() ); + $result->reset(); + $this->assertTrue( $result->addValue( null, 'foo', '1234567890' ) ); + $this->assertFalse( $result->addValue( null, 'foo', '1' ) ); + $result->removeValue( null, 'foo' ); + $this->assertTrue( $result->addValue( null, 'foo', '1' ) ); + + $result = new ApiResult( 8388608 ); + $result2 = new ApiResult( 8388608 ); + $result2->addValue( null, 'foo', 'bar' ); + $result->addValue( null, 'baz', $result2 ); + $this->assertSame( array( + 'baz' => array( + 'foo' => 'bar', + ApiResult::META_TYPE => 'assoc', + ), + ApiResult::META_TYPE => 'assoc', + ), $result->getResultData() ); + + $result = new ApiResult( 8388608 ); + $result->addValue( null, 'foo', "foo\x80bar" ); + $result->addValue( null, 'bar', "a\xcc\x81" ); + $result->addValue( null, 'baz', 74 ); + $result->addValue( null, null, "foo\x80bar" ); + $result->addValue( null, null, "a\xcc\x81" ); + $this->assertSame( array( + 'foo' => "foo\xef\xbf\xbdbar", + 'bar' => "\xc3\xa1", + 'baz' => 74, + 0 => "foo\xef\xbf\xbdbar", + 1 => "\xc3\xa1", + ApiResult::META_TYPE => 'assoc', + ), $result->getResultData() ); + } + + /** + * @covers ApiResult + */ + public function testMetadata() { + $arr = array( 'foo' => array( 'bar' => array() ) ); + $result = new ApiResult( 8388608 ); + $result->addValue( null, 'foo', array( 'bar' => array() ) ); + + $expect = array( + 'foo' => array( + 'bar' => array( + ApiResult::META_INDEXED_TAG_NAME => 'ritn', + ApiResult::META_TYPE => 'default', + ), + ApiResult::META_INDEXED_TAG_NAME => 'ritn', + ApiResult::META_TYPE => 'default', + ), + ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ), + ApiResult::META_INDEXED_TAG_NAME => 'itn', + ApiResult::META_PRESERVE_KEYS => array( 'foo', 'bar' ), + ApiResult::META_TYPE => 'array', + ); + + ApiResult::setSubelementsList( $arr, 'foo' ); + ApiResult::setSubelementsList( $arr, array( 'bar', 'baz' ) ); + ApiResult::unsetSubelementsList( $arr, 'baz' ); + ApiResult::setIndexedTagNameRecursive( $arr, 'ritn' ); + ApiResult::setIndexedTagName( $arr, 'itn' ); + ApiResult::setPreserveKeysList( $arr, 'foo' ); + ApiResult::setPreserveKeysList( $arr, array( 'bar', 'baz' ) ); + ApiResult::unsetPreserveKeysList( $arr, 'baz' ); + ApiResult::setArrayTypeRecursive( $arr, 'default' ); + ApiResult::setArrayType( $arr, 'array' ); + $this->assertSame( $expect, $arr ); + + $result->addSubelementsList( null, 'foo' ); + $result->addSubelementsList( null, array( 'bar', 'baz' ) ); + $result->removeSubelementsList( null, 'baz' ); + $result->addIndexedTagNameRecursive( null, 'ritn' ); + $result->addIndexedTagName( null, 'itn' ); + $result->addPreserveKeysList( null, 'foo' ); + $result->addPreserveKeysList( null, array( 'bar', 'baz' ) ); + $result->removePreserveKeysList( null, 'baz' ); + $result->addArrayTypeRecursive( null, 'default' ); + $result->addArrayType( null, 'array' ); + $this->assertEquals( $expect, $result->getResultData() ); + + $arr = array( 'foo' => array( 'bar' => array() ) ); + $expect = array( + 'foo' => array( + 'bar' => array( + ApiResult::META_TYPE => 'kvp', + ApiResult::META_KVP_KEY_NAME => 'key', + ), + ApiResult::META_TYPE => 'kvp', + ApiResult::META_KVP_KEY_NAME => 'key', + ), + ApiResult::META_TYPE => 'BCkvp', + ApiResult::META_KVP_KEY_NAME => 'bc', + ); + ApiResult::setArrayTypeRecursive( $arr, 'kvp', 'key' ); + ApiResult::setArrayType( $arr, 'BCkvp', 'bc' ); + $this->assertSame( $expect, $arr ); + } + + /** + * @covers ApiResult + */ + public function testUtilityFunctions() { + $arr = array( + 'foo' => array( + 'bar' => array( '_dummy' => 'foobaz' ), + 'bar2' => (object)array( '_dummy' => 'foobaz' ), + 'x' => 'ok', + '_dummy' => 'foobaz', + ), + 'foo2' => (object)array( + 'bar' => array( '_dummy' => 'foobaz' ), + 'bar2' => (object)array( '_dummy' => 'foobaz' ), + 'x' => 'ok', + '_dummy' => 'foobaz', + ), + ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ), + ApiResult::META_INDEXED_TAG_NAME => 'itn', + ApiResult::META_PRESERVE_KEYS => array( 'foo', 'bar', '_dummy2', 0 ), + ApiResult::META_TYPE => 'array', + '_dummy' => 'foobaz', + '_dummy2' => 'foobaz!', + ); + $this->assertEquals( array( + 'foo' => array( + 'bar' => array(), + 'bar2' => (object)array(), + 'x' => 'ok', + ), + 'foo2' => (object)array( + 'bar' => array(), + 'bar2' => (object)array(), + 'x' => 'ok', + ), + '_dummy2' => 'foobaz!', + ), ApiResult::stripMetadata( $arr ), 'ApiResult::stripMetadata' ); + + $metadata = array(); + $data = ApiResult::stripMetadataNonRecursive( $arr, $metadata ); + $this->assertEquals( array( + 'foo' => array( + 'bar' => array( '_dummy' => 'foobaz' ), + 'bar2' => (object)array( '_dummy' => 'foobaz' ), + 'x' => 'ok', + '_dummy' => 'foobaz', + ), + 'foo2' => (object)array( + 'bar' => array( '_dummy' => 'foobaz' ), + 'bar2' => (object)array( '_dummy' => 'foobaz' ), + 'x' => 'ok', + '_dummy' => 'foobaz', + ), + '_dummy2' => 'foobaz!', + ), $data, 'ApiResult::stripMetadataNonRecursive ($data)' ); + $this->assertEquals( array( + ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ), + ApiResult::META_INDEXED_TAG_NAME => 'itn', + ApiResult::META_PRESERVE_KEYS => array( 'foo', 'bar', '_dummy2', 0 ), + ApiResult::META_TYPE => 'array', + '_dummy' => 'foobaz', + ), $metadata, 'ApiResult::stripMetadataNonRecursive ($metadata)' ); + + $metadata = null; + $data = ApiResult::stripMetadataNonRecursive( (object)$arr, $metadata ); + $this->assertEquals( (object)array( + 'foo' => array( + 'bar' => array( '_dummy' => 'foobaz' ), + 'bar2' => (object)array( '_dummy' => 'foobaz' ), + 'x' => 'ok', + '_dummy' => 'foobaz', + ), + 'foo2' => (object)array( + 'bar' => array( '_dummy' => 'foobaz' ), + 'bar2' => (object)array( '_dummy' => 'foobaz' ), + 'x' => 'ok', + '_dummy' => 'foobaz', + ), + '_dummy2' => 'foobaz!', + ), $data, 'ApiResult::stripMetadataNonRecursive on object ($data)' ); + $this->assertEquals( array( + ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ), + ApiResult::META_INDEXED_TAG_NAME => 'itn', + ApiResult::META_PRESERVE_KEYS => array( 'foo', 'bar', '_dummy2', 0 ), + ApiResult::META_TYPE => 'array', + '_dummy' => 'foobaz', + ), $metadata, 'ApiResult::stripMetadataNonRecursive on object ($metadata)' ); + } + + /** + * @covers ApiResult + * @dataProvider provideTransformations + * @param string $label + * @param array $input + * @param array $transforms + * @param array|Exception $expect + */ + public function testTransformations( $label, $input, $transforms, $expect ) { + $result = new ApiResult( false ); + $result->addValue( null, 'test', $input ); + + if ( $expect instanceof Exception ) { + try { + $output = $result->getResultData( 'test', $transforms ); + $this->fail( 'Expected exception not thrown', $label ); + } catch ( Exception $ex ) { + $this->assertEquals( $ex, $expect, $label ); + } + } else { + $output = $result->getResultData( 'test', $transforms ); + $this->assertEquals( $expect, $output, $label ); + } + } + + public function provideTransformations() { + $kvp = function ( $keyKey, $key, $valKey, $value ) { + return array( + $keyKey => $key, + $valKey => $value, + ApiResult::META_PRESERVE_KEYS => array( $keyKey ), + ApiResult::META_CONTENT => $valKey, + ApiResult::META_TYPE => 'assoc', + ); + }; + $typeArr = array( + 'defaultArray' => array( 2 => 'a', 0 => 'b', 1 => 'c' ), + 'defaultAssoc' => array( 'x' => 'a', 1 => 'b', 0 => 'c' ), + 'defaultAssoc2' => array( 2 => 'a', 3 => 'b', 0 => 'c' ), + 'array' => array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'array' ), + 'BCarray' => array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'BCarray' ), + 'BCassoc' => array( 'a', 'b', 'c', ApiResult::META_TYPE => 'BCassoc' ), + 'assoc' => array( 2 => 'a', 0 => 'b', 1 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'kvp' => array( 'x' => 'a', 'y' => 'b', 'z' => array( 'c' ), ApiResult::META_TYPE => 'kvp' ), + 'BCkvp' => array( 'x' => 'a', 'y' => 'b', + ApiResult::META_TYPE => 'BCkvp', + ApiResult::META_KVP_KEY_NAME => 'key', + ), + 'emptyDefault' => array( '_dummy' => 1 ), + 'emptyAssoc' => array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), + '_dummy' => 1, + ApiResult::META_PRESERVE_KEYS => array( '_dummy' ), + ); + $stripArr = array( + 'foo' => array( + 'bar' => array( '_dummy' => 'foobaz' ), + 'baz' => array( + ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ), + ApiResult::META_INDEXED_TAG_NAME => 'itn', + ApiResult::META_PRESERVE_KEYS => array( 'foo', 'bar', '_dummy2', 0 ), + ApiResult::META_TYPE => 'array', + ), + 'x' => 'ok', + '_dummy' => 'foobaz', + ), + ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ), + ApiResult::META_INDEXED_TAG_NAME => 'itn', + ApiResult::META_PRESERVE_KEYS => array( 'foo', 'bar', '_dummy2', 0 ), + ApiResult::META_TYPE => 'array', + '_dummy' => 'foobaz', + '_dummy2' => 'foobaz!', + ); + + return array( + array( + 'BC: META_BC_BOOLS', + array( + 'BCtrue' => true, + 'BCfalse' => false, + 'true' => true, + 'false' => false, + ApiResult::META_BC_BOOLS => array( 0, 'true', 'false' ), + ), + array( 'BC' => array() ), + array( + 'BCtrue' => '', + 'true' => true, + 'false' => false, + ApiResult::META_BC_BOOLS => array( 0, 'true', 'false' ), + ) + ), + array( + 'BC: META_BC_SUBELEMENTS', + array( + 'bc' => 'foo', + 'nobc' => 'bar', + ApiResult::META_BC_SUBELEMENTS => array( 'bc' ), + ), + array( 'BC' => array() ), + array( + 'bc' => array( + '*' => 'foo', + ApiResult::META_CONTENT => '*', + ApiResult::META_TYPE => 'assoc', + ), + 'nobc' => 'bar', + ApiResult::META_BC_SUBELEMENTS => array( 'bc' ), + ), + ), + array( + 'BC: META_CONTENT', + array( + 'content' => '!!!', + ApiResult::META_CONTENT => 'content', + ), + array( 'BC' => array() ), + array( + '*' => '!!!', + ApiResult::META_CONTENT => '*', + ), + ), + array( + 'BC: BCkvp type', + array( + 'foo' => 'foo value', + 'bar' => 'bar value', + '_baz' => 'baz value', + ApiResult::META_TYPE => 'BCkvp', + ApiResult::META_KVP_KEY_NAME => 'key', + ApiResult::META_PRESERVE_KEYS => array( '_baz' ), + ), + array( 'BC' => array() ), + array( + $kvp( 'key', 'foo', '*', 'foo value' ), + $kvp( 'key', 'bar', '*', 'bar value' ), + $kvp( 'key', '_baz', '*', 'baz value' ), + ApiResult::META_TYPE => 'array', + ApiResult::META_KVP_KEY_NAME => 'key', + ApiResult::META_PRESERVE_KEYS => array( '_baz' ), + ), + ), + array( + 'BC: BCarray type', + array( + ApiResult::META_TYPE => 'BCarray', + ), + array( 'BC' => array() ), + array( + ApiResult::META_TYPE => 'default', + ), + ), + array( + 'BC: BCassoc type', + array( + ApiResult::META_TYPE => 'BCassoc', + ), + array( 'BC' => array() ), + array( + ApiResult::META_TYPE => 'default', + ), + ), + array( + 'BC: BCkvp exception', + array( + ApiResult::META_TYPE => 'BCkvp', + ), + array( 'BC' => array() ), + new UnexpectedValueException( + 'Type "BCkvp" used without setting ApiResult::META_KVP_KEY_NAME metadata item' + ), + ), + array( + 'BC: nobool, no*, nosub', + array( + 'true' => true, + 'false' => false, + 'content' => 'content', + ApiResult::META_CONTENT => 'content', + 'bc' => 'foo', + ApiResult::META_BC_SUBELEMENTS => array( 'bc' ), + 'BCarray' => array( ApiResult::META_TYPE => 'BCarray' ), + 'BCassoc' => array( ApiResult::META_TYPE => 'BCassoc' ), + 'BCkvp' => array( + 'foo' => 'foo value', + 'bar' => 'bar value', + '_baz' => 'baz value', + ApiResult::META_TYPE => 'BCkvp', + ApiResult::META_KVP_KEY_NAME => 'key', + ApiResult::META_PRESERVE_KEYS => array( '_baz' ), + ), + ), + array( 'BC' => array( 'nobool', 'no*', 'nosub' ) ), + array( + 'true' => true, + 'false' => false, + 'content' => 'content', + 'bc' => 'foo', + 'BCarray' => array( ApiResult::META_TYPE => 'default' ), + 'BCassoc' => array( ApiResult::META_TYPE => 'default' ), + 'BCkvp' => array( + $kvp( 'key', 'foo', '*', 'foo value' ), + $kvp( 'key', 'bar', '*', 'bar value' ), + $kvp( 'key', '_baz', '*', 'baz value' ), + ApiResult::META_TYPE => 'array', + ApiResult::META_KVP_KEY_NAME => 'key', + ApiResult::META_PRESERVE_KEYS => array( '_baz' ), + ), + ApiResult::META_CONTENT => 'content', + ApiResult::META_BC_SUBELEMENTS => array( 'bc' ), + ), + ), + + array( + 'Types: Normal transform', + $typeArr, + array( 'Types' => array() ), + array( + 'defaultArray' => array( 'b', 'c', 'a', ApiResult::META_TYPE => 'array' ), + 'defaultAssoc' => array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'defaultAssoc2' => array( 2 => 'a', 3 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'array' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), + 'BCarray' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), + 'BCassoc' => array( 'a', 'b', 'c', ApiResult::META_TYPE => 'assoc' ), + 'assoc' => array( 2 => 'a', 0 => 'b', 1 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'kvp' => array( 'x' => 'a', 'y' => 'b', + 'z' => array( 'c', ApiResult::META_TYPE => 'array' ), + ApiResult::META_TYPE => 'assoc' + ), + 'BCkvp' => array( 'x' => 'a', 'y' => 'b', + ApiResult::META_TYPE => 'assoc', + ApiResult::META_KVP_KEY_NAME => 'key', + ), + 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), + 'emptyAssoc' => array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), + '_dummy' => 1, + ApiResult::META_PRESERVE_KEYS => array( '_dummy' ), + ApiResult::META_TYPE => 'assoc', + ), + ), + array( + 'Types: AssocAsObject', + $typeArr, + array( 'Types' => array( 'AssocAsObject' => true ) ), + (object)array( + 'defaultArray' => array( 'b', 'c', 'a', ApiResult::META_TYPE => 'array' ), + 'defaultAssoc' => (object)array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'defaultAssoc2' => (object)array( 2 => 'a', 3 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'array' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), + 'BCarray' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), + 'BCassoc' => (object)array( 'a', 'b', 'c', ApiResult::META_TYPE => 'assoc' ), + 'assoc' => (object)array( 2 => 'a', 0 => 'b', 1 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'kvp' => (object)array( 'x' => 'a', 'y' => 'b', + 'z' => array( 'c', ApiResult::META_TYPE => 'array' ), + ApiResult::META_TYPE => 'assoc' + ), + 'BCkvp' => (object)array( 'x' => 'a', 'y' => 'b', + ApiResult::META_TYPE => 'assoc', + ApiResult::META_KVP_KEY_NAME => 'key', + ), + 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), + 'emptyAssoc' => (object)array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), + '_dummy' => 1, + ApiResult::META_PRESERVE_KEYS => array( '_dummy' ), + ApiResult::META_TYPE => 'assoc', + ), + ), + array( + 'Types: ArmorKVP', + $typeArr, + array( 'Types' => array( 'ArmorKVP' => 'name' ) ), + array( + 'defaultArray' => array( 'b', 'c', 'a', ApiResult::META_TYPE => 'array' ), + 'defaultAssoc' => array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'defaultAssoc2' => array( 2 => 'a', 3 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'array' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), + 'BCarray' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), + 'BCassoc' => array( 'a', 'b', 'c', ApiResult::META_TYPE => 'assoc' ), + 'assoc' => array( 2 => 'a', 0 => 'b', 1 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'kvp' => array( + $kvp( 'name', 'x', 'value', 'a' ), + $kvp( 'name', 'y', 'value', 'b' ), + $kvp( 'name', 'z', 'value', array( 'c', ApiResult::META_TYPE => 'array' ) ), + ApiResult::META_TYPE => 'array' + ), + 'BCkvp' => array( + $kvp( 'key', 'x', 'value', 'a' ), + $kvp( 'key', 'y', 'value', 'b' ), + ApiResult::META_TYPE => 'array', + ApiResult::META_KVP_KEY_NAME => 'key', + ), + 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), + 'emptyAssoc' => array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), + '_dummy' => 1, + ApiResult::META_PRESERVE_KEYS => array( '_dummy' ), + ApiResult::META_TYPE => 'assoc', + ), + ), + array( + 'Types: ArmorKVP + BC', + $typeArr, + array( 'BC' => array(), 'Types' => array( 'ArmorKVP' => 'name' ) ), + array( + 'defaultArray' => array( 'b', 'c', 'a', ApiResult::META_TYPE => 'array' ), + 'defaultAssoc' => array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'defaultAssoc2' => array( 2 => 'a', 3 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'array' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), + 'BCarray' => array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'BCassoc' => array( 'a', 'b', 'c', ApiResult::META_TYPE => 'array' ), + 'assoc' => array( 2 => 'a', 0 => 'b', 1 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'kvp' => array( + $kvp( 'name', 'x', '*', 'a' ), + $kvp( 'name', 'y', '*', 'b' ), + $kvp( 'name', 'z', '*', array( 'c', ApiResult::META_TYPE => 'array' ) ), + ApiResult::META_TYPE => 'array' + ), + 'BCkvp' => array( + $kvp( 'key', 'x', '*', 'a' ), + $kvp( 'key', 'y', '*', 'b' ), + ApiResult::META_TYPE => 'array', + ApiResult::META_KVP_KEY_NAME => 'key', + ), + 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), + 'emptyAssoc' => array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), + '_dummy' => 1, + ApiResult::META_PRESERVE_KEYS => array( '_dummy' ), + ApiResult::META_TYPE => 'assoc', + ), + ), + array( + 'Types: ArmorKVP + AssocAsObject', + $typeArr, + array( 'Types' => array( 'ArmorKVP' => 'name', 'AssocAsObject' => true ) ), + (object)array( + 'defaultArray' => array( 'b', 'c', 'a', ApiResult::META_TYPE => 'array' ), + 'defaultAssoc' => (object)array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'defaultAssoc2' => (object)array( 2 => 'a', 3 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'array' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), + 'BCarray' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ), + 'BCassoc' => (object)array( 'a', 'b', 'c', ApiResult::META_TYPE => 'assoc' ), + 'assoc' => (object)array( 2 => 'a', 0 => 'b', 1 => 'c', ApiResult::META_TYPE => 'assoc' ), + 'kvp' => array( + (object)$kvp( 'name', 'x', 'value', 'a' ), + (object)$kvp( 'name', 'y', 'value', 'b' ), + (object)$kvp( 'name', 'z', 'value', array( 'c', ApiResult::META_TYPE => 'array' ) ), + ApiResult::META_TYPE => 'array' + ), + 'BCkvp' => array( + (object)$kvp( 'key', 'x', 'value', 'a' ), + (object)$kvp( 'key', 'y', 'value', 'b' ), + ApiResult::META_TYPE => 'array', + ApiResult::META_KVP_KEY_NAME => 'key', + ), + 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), + 'emptyAssoc' => (object)array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), + '_dummy' => 1, + ApiResult::META_PRESERVE_KEYS => array( '_dummy' ), + ApiResult::META_TYPE => 'assoc', + ), + ), + array( + 'Types: BCkvp exception', + array( + ApiResult::META_TYPE => 'BCkvp', + ), + array( 'Types' => array() ), + new UnexpectedValueException( + 'Type "BCkvp" used without setting ApiResult::META_KVP_KEY_NAME metadata item' + ), + ), + + array( + 'Strip: With ArmorKVP + AssocAsObject transforms', + $typeArr, + array( 'Types' => array( 'ArmorKVP' => 'name', 'AssocAsObject' => true ), 'Strip' => 'all' ), + (object)array( + 'defaultArray' => array( 'b', 'c', 'a' ), + 'defaultAssoc' => (object)array( 'x' => 'a', 1 => 'b', 0 => 'c' ), + 'defaultAssoc2' => (object)array( 2 => 'a', 3 => 'b', 0 => 'c' ), + 'array' => array( 'a', 'c', 'b' ), + 'BCarray' => array( 'a', 'c', 'b' ), + 'BCassoc' => (object)array( 'a', 'b', 'c' ), + 'assoc' => (object)array( 2 => 'a', 0 => 'b', 1 => 'c' ), + 'kvp' => array( + (object)array( 'name' => 'x', 'value' => 'a' ), + (object)array( 'name' => 'y', 'value' => 'b' ), + (object)array( 'name' => 'z', 'value' => array( 'c' ) ), + ), + 'BCkvp' => array( + (object)array( 'key' => 'x', 'value' => 'a' ), + (object)array( 'key' => 'y', 'value' => 'b' ), + ), + 'emptyDefault' => array(), + 'emptyAssoc' => (object)array(), + '_dummy' => 1, + ), + ), + + array( + 'Strip: all', + $stripArr, + array( 'Strip' => 'all' ), + array( + 'foo' => array( + 'bar' => array(), + 'baz' => array(), + 'x' => 'ok', + ), + '_dummy2' => 'foobaz!', + ), + ), + array( + 'Strip: base', + $stripArr, + array( 'Strip' => 'base' ), + array( + 'foo' => array( + 'bar' => array( '_dummy' => 'foobaz' ), + 'baz' => array( + ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ), + ApiResult::META_INDEXED_TAG_NAME => 'itn', + ApiResult::META_PRESERVE_KEYS => array( 'foo', 'bar', '_dummy2', 0 ), + ApiResult::META_TYPE => 'array', + ), + 'x' => 'ok', + '_dummy' => 'foobaz', + ), + '_dummy2' => 'foobaz!', + ), + ), + array( + 'Strip: bc', + $stripArr, + array( 'Strip' => 'bc' ), + array( + 'foo' => array( + 'bar' => array(), + 'baz' => array( + ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ), + ApiResult::META_INDEXED_TAG_NAME => 'itn', + ), + 'x' => 'ok', + ), + '_dummy2' => 'foobaz!', + ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ), + ApiResult::META_INDEXED_TAG_NAME => 'itn', + ), + ), + + array( + 'Custom transform', + array( + 'foo' => '?', + 'bar' => '?', + '_dummy' => '?', + '_dummy2' => '?', + '_dummy3' => '?', + ApiResult::META_CONTENT => 'foo', + ApiResult::META_PRESERVE_KEYS => array( '_dummy2', '_dummy3' ), + ), + array( + 'Custom' => array( $this, 'customTransform' ), + 'BC' => array(), + 'Types' => array(), + 'Strip' => 'all' + ), + array( + '*' => 'FOO', + 'bar' => 'BAR', + 'baz' => array( 'a', 'b' ), + '_dummy2' => '_DUMMY2', + '_dummy3' => '_DUMMY3', + ApiResult::META_CONTENT => 'bar', + ), + ), + ); + + } + + /** + * Custom transformer for testTransformations + * @param array &$data + * @param array &$metadata + */ + public function customTransform( &$data, &$metadata ) { + // Prevent recursion + if ( isset( $metadata['_added'] ) ) { + $metadata[ApiResult::META_TYPE] = 'array'; + return; + } + + foreach ( $data as $k => $v ) { + $data[$k] = strtoupper( $k ); + } + $data['baz'] = array( '_added' => 1, 'z' => 'b', 'y' => 'a' ); + $metadata[ApiResult::META_PRESERVE_KEYS][0] = '_dummy'; + $data[ApiResult::META_CONTENT] = 'bar'; + } + + /** + * @covers ApiResult + */ + public function testDeprecatedFunctions() { + // Ignore ApiResult deprecation warnings during this test + set_error_handler( function ( $errno, $errstr ) use ( &$warnings ) { + if ( preg_match( '/Use of ApiResult::\S+ was deprecated in MediaWiki \d+.\d+\./', $errstr ) ) { + return true; + } + if ( preg_match( '/Use of ApiMain to ApiResult::__construct was deprecated in MediaWiki \d+.\d+\./', $errstr ) ) { + return true; + } + return false; + } ); + $reset = new ScopedCallback( 'restore_error_handler' ); + + $context = new DerivativeContext( RequestContext::getMain() ); + $context->setConfig( new HashConfig( array( + 'APIModules' => array(), + 'APIFormatModules' => array(), + 'APIMaxResultSize' => 42, + ) ) ); + $main = new ApiMain( $context ); + $result = TestingAccessWrapper::newFromObject( new ApiResult( $main ) ); + $this->assertSame( 42, $result->maxSize ); + $this->assertSame( $main->getErrorFormatter(), $result->errorFormatter ); + $this->assertSame( $main, $result->mainForContinuation ); + + $result = new ApiResult( 8388608 ); + + $result->addContentValue( null, 'test', 'content' ); + $result->addContentValue( array( 'foo', 'bar' ), 'test', 'content' ); + $result->addIndexedTagName( null, 'itn' ); + $result->addSubelementsList( null, array( 'sub' ) ); + $this->assertSame( array( + 'foo' => array( + 'bar' => array( + '*' => 'content', + ), + ), + '*' => 'content', + ), $result->getData() ); + $result->setRawMode(); + $this->assertSame( array( + 'foo' => array( + 'bar' => array( + '*' => 'content', + ), + ), + '*' => 'content', + '_element' => 'itn', + '_subelements' => array( 'sub' ), + ), $result->getData() ); + + $arr = array(); + ApiResult::setContent( $arr, 'value' ); + ApiResult::setContent( $arr, 'value2', 'foobar' ); + $this->assertSame( array( + ApiResult::META_CONTENT => 'content', + 'content' => 'value', + 'foobar' => array( + ApiResult::META_CONTENT => 'content', + 'content' => 'value2', + ), + ), $arr ); + + $result = new ApiResult( 3 ); + $formatter = new ApiErrorFormatter_BackCompat( $result ); + $result->setErrorFormatter( $formatter ); + $result->disableSizeCheck(); + $this->assertTrue( $result->addValue( null, 'foo', '1234567890' ) ); + $result->enableSizeCheck(); + $this->assertSame( 0, $result->getSize() ); + $this->assertFalse( $result->addValue( null, 'foo', '1234567890' ) ); + + $arr = array( 'foo' => array( 'bar' => 1 ) ); + $result->setIndexedTagName_recursive( $arr, 'itn' ); + $this->assertSame( array( + 'foo' => array( + 'bar' => 1, + ApiResult::META_INDEXED_TAG_NAME => 'itn' + ), + ), $arr ); + + $status = Status::newGood(); + $status->fatal( 'parentheses', '1' ); + $status->fatal( 'parentheses', '2' ); + $status->warning( 'parentheses', '3' ); + $status->warning( 'parentheses', '4' ); + $this->assertSame( array( + array( + 'type' => 'error', + 'message' => 'parentheses', + 'params' => array( + 0 => '1', + ApiResult::META_INDEXED_TAG_NAME => 'param', + ), + ), + array( + 'type' => 'error', + 'message' => 'parentheses', + 'params' => array( + 0 => '2', + ApiResult::META_INDEXED_TAG_NAME => 'param', + ), + ), + ApiResult::META_INDEXED_TAG_NAME => 'error', + ), $result->convertStatusToArray( $status, 'error' ) ); + $this->assertSame( array( + array( + 'type' => 'warning', + 'message' => 'parentheses', + 'params' => array( + 0 => '3', + ApiResult::META_INDEXED_TAG_NAME => 'param', + ), + ), + array( + 'type' => 'warning', + 'message' => 'parentheses', + 'params' => array( + 0 => '4', + ApiResult::META_INDEXED_TAG_NAME => 'param', + ), + ), + ApiResult::META_INDEXED_TAG_NAME => 'warning', + ), $result->convertStatusToArray( $status, 'warning' ) ); + } + + /** + * @covers ApiResult + */ + public function testDeprecatedContinuation() { + // Ignore ApiResult deprecation warnings during this test + set_error_handler( function ( $errno, $errstr ) use ( &$warnings ) { + if ( preg_match( '/Use of ApiResult::\S+ was deprecated in MediaWiki \d+.\d+\./', $errstr ) ) { + return true; + } + return false; + } ); + + $reset = new ScopedCallback( 'restore_error_handler' ); + $allModules = array( + new MockApiQueryBase( 'mock1' ), + new MockApiQueryBase( 'mock2' ), + new MockApiQueryBase( 'mocklist' ), + ); + $generator = new MockApiQueryBase( 'generator' ); + + $main = new ApiMain( RequestContext::getMain() ); + $result = new ApiResult( 8388608 ); + $result->setMainForContinuation( $main ); + $ret = $result->beginContinuation( null, $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( array( false, $allModules ), $ret ); + $result->setContinueParam( $allModules[0], 'm1continue', array( 1, 2 ) ); + $result->setContinueParam( $allModules[2], 'mlcontinue', 2 ); + $result->setGeneratorContinueParam( $generator, 'gcontinue', 3 ); + $result->endContinuation( 'raw' ); + $result->endContinuation( 'standard' ); + $this->assertSame( array( + 'mlcontinue' => 2, + 'm1continue' => '1|2', + 'continue' => '||mock2', + ), $result->getResultData( 'continue' ) ); + $this->assertSame( null, $result->getResultData( 'batchcomplete' ) ); + $this->assertSame( array( + 'mock1' => array( 'm1continue' => '1|2' ), + 'mocklist' => array( 'mlcontinue' => 2 ), + 'generator' => array( 'gcontinue' => 3 ), + ), $result->getResultData( 'query-continue' ) ); + $main->setContinuationManager( null ); + + $result = new ApiResult( 8388608 ); + $result->setMainForContinuation( $main ); + $ret = $result->beginContinuation( null, $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( array( false, $allModules ), $ret ); + $result->setContinueParam( $allModules[0], 'm1continue', array( 1, 2 ) ); + $result->setGeneratorContinueParam( $generator, 'gcontinue', array( 3, 4 ) ); + $result->endContinuation( 'raw' ); + $result->endContinuation( 'standard' ); + $this->assertSame( array( + 'm1continue' => '1|2', + 'continue' => '||mock2|mocklist', + ), $result->getResultData( 'continue' ) ); + $this->assertSame( null, $result->getResultData( 'batchcomplete' ) ); + $this->assertSame( array( + 'mock1' => array( 'm1continue' => '1|2' ), + 'generator' => array( 'gcontinue' => '3|4' ), + ), $result->getResultData( 'query-continue' ) ); + $main->setContinuationManager( null ); + + $result = new ApiResult( 8388608 ); + $result->setMainForContinuation( $main ); + $ret = $result->beginContinuation( null, $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( array( false, $allModules ), $ret ); + $result->setContinueParam( $allModules[2], 'mlcontinue', 2 ); + $result->setGeneratorContinueParam( $generator, 'gcontinue', 3 ); + $result->endContinuation( 'raw' ); + $result->endContinuation( 'standard' ); + $this->assertSame( array( + 'mlcontinue' => 2, + 'gcontinue' => 3, + 'continue' => 'gcontinue||', + ), $result->getResultData( 'continue' ) ); + $this->assertSame( true, $result->getResultData( 'batchcomplete' ) ); + $this->assertSame( array( + 'mocklist' => array( 'mlcontinue' => 2 ), + 'generator' => array( 'gcontinue' => 3 ), + ), $result->getResultData( 'query-continue' ) ); + $main->setContinuationManager( null ); + + $result = new ApiResult( 8388608 ); + $result->setMainForContinuation( $main ); + $ret = $result->beginContinuation( null, $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( array( false, $allModules ), $ret ); + $result->setGeneratorContinueParam( $generator, 'gcontinue', 3 ); + $result->endContinuation( 'raw' ); + $result->endContinuation( 'standard' ); + $this->assertSame( array( + 'gcontinue' => 3, + 'continue' => 'gcontinue||mocklist', + ), $result->getResultData( 'continue' ) ); + $this->assertSame( true, $result->getResultData( 'batchcomplete' ) ); + $this->assertSame( array( + 'generator' => array( 'gcontinue' => 3 ), + ), $result->getResultData( 'query-continue' ) ); + $main->setContinuationManager( null ); + + $result = new ApiResult( 8388608 ); + $result->setMainForContinuation( $main ); + $ret = $result->beginContinuation( null, $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( array( false, $allModules ), $ret ); + $result->setContinueParam( $allModules[0], 'm1continue', array( 1, 2 ) ); + $result->setContinueParam( $allModules[2], 'mlcontinue', 2 ); + $result->endContinuation( 'raw' ); + $result->endContinuation( 'standard' ); + $this->assertSame( array( + 'mlcontinue' => 2, + 'm1continue' => '1|2', + 'continue' => '||mock2', + ), $result->getResultData( 'continue' ) ); + $this->assertSame( null, $result->getResultData( 'batchcomplete' ) ); + $this->assertSame( array( + 'mock1' => array( 'm1continue' => '1|2' ), + 'mocklist' => array( 'mlcontinue' => 2 ), + ), $result->getResultData( 'query-continue' ) ); + $main->setContinuationManager( null ); + + $result = new ApiResult( 8388608 ); + $result->setMainForContinuation( $main ); + $ret = $result->beginContinuation( null, $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( array( false, $allModules ), $ret ); + $result->setContinueParam( $allModules[0], 'm1continue', array( 1, 2 ) ); + $result->endContinuation( 'raw' ); + $result->endContinuation( 'standard' ); + $this->assertSame( array( + 'm1continue' => '1|2', + 'continue' => '||mock2|mocklist', + ), $result->getResultData( 'continue' ) ); + $this->assertSame( null, $result->getResultData( 'batchcomplete' ) ); + $this->assertSame( array( + 'mock1' => array( 'm1continue' => '1|2' ), + ), $result->getResultData( 'query-continue' ) ); + $main->setContinuationManager( null ); + + $result = new ApiResult( 8388608 ); + $result->setMainForContinuation( $main ); + $ret = $result->beginContinuation( null, $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( array( false, $allModules ), $ret ); + $result->setContinueParam( $allModules[2], 'mlcontinue', 2 ); + $result->endContinuation( 'raw' ); + $result->endContinuation( 'standard' ); + $this->assertSame( array( + 'mlcontinue' => 2, + 'continue' => '-||mock1|mock2', + ), $result->getResultData( 'continue' ) ); + $this->assertSame( true, $result->getResultData( 'batchcomplete' ) ); + $this->assertSame( array( + 'mocklist' => array( 'mlcontinue' => 2 ), + ), $result->getResultData( 'query-continue' ) ); + $main->setContinuationManager( null ); + + $result = new ApiResult( 8388608 ); + $result->setMainForContinuation( $main ); + $ret = $result->beginContinuation( null, $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( array( false, $allModules ), $ret ); + $result->endContinuation( 'raw' ); + $result->endContinuation( 'standard' ); + $this->assertSame( null, $result->getResultData( 'continue' ) ); + $this->assertSame( true, $result->getResultData( 'batchcomplete' ) ); + $this->assertSame( null, $result->getResultData( 'query-continue' ) ); + $main->setContinuationManager( null ); + + $result = new ApiResult( 8388608 ); + $result->setMainForContinuation( $main ); + $ret = $result->beginContinuation( '||mock2', $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( + array( false, array_values( array_diff_key( $allModules, array( 1 => 1 ) ) ) ), + $ret + ); + $main->setContinuationManager( null ); + + $result = new ApiResult( 8388608 ); + $result->setMainForContinuation( $main ); + $ret = $result->beginContinuation( '-||', $allModules, array( 'mock1', 'mock2' ) ); + $this->assertSame( + array( true, array_values( array_diff_key( $allModules, array( 0 => 0, 1 => 1 ) ) ) ), + $ret + ); + $main->setContinuationManager( null ); + + $result = new ApiResult( 8388608 ); + $result->setMainForContinuation( $main ); + try { + $result->beginContinuation( 'foo', $allModules, array( 'mock1', 'mock2' ) ); + $this->fail( 'Expected exception not thrown' ); + } catch ( UsageException $ex ) { + $this->assertSame( + 'Invalid continue param. You should pass the original value returned by the previous query', + $ex->getMessage(), + 'Expected exception' + ); + } + $main->setContinuationManager( null ); + + $result = new ApiResult( 8388608 ); + $result->setMainForContinuation( $main ); + $result->beginContinuation( '||mock2', array_slice( $allModules, 0, 2 ), array( 'mock1', 'mock2' ) ); + try { + $result->setContinueParam( $allModules[1], 'm2continue', 1 ); + $this->fail( 'Expected exception not thrown' ); + } catch ( UnexpectedValueException $ex ) { + $this->assertSame( + 'Module \'mock2\' was not supposed to have been executed, but it was executed anyway', + $ex->getMessage(), + 'Expected exception' + ); + } + try { + $result->setContinueParam( $allModules[2], 'mlcontinue', 1 ); + $this->fail( 'Expected exception not thrown' ); + } catch ( UnexpectedValueException $ex ) { + $this->assertSame( + 'Module \'mocklist\' called ApiContinuationManager::addContinueParam but was not passed to ApiContinuationManager::__construct', + $ex->getMessage(), + 'Expected exception' + ); + } + $main->setContinuationManager( null ); + + } + + public function testObjectSerialization() { + $arr = array(); + ApiResult::setValue( $arr, 'foo', (object)array( 'a' => 1, 'b' => 2 ) ); + $this->assertSame( array( + 'a' => 1, + 'b' => 2, + ApiResult::META_TYPE => 'assoc', + ), $arr['foo'] ); + + $arr = array(); + ApiResult::setValue( $arr, 'foo', new ApiResultTestStringifiableObject() ); + $this->assertSame( 'Ok', $arr['foo'] ); + + $arr = array(); + ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( 'Ok' ) ); + $this->assertSame( 'Ok', $arr['foo'] ); + + try { + $arr = array(); + ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( + new ApiResultTestStringifiableObject() + ) ); + $this->fail( 'Expected exception not thrown' ); + } catch ( UnexpectedValueException $ex ) { + $this->assertSame( + 'ApiResultTestSerializableObject::serializeForApiResult() returned an object of class ApiResultTestStringifiableObject', + $ex->getMessage(), + 'Expected exception' + ); + } + + try { + $arr = array(); + ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( NAN ) ); + $this->fail( 'Expected exception not thrown' ); + } catch ( UnexpectedValueException $ex ) { + $this->assertSame( + 'ApiResultTestSerializableObject::serializeForApiResult() returned an invalid value: Cannot add non-finite floats to ApiResult', + $ex->getMessage(), + 'Expected exception' + ); + } + + $arr = array(); + ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( + array( + 'one' => new ApiResultTestStringifiableObject( '1' ), + 'two' => new ApiResultTestSerializableObject( 2 ), + ) + ) ); + $this->assertSame( array( + 'one' => '1', + 'two' => 2, + ), $arr['foo'] ); + } + +} + +class ApiResultTestStringifiableObject { + private $ret; + + public function __construct( $ret = 'Ok' ) { + $this->ret = $ret; + } + + public function __toString() { + return $this->ret; + } +} + +class ApiResultTestSerializableObject { + private $ret; + + public function __construct( $ret ) { + $this->ret = $ret; + } + + public function __toString() { + return "Fail"; + } + + public function serializeForApiResult() { + return $this->ret; + } +} diff --git a/tests/phpunit/includes/api/ApiTestCase.php b/tests/phpunit/includes/api/ApiTestCase.php index cd141947..da62bb0a 100644 --- a/tests/phpunit/includes/api/ApiTestCase.php +++ b/tests/phpunit/includes/api/ApiTestCase.php @@ -8,6 +8,11 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { */ protected $apiContext; + /** + * @var array + */ + protected $tablesUsed = array( 'user', 'user_groups', 'user_properties' ); + protected function setUp() { global $wgServer; @@ -41,6 +46,17 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { $this->apiContext = new ApiTestContext(); } + protected function tearDown() { + // Avoid leaking session over tests + if ( session_id() != '' ) { + global $wgUser; + $wgUser->logout(); + session_destroy(); + } + + parent::tearDown(); + } + /** * Edits or creates a page/revision * @param string $pageName Page title @@ -100,7 +116,7 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { // construct result $results = array( - $module->getResultData(), + $module->getResult()->getResultData( null, array( 'Strip' => 'all' ) ), $context->getRequest(), $context->getRequest()->getSessionArray() ); @@ -134,10 +150,14 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { } if ( isset( $session['wsToken'] ) && $session['wsToken'] ) { + // @todo Why does this directly mess with the session? Fix that. // add edit token to fake session $session['wsEditToken'] = $session['wsToken']; // add token to request parameters - $params['token'] = md5( $session['wsToken'] ) . User::EDIT_TOKEN_SUFFIX; + $timestamp = wfTimestamp(); + $params['token'] = hash_hmac( 'md5', $timestamp, $session['wsToken'] ) . + dechex( $timestamp ) . + User::EDIT_TOKEN_SUFFIX; return $this->doApiRequest( $params, $session, false, $user ); } else { diff --git a/tests/phpunit/includes/api/ApiTestCaseUpload.php b/tests/phpunit/includes/api/ApiTestCaseUpload.php index 7e513394..87f794c1 100644 --- a/tests/phpunit/includes/api/ApiTestCaseUpload.php +++ b/tests/phpunit/includes/api/ApiTestCaseUpload.php @@ -1,9 +1,8 @@ <?php /** - * * Abstract class to support upload tests + * Abstract class to support upload tests */ - abstract class ApiTestCaseUpload extends ApiTestCase { /** * Fixture -- run before every test @@ -21,12 +20,6 @@ abstract class ApiTestCaseUpload extends ApiTestCase { $this->clearFakeUploads(); } - protected function tearDown() { - $this->clearTempUpload(); - - parent::tearDown(); - } - /** * Helper function -- remove files and associated articles by Title * @@ -105,7 +98,7 @@ abstract class ApiTestCaseUpload extends ApiTestCase { * @return bool */ function fakeUploadFile( $fieldName, $fileName, $type, $filePath ) { - $tmpName = tempnam( wfTempDir(), "" ); + $tmpName = $this->getNewTempFile(); if ( !file_exists( $filePath ) ) { throw new Exception( "$filePath doesn't exist!" ); } @@ -132,7 +125,7 @@ abstract class ApiTestCaseUpload extends ApiTestCase { } function fakeUploadChunk( $fieldName, $fileName, $type, & $chunkData ) { - $tmpName = tempnam( wfTempDir(), "" ); + $tmpName = $this->getNewTempFile(); // copy the chunk data to temp location: if ( !file_put_contents( $tmpName, $chunkData ) ) { throw new Exception( "couldn't copy chunk data to $tmpName" ); @@ -153,15 +146,6 @@ abstract class ApiTestCaseUpload extends ApiTestCase { ); } - function clearTempUpload() { - if ( isset( $_FILES['file']['tmp_name'] ) ) { - $tmp = $_FILES['file']['tmp_name']; - if ( file_exists( $tmp ) ) { - unlink( $tmp ); - } - } - } - /** * Remove traces of previous fake uploads */ diff --git a/tests/phpunit/includes/api/ApiUploadTest.php b/tests/phpunit/includes/api/ApiUploadTest.php index 8ea761f8..f74fc354 100644 --- a/tests/phpunit/includes/api/ApiUploadTest.php +++ b/tests/phpunit/includes/api/ApiUploadTest.php @@ -1,31 +1,24 @@ <?php /** - * @group API - * @group Database - * @group medium - */ - -/** * n.b. Ensure that you can write to the images/ directory as the * user that will run tests. - */ - -// Note for reviewers: this intentionally duplicates functionality already in -// "ApiSetup" and so on. This framework works better IMO and has less -// strangeness (such as test cases inheriting from "ApiSetup"...) (and in the -// case of the other Upload tests, this flat out just actually works... ) - -// @todo Port the other Upload tests, and other API tests to this framework - -require_once 'ApiTestCaseUpload.php'; - -/** - * @group Database - * @group Broken - * Broken test, reports false errors from time to time. + * + * Note for reviewers: this intentionally duplicates functionality already in + * "ApiSetup" and so on. This framework works better IMO and has less + * strangeness (such as test cases inheriting from "ApiSetup"...) (and in the + * case of the other Upload tests, this flat out just actually works... ) + * + * @todo Port the other Upload tests, and other API tests to this framework + * + * @todo Broken test, reports false errors from time to time. * See https://bugzilla.wikimedia.org/26169 * - * This is pretty sucky... needs to be prettified. + * @todo This is pretty sucky... needs to be prettified. + * + * @group API + * @group Database + * @group medium + * @group Broken */ class ApiUploadTest extends ApiTestCaseUpload { /** @@ -105,7 +98,7 @@ class ApiUploadTest extends ApiTestCaseUpload { try { $randomImageGenerator = new RandomImageGenerator(); - $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() ); + $filePaths = $randomImageGenerator->writeImages( 1, $extension, $this->getNewTempDirectory() ); } catch ( Exception $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -145,7 +138,6 @@ class ApiUploadTest extends ApiTestCaseUpload { // clean up $this->deleteFileByFilename( $fileName ); - unlink( $filePath ); } /** @@ -154,7 +146,7 @@ class ApiUploadTest extends ApiTestCaseUpload { public function testUploadZeroLength( $session ) { $mimeType = 'image/png'; - $filePath = tempnam( wfTempDir(), "" ); + $filePath = $this->getNewTempFile(); $fileName = "apiTestUploadZeroLength.png"; $this->deleteFileByFileName( $fileName ); @@ -182,7 +174,6 @@ class ApiUploadTest extends ApiTestCaseUpload { // clean up $this->deleteFileByFilename( $fileName ); - unlink( $filePath ); } /** @@ -194,7 +185,7 @@ class ApiUploadTest extends ApiTestCaseUpload { try { $randomImageGenerator = new RandomImageGenerator(); - $filePaths = $randomImageGenerator->writeImages( 2, $extension, wfTempDir() ); + $filePaths = $randomImageGenerator->writeImages( 2, $extension, $this->getNewTempDirectory() ); } catch ( Exception $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -253,8 +244,6 @@ class ApiUploadTest extends ApiTestCaseUpload { // clean up $this->deleteFileByFilename( $fileName ); - unlink( $filePaths[0] ); - unlink( $filePaths[1] ); } /** @@ -266,7 +255,7 @@ class ApiUploadTest extends ApiTestCaseUpload { try { $randomImageGenerator = new RandomImageGenerator(); - $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() ); + $filePaths = $randomImageGenerator->writeImages( 1, $extension, $this->getNewTempDirectory() ); } catch ( Exception $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -335,7 +324,6 @@ class ApiUploadTest extends ApiTestCaseUpload { // clean up $this->deleteFileByFilename( $fileNames[0] ); $this->deleteFileByFilename( $fileNames[1] ); - unlink( $filePaths[0] ); } /** @@ -351,7 +339,7 @@ class ApiUploadTest extends ApiTestCaseUpload { try { $randomImageGenerator = new RandomImageGenerator(); - $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() ); + $filePaths = $randomImageGenerator->writeImages( 1, $extension, $this->getNewTempDirectory() ); } catch ( Exception $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -419,7 +407,6 @@ class ApiUploadTest extends ApiTestCaseUpload { // clean up $this->deleteFileByFilename( $fileName ); - unlink( $filePath ); } /** @@ -433,16 +420,14 @@ class ApiUploadTest extends ApiTestCaseUpload { $chunkSize = 1048576; // Download a large image file - // ( using RandomImageGenerator for large files is not stable ) + // (using RandomImageGenerator for large files is not stable) + // @todo Don't download files from wikimedia.org $mimeType = 'image/jpeg'; $url = 'http://upload.wikimedia.org/wikipedia/commons/' . 'e/ed/Oberaargletscher_from_Oberaar%2C_2010_07.JPG'; - $filePath = wfTempDir() . '/Oberaargletscher_from_Oberaar.jpg'; + $filePath = $this->getNewTempDirectory() . '/Oberaargletscher_from_Oberaar.jpg'; try { - // Only download if the file is not avaliable in the temp location: - if ( !is_file( $filePath ) ) { - copy( $url, $filePath ); - } + copy( $url, $filePath ); } catch ( Exception $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -566,7 +551,5 @@ class ApiUploadTest extends ApiTestCaseUpload { // clean up $this->deleteFileByFilename( $fileName ); - // don't remove downloaded temporary file for fast subquent tests. - //unlink( $filePath ); } } diff --git a/tests/phpunit/includes/api/MockApi.php b/tests/phpunit/includes/api/MockApi.php index d94aa2cd..516da0c8 100644 --- a/tests/phpunit/includes/api/MockApi.php +++ b/tests/phpunit/includes/api/MockApi.php @@ -4,9 +4,6 @@ class MockApi extends ApiBase { public function execute() { } - public function getVersion() { - } - public function __construct() { } diff --git a/tests/phpunit/includes/api/MockApiQueryBase.php b/tests/phpunit/includes/api/MockApiQueryBase.php index 4bede519..f5b50e5a 100644 --- a/tests/phpunit/includes/api/MockApiQueryBase.php +++ b/tests/phpunit/includes/api/MockApiQueryBase.php @@ -1,11 +1,15 @@ <?php class MockApiQueryBase extends ApiQueryBase { + private $name; + public function execute() { } - public function getVersion() { + public function __construct( $name = 'mock' ) { + $this->name = $name; } - public function __construct() { + public function getModuleName() { + return $this->name; } } diff --git a/tests/phpunit/includes/api/PrefixUniquenessTest.php b/tests/phpunit/includes/api/PrefixUniquenessTest.php index 13da33c7..d04766be 100644 --- a/tests/phpunit/includes/api/PrefixUniquenessTest.php +++ b/tests/phpunit/includes/api/PrefixUniquenessTest.php @@ -20,7 +20,7 @@ class PrefixUniquenessTest extends MediaWikiTestCase { $class = get_class( $module ); $prefix = $module->getModulePrefix(); - if ( isset( $prefixes[$prefix] ) ) { + if ( $prefix !== '' && isset( $prefixes[$prefix] ) ) { $this->fail( "Module prefix '{$prefix}' is shared between {$class} and {$prefixes[$prefix]}" ); } $prefixes[$module->getModulePrefix()] = $class; diff --git a/tests/phpunit/includes/api/format/ApiFormatDbgTest.php b/tests/phpunit/includes/api/format/ApiFormatDbgTest.php new file mode 100644 index 00000000..3fcfc73f --- /dev/null +++ b/tests/phpunit/includes/api/format/ApiFormatDbgTest.php @@ -0,0 +1,55 @@ +<?php + +/** + * @group API + * @covers ApiFormatDbg + */ +class ApiFormatDbgTest extends ApiFormatTestBase { + + protected $printerName = 'dbg'; + + public static function provideGeneralEncoding() { + $warning = "\n 'warnings' => \n array (\n 'dbg' => \n array (\n" . + " '*' => 'format=dbg has been deprecated. Please use format=json instead.',\n" . + " ),\n ),"; + + return array( + // Basic types + array( array( null ), "array ({$warning}\n 0 => NULL,\n)" ), + array( array( true ), "array ({$warning}\n 0 => '',\n)" ), + array( array( false ), "array ({$warning}\n)" ), + array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ), + "array ({$warning}\n 0 => true,\n)" ), + array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ), + "array ({$warning}\n 0 => false,\n)" ), + array( array( 42 ), "array ({$warning}\n 0 => 42,\n)" ), + array( array( 42.5 ), "array ({$warning}\n 0 => 42.5,\n)" ), + array( array( 1e42 ), "array ({$warning}\n 0 => 1.0E+42,\n)" ), + array( array( 'foo' ), "array ({$warning}\n 0 => 'foo',\n)" ), + array( array( 'fóo' ), "array ({$warning}\n 0 => 'fóo',\n)" ), + + // Arrays and objects + array( array( array() ), "array ({$warning}\n 0 => \n array (\n ),\n)" ), + array( array( array( 1 ) ), "array ({$warning}\n 0 => \n array (\n 0 => 1,\n ),\n)" ), + array( array( array( 'x' => 1 ) ), "array ({$warning}\n 0 => \n array (\n 'x' => 1,\n ),\n)" ), + array( array( array( 2 => 1 ) ), "array ({$warning}\n 0 => \n array (\n 2 => 1,\n ),\n)" ), + array( array( (object)array() ), "array ({$warning}\n 0 => \n array (\n ),\n)" ), + array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), "array ({$warning}\n 0 => \n array (\n 0 => 1,\n ),\n)" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), "array ({$warning}\n 0 => \n array (\n 0 => 1,\n ),\n)" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), "array ({$warning}\n 0 => \n array (\n 'x' => 1,\n ),\n)" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), + "array ({$warning}\n 0 => \n array (\n 0 => \n array (\n 'key' => 'x',\n '*' => 1,\n ),\n ),\n)" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), "array ({$warning}\n 0 => \n array (\n 'x' => 1,\n ),\n)" ), + array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), "array ({$warning}\n 0 => \n array (\n 0 => 'a',\n 1 => 'b',\n ),\n)" ), + + // Content + array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), + "array ({$warning}\n '*' => 'foo',\n)" ), + + // BC Subelements + array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), + "array ({$warning}\n 'foo' => \n array (\n '*' => 'foo',\n ),\n)" ), + ); + } + +} diff --git a/tests/phpunit/includes/api/format/ApiFormatDumpTest.php b/tests/phpunit/includes/api/format/ApiFormatDumpTest.php new file mode 100644 index 00000000..c0f67f8d --- /dev/null +++ b/tests/phpunit/includes/api/format/ApiFormatDumpTest.php @@ -0,0 +1,63 @@ +<?php + +/** + * @group API + * @covers ApiFormatDump + */ +class ApiFormatDumpTest extends ApiFormatTestBase { + + protected $printerName = 'dump'; + + public static function provideGeneralEncoding() { + // Sigh. Docs claim it's a boolean, but can have values 0, 1, or 2. + // Fortunately wfIniGetBool does the right thing. + if ( wfIniGetBool( 'xdebug.overload_var_dump' ) ) { + return array( + array( array(), 'Cannot test ApiFormatDump when xDebug overloads var_dump', array( 'SKIP' => true ) ), + ); + } + + $warning = "\n [\"warnings\"]=>\n array(1) {\n [\"dump\"]=>\n array(1) {\n [\"*\"]=>\n" . + " string(64) \"format=dump has been deprecated. Please use format=json instead.\"\n" . + " }\n }"; + + return array( + // Basic types + array( array( null ), "array(2) {{$warning}\n [0]=>\n NULL\n}\n" ), + array( array( true ), "array(2) {{$warning}\n [0]=>\n string(0) \"\"\n}\n" ), + array( array( false ), "array(1) {{$warning}\n}\n" ), + array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ), + "array(2) {{$warning}\n [0]=>\n bool(true)\n}\n" ), + array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ), + "array(2) {{$warning}\n [0]=>\n bool(false)\n}\n" ), + array( array( 42 ), "array(2) {{$warning}\n [0]=>\n int(42)\n}\n" ), + array( array( 42.5 ), "array(2) {{$warning}\n [0]=>\n float(42.5)\n}\n" ), + array( array( 1e42 ), "array(2) {{$warning}\n [0]=>\n float(1.0E+42)\n}\n" ), + array( array( 'foo' ), "array(2) {{$warning}\n [0]=>\n string(3) \"foo\"\n}\n" ), + array( array( 'fóo' ), "array(2) {{$warning}\n [0]=>\n string(4) \"fóo\"\n}\n" ), + + // Arrays + array( array( array() ), "array(2) {{$warning}\n [0]=>\n array(0) {\n }\n}\n" ), + array( array( array( 1 ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [0]=>\n int(1)\n }\n}\n" ), + array( array( array( 'x' => 1 ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [\"x\"]=>\n int(1)\n }\n}\n" ), + array( array( array( 2 => 1 ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [2]=>\n int(1)\n }\n}\n" ), + array( array( (object)array() ), "array(2) {{$warning}\n [0]=>\n array(0) {\n }\n}\n" ), + array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [0]=>\n int(1)\n }\n}\n" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [0]=>\n int(1)\n }\n}\n" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [\"x\"]=>\n int(1)\n }\n}\n" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), + "array(2) {{$warning}\n [0]=>\n array(1) {\n [0]=>\n array(2) {\n [\"key\"]=>\n string(1) \"x\"\n [\"*\"]=>\n int(1)\n }\n }\n}\n" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [\"x\"]=>\n int(1)\n }\n}\n" ), + array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), "array(2) {{$warning}\n [0]=>\n array(2) {\n [0]=>\n string(1) \"a\"\n [1]=>\n string(1) \"b\"\n }\n}\n" ), + + // Content + array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), + "array(2) {{$warning}\n [\"*\"]=>\n string(3) \"foo\"\n}\n" ), + + // BC Subelements + array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), + "array(2) {{$warning}\n [\"foo\"]=>\n array(1) {\n [\"*\"]=>\n string(3) \"foo\"\n }\n}\n" ), + ); + } + +} diff --git a/tests/phpunit/includes/api/format/ApiFormatJsonTest.php b/tests/phpunit/includes/api/format/ApiFormatJsonTest.php index fc1f9021..3dfcaf0f 100644 --- a/tests/phpunit/includes/api/format/ApiFormatJsonTest.php +++ b/tests/phpunit/includes/api/format/ApiFormatJsonTest.php @@ -2,21 +2,109 @@ /** * @group API - * @group Database - * @group medium * @covers ApiFormatJson */ class ApiFormatJsonTest extends ApiFormatTestBase { - public function testValidSyntax( ) { - $data = $this->apiRequest( 'json', array( 'action' => 'query', 'meta' => 'siteinfo' ) ); + protected $printerName = 'json'; - $this->assertInternalType( 'array', json_decode( $data, true ) ); - $this->assertGreaterThan( 0, count( (array)$data ) ); + private static function addFormatVersion( $format, $arr ) { + foreach ( $arr as &$p ) { + if ( !isset( $p[2] ) ) { + $p[2] = array( 'formatversion' => $format ); + } else { + $p[2]['formatversion'] = $format; + } + } + return $arr; } - public function testJsonpInjection( ) { - $data = $this->apiRequest( 'json', array( 'action' => 'query', 'meta' => 'siteinfo', 'callback' => 'myCallback' ) ); - $this->assertEquals( '/**/myCallback(', substr( $data, 0, 15 ) ); + public static function provideGeneralEncoding() { + return array_merge( + self::addFormatVersion( 1, array( + // Basic types + array( array( null ), '[null]' ), + array( array( true ), '[""]' ), + array( array( false ), '[]' ), + array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ), '[true]' ), + array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ), '[false]' ), + array( array( 42 ), '[42]' ), + array( array( 42.5 ), '[42.5]' ), + array( array( 1e42 ), '[1.0e+42]' ), + array( array( 'foo' ), '["foo"]' ), + array( array( 'fóo' ), '["f\u00f3o"]' ), + array( array( 'fóo' ), '["fóo"]', array( 'utf8' => 1 ) ), + + // Arrays and objects + array( array( array() ), '[[]]' ), + array( array( array( 1 ) ), '[[1]]' ), + array( array( array( 'x' => 1 ) ), '[{"x":1}]' ), + array( array( array( 2 => 1 ) ), '[{"2":1}]' ), + array( array( (object)array() ), '[{}]' ), + array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), '[{"0":1}]' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), '[[1]]' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), '[{"x":1}]' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), + '[[{"key":"x","*":1}]]' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), '[{"x":1}]' ), + array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), '[["a","b"]]' ), + + // Content + array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), + '{"*":"foo"}' ), + + // BC Subelements + array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), + '{"foo":{"*":"foo"}}' ), + + // Callbacks + array( array( 1 ), '/**/myCallback([1])', array( 'callback' => 'myCallback' ) ), + + // Cross-domain mangling + array( array( '< Cross-Domain-Policy >' ), '["\u003C Cross-Domain-Policy \u003E"]' ), + ) ), + self::addFormatVersion( 2, array( + // Basic types + array( array( null ), '[null]' ), + array( array( true ), '[true]' ), + array( array( false ), '[false]' ), + array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ), '[true]' ), + array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ), '[false]' ), + array( array( 42 ), '[42]' ), + array( array( 42.5 ), '[42.5]' ), + array( array( 1e42 ), '[1.0e+42]' ), + array( array( 'foo' ), '["foo"]' ), + array( array( 'fóo' ), '["fóo"]' ), + array( array( 'fóo' ), '["f\u00f3o"]', array( 'ascii' => 1 ) ), + + // Arrays and objects + array( array( array() ), '[[]]' ), + array( array( array( 'x' => 1 ) ), '[{"x":1}]' ), + array( array( array( 2 => 1 ) ), '[{"2":1}]' ), + array( array( (object)array() ), '[{}]' ), + array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), '[{"0":1}]' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), '[[1]]' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), '[{"x":1}]' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), + '[{"x":1}]' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), '[[1]]' ), + array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), '[{"0":"a","1":"b"}]' ), + + // Content + array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), + '{"content":"foo"}' ), + + // BC Subelements + array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), + '{"foo":"foo"}' ), + + // Callbacks + array( array( 1 ), '/**/myCallback([1])', array( 'callback' => 'myCallback' ) ), + + // Cross-domain mangling + array( array( '< Cross-Domain-Policy >' ), '["\u003C Cross-Domain-Policy \u003E"]' ), + ) ) + ); } + } diff --git a/tests/phpunit/includes/api/format/ApiFormatNoneTest.php b/tests/phpunit/includes/api/format/ApiFormatNoneTest.php index cabd750b..8f81a411 100644 --- a/tests/phpunit/includes/api/format/ApiFormatNoneTest.php +++ b/tests/phpunit/includes/api/format/ApiFormatNoneTest.php @@ -2,15 +2,43 @@ /** * @group API - * @group Database - * @group medium * @covers ApiFormatNone */ class ApiFormatNoneTest extends ApiFormatTestBase { - public function testValidSyntax( ) { - $data = $this->apiRequest( 'none', array( 'action' => 'query', 'meta' => 'siteinfo' ) ); + protected $printerName = 'none'; - $this->assertEquals( '', $data ); // No output! + public static function provideGeneralEncoding() { + return array( + // Basic types + array( array( null ), '' ), + array( array( true ), '' ), + array( array( false ), '' ), + array( array( 42 ), '' ), + array( array( 42.5 ), '' ), + array( array( 1e42 ), '' ), + array( array( 'foo' ), '' ), + array( array( 'fóo' ), '' ), + + // Arrays and objects + array( array( array() ), '' ), + array( array( array( 1 ) ), '' ), + array( array( array( 'x' => 1 ) ), '' ), + array( array( array( 2 => 1 ) ), '' ), + array( array( (object)array() ), '' ), + array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), '' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), '' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), '' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), '' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), '' ), + array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), '' ), + + // Content + array( array( '*' => 'foo' ), '' ), + + // BC Subelements + array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), '' ), + ); } + } diff --git a/tests/phpunit/includes/api/format/ApiFormatPhpTest.php b/tests/phpunit/includes/api/format/ApiFormatPhpTest.php index 54f447a9..0cb44e92 100644 --- a/tests/phpunit/includes/api/format/ApiFormatPhpTest.php +++ b/tests/phpunit/includes/api/format/ApiFormatPhpTest.php @@ -2,16 +2,143 @@ /** * @group API - * @group Database - * @group medium * @covers ApiFormatPhp */ class ApiFormatPhpTest extends ApiFormatTestBase { - public function testValidSyntax( ) { - $data = $this->apiRequest( 'php', array( 'action' => 'query', 'meta' => 'siteinfo' ) ); + protected $printerName = 'php'; - $this->assertInternalType( 'array', unserialize( $data ) ); - $this->assertGreaterThan( 0, count( (array)$data ) ); + private static function addFormatVersion( $format, $arr ) { + foreach ( $arr as &$p ) { + if ( !isset( $p[2] ) ) { + $p[2] = array( 'formatversion' => $format ); + } else { + $p[2]['formatversion'] = $format; + } + } + return $arr; } + + public static function provideGeneralEncoding() { + return array_merge( + self::addFormatVersion( 1, array( + // Basic types + array( array( null ), 'a:1:{i:0;N;}' ), + array( array( true ), 'a:1:{i:0;s:0:"";}' ), + array( array( false ), 'a:0:{}' ), + array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ), + 'a:1:{i:0;b:1;}' ), + array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ), + 'a:1:{i:0;b:0;}' ), + array( array( 42 ), 'a:1:{i:0;i:42;}' ), + array( array( 42.5 ), 'a:1:{i:0;d:42.5;}' ), + array( array( 1e42 ), 'a:1:{i:0;d:1.0E+42;}' ), + array( array( 'foo' ), 'a:1:{i:0;s:3:"foo";}' ), + array( array( 'fóo' ), 'a:1:{i:0;s:4:"fóo";}' ), + + // Arrays and objects + array( array( array() ), 'a:1:{i:0;a:0:{}}' ), + array( array( array( 1 ) ), 'a:1:{i:0;a:1:{i:0;i:1;}}' ), + array( array( array( 'x' => 1 ) ), 'a:1:{i:0;a:1:{s:1:"x";i:1;}}' ), + array( array( array( 2 => 1 ) ), 'a:1:{i:0;a:1:{i:2;i:1;}}' ), + array( array( (object)array() ), 'a:1:{i:0;a:0:{}}' ), + array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), 'a:1:{i:0;a:1:{i:0;i:1;}}' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), 'a:1:{i:0;a:1:{i:0;i:1;}}' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), 'a:1:{i:0;a:1:{s:1:"x";i:1;}}' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), + 'a:1:{i:0;a:1:{i:0;a:2:{s:3:"key";s:1:"x";s:1:"*";i:1;}}}' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), 'a:1:{i:0;a:1:{s:1:"x";i:1;}}' ), + array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), 'a:1:{i:0;a:2:{i:0;s:1:"a";i:1;s:1:"b";}}' ), + + // Content + array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), + 'a:1:{s:1:"*";s:3:"foo";}' ), + + // BC Subelements + array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), + 'a:1:{s:3:"foo";a:1:{s:1:"*";s:3:"foo";}}' ), + ) ), + self::addFormatVersion( 2, array( + // Basic types + array( array( null ), 'a:1:{i:0;N;}' ), + array( array( true ), 'a:1:{i:0;b:1;}' ), + array( array( false ), 'a:1:{i:0;b:0;}' ), + array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ), + 'a:1:{i:0;b:1;}' ), + array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ), + 'a:1:{i:0;b:0;}' ), + array( array( 42 ), 'a:1:{i:0;i:42;}' ), + array( array( 42.5 ), 'a:1:{i:0;d:42.5;}' ), + array( array( 1e42 ), 'a:1:{i:0;d:1.0E+42;}' ), + array( array( 'foo' ), 'a:1:{i:0;s:3:"foo";}' ), + array( array( 'fóo' ), 'a:1:{i:0;s:4:"fóo";}' ), + + // Arrays and objects + array( array( array() ), 'a:1:{i:0;a:0:{}}' ), + array( array( array( 1 ) ), 'a:1:{i:0;a:1:{i:0;i:1;}}' ), + array( array( array( 'x' => 1 ) ), 'a:1:{i:0;a:1:{s:1:"x";i:1;}}' ), + array( array( array( 2 => 1 ) ), 'a:1:{i:0;a:1:{i:2;i:1;}}' ), + array( array( (object)array() ), 'a:1:{i:0;a:0:{}}' ), + array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), 'a:1:{i:0;a:1:{i:0;i:1;}}' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), 'a:1:{i:0;a:1:{i:0;i:1;}}' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), 'a:1:{i:0;a:1:{s:1:"x";i:1;}}' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), + 'a:1:{i:0;a:1:{s:1:"x";i:1;}}' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), 'a:1:{i:0;a:1:{i:0;i:1;}}' ), + array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), 'a:1:{i:0;a:2:{i:0;s:1:"a";i:1;s:1:"b";}}' ), + + // Content + array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), + 'a:1:{s:7:"content";s:3:"foo";}' ), + + // BC Subelements + array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), + 'a:1:{s:3:"foo";s:3:"foo";}' ), + ) ) + ); + } + + public function testCrossDomainMangling() { + $config = new HashConfig( array( 'MangleFlashPolicy' => false ) ); + $context = new RequestContext; + $context->setConfig( new MultiConfig( array( + $config, + $context->getConfig(), + ) ) ); + $main = new ApiMain( $context ); + $main->getResult()->addValue( null, null, '< Cross-Domain-Policy >' ); + + if ( !function_exists( 'wfOutputHandler' ) ) { + function wfOutputHandler( $s ) { + return $s; + } + } + + $printer = $main->createPrinterByName( 'php' ); + ob_start( 'wfOutputHandler' ); + $printer->initPrinter(); + $printer->execute(); + $printer->closePrinter(); + $ret = ob_get_clean(); + $this->assertSame( 'a:1:{i:0;s:23:"< Cross-Domain-Policy >";}', $ret ); + + $config->set( 'MangleFlashPolicy', true ); + $printer = $main->createPrinterByName( 'php' ); + ob_start( 'wfOutputHandler' ); + try { + $printer->initPrinter(); + $printer->execute(); + $printer->closePrinter(); + ob_end_clean(); + $this->fail( 'Expected exception not thrown' ); + } catch ( UsageException $ex ) { + ob_end_clean(); + $this->assertSame( + 'This response cannot be represented using format=php. See https://bugzilla.wikimedia.org/show_bug.cgi?id=66776', + $ex->getMessage(), + 'Expected exception' + ); + } + } + } diff --git a/tests/phpunit/includes/api/format/ApiFormatTestBase.php b/tests/phpunit/includes/api/format/ApiFormatTestBase.php index 5f6d53ce..cabf62b1 100644 --- a/tests/phpunit/includes/api/format/ApiFormatTestBase.php +++ b/tests/phpunit/includes/api/format/ApiFormatTestBase.php @@ -1,32 +1,64 @@ <?php -abstract class ApiFormatTestBase extends ApiTestCase { +abstract class ApiFormatTestBase extends MediaWikiTestCase { /** - * @param string $format - * @param array $params - * @param array $data - * - * @return string + * Name of the formatter being tested + * @var string */ - protected function apiRequest( $format, $params, $data = null ) { - $data = parent::doApiRequest( $params, $data, true ); - - /** @var ApiMain $module */ - $module = $data[3]; + protected $printerName; - $printer = $module->createPrinterByName( $format ); - $printer->setUnescapeAmps( false ); + /** + * Return general data to be encoded for testing + * @return array See self::testGeneralEncoding + * @throws Exception + */ + public static function provideGeneralEncoding() { + throw new Exception( 'Subclass must implement ' . __METHOD__ ); + } - $printer->initPrinter( false ); + /** + * Get the formatter output for the given input data + * @param array $params Query parameters + * @param array $data Data to encode + * @param string $class Printer class to use instead of the normal one + * @return string + * @throws Exception + */ + protected function encodeData( array $params, array $data, $class = null ) { + $context = new RequestContext; + $context->setRequest( new FauxRequest( $params, true ) ); + $main = new ApiMain( $context ); + if ( $class !== null ) { + $main->getModuleManager()->addModule( $this->printerName, 'format', $class ); + } + $result = $main->getResult(); + $result->addArrayType( null, 'default' ); + foreach ( $data as $k => $v ) { + $result->addValue( null, $k, $v ); + } - ob_start(); + $printer = $main->createPrinterByName( $this->printerName ); + $printer->initPrinter(); $printer->execute(); - $out = ob_get_clean(); - - $printer->closePrinter(); + ob_start(); + try { + $printer->closePrinter(); + return ob_get_clean(); + } catch ( Exception $ex ) { + ob_end_clean(); + throw $ex; + } + } - return $out; + /** + * @dataProvider provideGeneralEncoding + */ + public function testGeneralEncoding( array $data, $expect, array $params = array() ) { + if ( isset( $params['SKIP'] ) ) { + $this->markTestSkipped( $expect ); + } + $this->assertSame( $expect, $this->encodeData( $params, $data ) ); } } diff --git a/tests/phpunit/includes/api/format/ApiFormatTxtTest.php b/tests/phpunit/includes/api/format/ApiFormatTxtTest.php new file mode 100644 index 00000000..b0a2a960 --- /dev/null +++ b/tests/phpunit/includes/api/format/ApiFormatTxtTest.php @@ -0,0 +1,55 @@ +<?php + +/** + * @group API + * @covers ApiFormatTxt + */ +class ApiFormatTxtTest extends ApiFormatTestBase { + + protected $printerName = 'txt'; + + public static function provideGeneralEncoding() { + $warning = "\n [warnings] => Array\n (\n [txt] => Array\n (\n" . + " [*] => format=txt has been deprecated. Please use format=json instead.\n" . + " )\n\n )\n"; + + return array( + // Basic types + array( array( null ), "Array\n({$warning}\n [0] => \n)\n" ), + array( array( true ), "Array\n({$warning}\n [0] => \n)\n" ), + array( array( false ), "Array\n({$warning}\n)\n" ), + array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ), + "Array\n({$warning}\n [0] => 1\n)\n" ), + array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ), + "Array\n({$warning}\n [0] => \n)\n" ), + array( array( 42 ), "Array\n({$warning}\n [0] => 42\n)\n" ), + array( array( 42.5 ), "Array\n({$warning}\n [0] => 42.5\n)\n" ), + array( array( 1e42 ), "Array\n({$warning}\n [0] => 1.0E+42\n)\n" ), + array( array( 'foo' ), "Array\n({$warning}\n [0] => foo\n)\n" ), + array( array( 'fóo' ), "Array\n({$warning}\n [0] => fóo\n)\n" ), + + // Arrays and objects + array( array( array() ), "Array\n({$warning}\n [0] => Array\n (\n )\n\n)\n" ), + array( array( array( 1 ) ), "Array\n({$warning}\n [0] => Array\n (\n [0] => 1\n )\n\n)\n" ), + array( array( array( 'x' => 1 ) ), "Array\n({$warning}\n [0] => Array\n (\n [x] => 1\n )\n\n)\n" ), + array( array( array( 2 => 1 ) ), "Array\n({$warning}\n [0] => Array\n (\n [2] => 1\n )\n\n)\n" ), + array( array( (object)array() ), "Array\n({$warning}\n [0] => Array\n (\n )\n\n)\n" ), + array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), "Array\n({$warning}\n [0] => Array\n (\n [0] => 1\n )\n\n)\n" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), "Array\n({$warning}\n [0] => Array\n (\n [0] => 1\n )\n\n)\n" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), "Array\n({$warning}\n [0] => Array\n (\n [x] => 1\n )\n\n)\n" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), + "Array\n({$warning}\n [0] => Array\n (\n [0] => Array\n (\n [key] => x\n [*] => 1\n )\n\n )\n\n)\n" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), "Array\n({$warning}\n [0] => Array\n (\n [x] => 1\n )\n\n)\n" ), + array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), "Array\n({$warning}\n [0] => Array\n (\n [0] => a\n [1] => b\n )\n\n)\n" ), + + // Content + array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), + "Array\n({$warning}\n [*] => foo\n)\n" ), + + // BC Subelements + array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), + "Array\n({$warning}\n [foo] => Array\n (\n [*] => foo\n )\n\n)\n" ), + ); + } + +} diff --git a/tests/phpunit/includes/api/format/ApiFormatWddxTest.php b/tests/phpunit/includes/api/format/ApiFormatWddxTest.php index d075f547..07111300 100644 --- a/tests/phpunit/includes/api/format/ApiFormatWddxTest.php +++ b/tests/phpunit/includes/api/format/ApiFormatWddxTest.php @@ -2,19 +2,79 @@ /** * @group API - * @group Database - * @group medium * @covers ApiFormatWddx */ class ApiFormatWddxTest extends ApiFormatTestBase { + protected $printerName = 'wddx'; + + public static function provideGeneralEncoding() { + if ( ApiFormatWddx::useSlowPrinter() ) { + return array( + array( array(), 'Fast Wddx printer is unavailable', array( 'SKIP' => true ) ) + ); + } + return self::provideEncoding(); + } + + public static function provideEncoding() { + $p = '<wddxPacket version=\'1.0\'><header/><data><struct><var name=\'warnings\'><struct><var name=\'wddx\'><struct><var name=\'*\'><string>format=wddx has been deprecated. Please use format=json instead.</string></var></struct></var></struct></var>'; + $s = '</struct></data></wddxPacket>'; + + return array( + // Basic types + array( array( null ), "{$p}<var name='0'><null/></var>{$s}" ), + array( array( true ), "{$p}<var name='0'><string></string></var>{$s}" ), + array( array( false ), "{$p}{$s}" ), + array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ), + "{$p}<var name='0'><boolean value='true'/></var>{$s}" ), + array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ), + "{$p}<var name='0'><boolean value='false'/></var>{$s}" ), + array( array( 42 ), "{$p}<var name='0'><number>42</number></var>{$s}" ), + array( array( 42.5 ), "{$p}<var name='0'><number>42.5</number></var>{$s}" ), + array( array( 1e42 ), "{$p}<var name='0'><number>1.0E+42</number></var>{$s}" ), + array( array( 'foo' ), "{$p}<var name='0'><string>foo</string></var>{$s}" ), + array( array( 'fóo' ), "{$p}<var name='0'><string>fóo</string></var>{$s}" ), + + // Arrays and objects + array( array( array() ), "{$p}<var name='0'><array length='0'></array></var>{$s}" ), + array( array( array( 1 ) ), "{$p}<var name='0'><array length='1'><number>1</number></array></var>{$s}" ), + array( array( array( 'x' => 1 ) ), "{$p}<var name='0'><struct><var name='x'><number>1</number></var></struct></var>{$s}" ), + array( array( array( 2 => 1 ) ), "{$p}<var name='0'><struct><var name='2'><number>1</number></var></struct></var>{$s}" ), + array( array( (object)array() ), "{$p}<var name='0'><struct></struct></var>{$s}" ), + array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), "{$p}<var name='0'><struct><var name='0'><number>1</number></var></struct></var>{$s}" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), "{$p}<var name='0'><array length='1'><number>1</number></array></var>{$s}" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), "{$p}<var name='0'><struct><var name='x'><number>1</number></var></struct></var>{$s}" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), + "{$p}<var name='0'><array length='1'><struct><var name='key'><string>x</string></var><var name='*'><number>1</number></var></struct></array></var>{$s}" ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), "{$p}<var name='0'><struct><var name='x'><number>1</number></var></struct></var>{$s}" ), + array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), "{$p}<var name='0'><array length='2'><string>a</string><string>b</string></array></var>{$s}" ), + + // Content + array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), + "{$p}<var name='*'><string>foo</string></var>{$s}" ), + + // BC Subelements + array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), + "{$p}<var name='foo'><struct><var name='*'><string>foo</string></var></struct></var>{$s}" ), + ); + } + /** - * @requires function wddx_deserialize + * @dataProvider provideEncoding */ - public function testValidSyntax( ) { - $data = $this->apiRequest( 'wddx', array( 'action' => 'query', 'meta' => 'siteinfo' ) ); + public function testSlowEncoding( array $data, $expect, array $params = array() ) { + // Adjust expectation for differences between fast and slow printers. + $expect = str_replace( '\'', '"', $expect ); + $expect = str_replace( '/>', ' />', $expect ); + $expect = '<?xml version="1.0"?>' . $expect; + + $this->assertSame( $expect, $this->encodeData( $params, $data, 'ApiFormatWddxTest_SlowWddx' ) ); + } +} - $this->assertInternalType( 'array', wddx_deserialize( $data ) ); - $this->assertGreaterThan( 0, count( (array)$data ) ); +class ApiFormatWddxTest_SlowWddx extends ApiFormatWddx { + public static function useSlowPrinter() { + return true; } } diff --git a/tests/phpunit/includes/api/format/ApiFormatXmlTest.php b/tests/phpunit/includes/api/format/ApiFormatXmlTest.php new file mode 100644 index 00000000..7babaedb --- /dev/null +++ b/tests/phpunit/includes/api/format/ApiFormatXmlTest.php @@ -0,0 +1,119 @@ +<?php + +/** + * @group API + * @group Database + * @covers ApiFormatXml + */ +class ApiFormatXmlTest extends ApiFormatTestBase { + + protected $printerName = 'xml'; + + public static function setUpBeforeClass() { + parent::setUpBeforeClass(); + $page = WikiPage::factory( Title::newFromText( 'MediaWiki:ApiFormatXmlTest.xsl' ) ); + $page->doEditContent( new WikitextContent( + '<?xml version="1.0"?><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" />' + ), 'Summary' ); + $page = WikiPage::factory( Title::newFromText( 'MediaWiki:ApiFormatXmlTest' ) ); + $page->doEditContent( new WikitextContent( 'Bogus' ), 'Summary' ); + $page = WikiPage::factory( Title::newFromText( 'ApiFormatXmlTest' ) ); + $page->doEditContent( new WikitextContent( 'Bogus' ), 'Summary' ); + } + + public static function provideGeneralEncoding() { + return array( + // Basic types + array( array( null, 'a' => null ), '<?xml version="1.0"?><api><_v _idx="0" /></api>' ), + array( array( true, 'a' => true ), '<?xml version="1.0"?><api a=""><_v _idx="0">true</_v></api>' ), + array( array( false, 'a' => false ), '<?xml version="1.0"?><api><_v _idx="0">false</_v></api>' ), + array( array( true, 'a' => true, ApiResult::META_BC_BOOLS => array( 0, 'a' ) ), + '<?xml version="1.0"?><api a=""><_v _idx="0">1</_v></api>' ), + array( array( false, 'a' => false, ApiResult::META_BC_BOOLS => array( 0, 'a' ) ), + '<?xml version="1.0"?><api><_v _idx="0"></_v></api>' ), + array( array( 42, 'a' => 42 ), '<?xml version="1.0"?><api a="42"><_v _idx="0">42</_v></api>' ), + array( array( 42.5, 'a' => 42.5 ), '<?xml version="1.0"?><api a="42.5"><_v _idx="0">42.5</_v></api>' ), + array( array( 1e42, 'a' => 1e42 ), '<?xml version="1.0"?><api a="1.0E+42"><_v _idx="0">1.0E+42</_v></api>' ), + array( array( 'foo', 'a' => 'foo' ), '<?xml version="1.0"?><api a="foo"><_v _idx="0">foo</_v></api>' ), + array( array( 'fóo', 'a' => 'fóo' ), '<?xml version="1.0"?><api a="fóo"><_v _idx="0">fóo</_v></api>' ), + + // Arrays and objects + array( array( array() ), '<?xml version="1.0"?><api><_v /></api>' ), + array( array( array( 'x' => 1 ) ), '<?xml version="1.0"?><api><_v x="1" /></api>' ), + array( array( array( 2 => 1 ) ), '<?xml version="1.0"?><api><_v><_v _idx="2">1</_v></_v></api>' ), + array( array( (object)array() ), '<?xml version="1.0"?><api><_v /></api>' ), + array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), '<?xml version="1.0"?><api><_v><_v _idx="0">1</_v></_v></api>' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), '<?xml version="1.0"?><api><_v><_v>1</_v></_v></api>' ), + array( array( array( 'x' => 1, 'y' => array( 'z' => 1 ), ApiResult::META_TYPE => 'kvp' ) ), + '<?xml version="1.0"?><api><_v><_v _name="x" xml:space="preserve">1</_v><_v _name="y"><z xml:space="preserve">1</z></_v></_v></api>' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp', ApiResult::META_INDEXED_TAG_NAME => 'i', ApiResult::META_KVP_KEY_NAME => 'key' ) ), + '<?xml version="1.0"?><api><_v><i key="x" xml:space="preserve">1</i></_v></api>' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), + '<?xml version="1.0"?><api><_v><_v key="x" xml:space="preserve">1</_v></_v></api>' ), + array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), '<?xml version="1.0"?><api><_v x="1" /></api>' ), + array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), '<?xml version="1.0"?><api><_v><_v _idx="0">a</_v><_v _idx="1">b</_v></_v></api>' ), + + // Content + array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), + '<?xml version="1.0"?><api xml:space="preserve">foo</api>' ), + + // Specified element name + array( array( 'foo', 'bar', ApiResult::META_INDEXED_TAG_NAME => 'itn' ), + '<?xml version="1.0"?><api><itn>foo</itn><itn>bar</itn></api>' ), + + // Subelements + array( array( 'a' => 1, 's' => 1, '_subelements' => array( 's' ) ), + '<?xml version="1.0"?><api a="1"><s xml:space="preserve">1</s></api>' ), + + // Content and subelement + array( array( 'a' => 1, 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), + '<?xml version="1.0"?><api a="1" xml:space="preserve">foo</api>' ), + array( array( 's' => array(), 'content' => 'foo', ApiResult::META_CONTENT => 'content' ), + '<?xml version="1.0"?><api><s /><content xml:space="preserve">foo</content></api>' ), + array( + array( + 's' => 1, + 'content' => 'foo', + ApiResult::META_CONTENT => 'content', + ApiResult::META_SUBELEMENTS => array( 's' ) + ), + '<?xml version="1.0"?><api><s xml:space="preserve">1</s><content xml:space="preserve">foo</content></api>' + ), + + // BC Subelements + array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), + '<?xml version="1.0"?><api><foo xml:space="preserve">foo</foo></api>' ), + + // Name mangling + array( array( 'foo.bar' => 1 ), '<?xml version="1.0"?><api foo.bar="1" />' ), + array( array( '' => 1 ), '<?xml version="1.0"?><api _="1" />' ), + array( array( 'foo bar' => 1 ), '<?xml version="1.0"?><api _foo.20.bar="1" />' ), + array( array( 'foo:bar' => 1 ), '<?xml version="1.0"?><api _foo.3A.bar="1" />' ), + array( array( 'foo%.bar' => 1 ), '<?xml version="1.0"?><api _foo.25..2E.bar="1" />' ), + array( array( '4foo' => 1, 'foo4' => 1 ), '<?xml version="1.0"?><api _4foo="1" foo4="1" />' ), + array( array( "foo\xe3\x80\x80bar" => 1 ), '<?xml version="1.0"?><api _foo.3000.bar="1" />' ), + array( array( 'foo:bar' => 1, ApiResult::META_PRESERVE_KEYS => array( 'foo:bar' ) ), + '<?xml version="1.0"?><api foo:bar="1" />' ), + array( array( 'a', 'b', ApiResult::META_INDEXED_TAG_NAME => 'foo bar' ), + '<?xml version="1.0"?><api><_foo.20.bar>a</_foo.20.bar><_foo.20.bar>b</_foo.20.bar></api>' ), + + // includenamespace param + array( array( 'x' => 'foo' ), '<?xml version="1.0"?><api x="foo" xmlns="http://www.mediawiki.org/xml/api/" />', + array( 'includexmlnamespace' => 1 ) ), + + // xslt param + array( array(), '<?xml version="1.0"?><api><warnings><xml xml:space="preserve">Invalid or non-existent stylesheet specified</xml></warnings></api>', + array( 'xslt' => 'DoesNotExist' ) ), + array( array(), '<?xml version="1.0"?><api><warnings><xml xml:space="preserve">Stylesheet should be in the MediaWiki namespace.</xml></warnings></api>', + array( 'xslt' => 'ApiFormatXmlTest' ) ), + array( array(), '<?xml version="1.0"?><api><warnings><xml xml:space="preserve">Stylesheet should have .xsl extension.</xml></warnings></api>', + array( 'xslt' => 'MediaWiki:ApiFormatXmlTest' ) ), + array( array(), + '<?xml version="1.0"?><?xml-stylesheet href="' . + htmlspecialchars( Title::newFromText( 'MediaWiki:ApiFormatXmlTest.xsl' )->getLocalURL( 'action=raw' ) ) . + '" type="text/xsl" ?><api />', + array( 'xslt' => 'MediaWiki:ApiFormatXmlTest.xsl' ) ), + ); + } + +} diff --git a/tests/phpunit/includes/api/query/ApiQueryBasicTest.php b/tests/phpunit/includes/api/query/ApiQueryBasicTest.php index e486c4f4..fa0e4cb5 100644 --- a/tests/phpunit/includes/api/query/ApiQueryBasicTest.php +++ b/tests/phpunit/includes/api/query/ApiQueryBasicTest.php @@ -23,8 +23,6 @@ * @file */ -require_once 'ApiQueryTestBase.php'; - /** * These tests validate basic functionality of the api query module * diff --git a/tests/phpunit/includes/api/query/ApiQueryContinue2Test.php b/tests/phpunit/includes/api/query/ApiQueryContinue2Test.php index 347cd6f8..cd735223 100644 --- a/tests/phpunit/includes/api/query/ApiQueryContinue2Test.php +++ b/tests/phpunit/includes/api/query/ApiQueryContinue2Test.php @@ -18,8 +18,6 @@ * http://www.gnu.org/copyleft/gpl.html */ -require_once 'ApiQueryContinueTestBase.php'; - /** * @group API * @group Database @@ -62,7 +60,8 @@ class ApiQueryContinue2Test extends ApiQueryContinueTestBase { ); }; // generator + 1 prop + 1 list - $data = $this->query( $mk( 99, 99, true ), 1, 'g1p', false ); + $data = $this->query( $mk( 99, 99, true ), 1, 'g1p', false ) + + array( 'batchcomplete' => true ); $this->checkC( $data, $mk( 1, 1, true ), 6, 'g1p-11t' ); $this->checkC( $data, $mk( 2, 2, true ), 3, 'g1p-22t' ); $this->checkC( $data, $mk( 1, 1, false ), 6, 'g1p-11f' ); diff --git a/tests/phpunit/includes/api/query/ApiQueryContinueTest.php b/tests/phpunit/includes/api/query/ApiQueryContinueTest.php index 03797901..d441f4c4 100644 --- a/tests/phpunit/includes/api/query/ApiQueryContinueTest.php +++ b/tests/phpunit/includes/api/query/ApiQueryContinueTest.php @@ -18,8 +18,6 @@ * http://www.gnu.org/copyleft/gpl.html */ -require_once 'ApiQueryContinueTestBase.php'; - /** * These tests validate the new continue functionality of the api query module by * doing multiple requests with varying parameters, merging the results, and checking @@ -68,7 +66,8 @@ class ApiQueryContinueTest extends ApiQueryContinueTestBase { 'aplimit' => "$l", ); }; - $data = $this->query( $mk( 99 ), 1, '1L', false ); + $data = $this->query( $mk( 99 ), 1, '1L', false ) + + array( 'batchcomplete' => true ); // 1 list $this->checkC( $data, $mk( 1 ), 5, '1L-1' ); @@ -95,7 +94,8 @@ class ApiQueryContinueTest extends ApiQueryContinueTestBase { ); }; // 2 lists - $data = $this->query( $mk( 99, 99 ), 1, '2L', false ); + $data = $this->query( $mk( 99, 99 ), 1, '2L', false ) + + array( 'batchcomplete' => true ); $this->checkC( $data, $mk( 1, 1 ), 5, '2L-11' ); $this->checkC( $data, $mk( 2, 2 ), 3, '2L-22' ); $this->checkC( $data, $mk( 3, 3 ), 2, '2L-33' ); @@ -119,7 +119,8 @@ class ApiQueryContinueTest extends ApiQueryContinueTestBase { ); }; // generator + 1 prop - $data = $this->query( $mk( 99, 99 ), 1, 'G1P', false ); + $data = $this->query( $mk( 99, 99 ), 1, 'G1P', false ) + + array( 'batchcomplete' => true ); $this->checkC( $data, $mk( 1, 1 ), 11, 'G1P-11' ); $this->checkC( $data, $mk( 2, 2 ), 6, 'G1P-22' ); $this->checkC( $data, $mk( 3, 3 ), 4, 'G1P-33' ); @@ -144,7 +145,8 @@ class ApiQueryContinueTest extends ApiQueryContinueTestBase { ); }; // generator + 2 props - $data = $this->query( $mk( 99, 99, 99 ), 1, 'G2P', false ); + $data = $this->query( $mk( 99, 99, 99 ), 1, 'G2P', false ) + + array( 'batchcomplete' => true ); $this->checkC( $data, $mk( 1, 1, 1 ), 16, 'G2P-111' ); $this->checkC( $data, $mk( 2, 2, 2 ), 9, 'G2P-222' ); $this->checkC( $data, $mk( 3, 3, 3 ), 6, 'G2P-333' ); @@ -177,7 +179,8 @@ class ApiQueryContinueTest extends ApiQueryContinueTestBase { ); }; // generator + 1 prop + 1 list - $data = $this->query( $mk( 99, 99, 99 ), 1, 'G1P1L', false ); + $data = $this->query( $mk( 99, 99, 99 ), 1, 'G1P1L', false ) + + array( 'batchcomplete' => true ); $this->checkC( $data, $mk( 1, 1, 1 ), 11, 'G1P1L-111' ); $this->checkC( $data, $mk( 2, 2, 2 ), 6, 'G1P1L-222' ); $this->checkC( $data, $mk( 3, 3, 3 ), 4, 'G1P1L-333' ); @@ -214,7 +217,8 @@ class ApiQueryContinueTest extends ApiQueryContinueTestBase { ); }; // generator + 1 prop + 1 list - $data = $this->query( $mk( 99, 99, 99, 99, 99 ), 1, 'G2P2L1M', false ); + $data = $this->query( $mk( 99, 99, 99, 99, 99 ), 1, 'G2P2L1M', false ) + + array( 'batchcomplete' => true ); $this->checkC( $data, $mk( 1, 1, 1, 1, 1 ), 16, 'G2P2L1M-11111' ); $this->checkC( $data, $mk( 2, 2, 2, 2, 2 ), 9, 'G2P2L1M-22222' ); $this->checkC( $data, $mk( 3, 3, 3, 3, 3 ), 6, 'G2P2L1M-33333' ); @@ -244,7 +248,8 @@ class ApiQueryContinueTest extends ApiQueryContinueTestBase { ); }; // generator + 1 prop - $data = $this->query( $mk( 99, true, 99, true ), 1, 'G=P', false ); + $data = $this->query( $mk( 99, true, 99, true ), 1, 'G=P', false ) + + array( 'batchcomplete' => true ); $this->checkC( $data, $mk( 1, true, 1, true ), 4, 'G=P-1t1t' ); $this->checkC( $data, $mk( 2, true, 2, true ), 2, 'G=P-2t2t' ); @@ -290,7 +295,8 @@ class ApiQueryContinueTest extends ApiQueryContinueTestBase { ); }; // generator + 1 list - $data = $this->query( $mk( 99, true, 99, true ), 1, 'G=L', false ); + $data = $this->query( $mk( 99, true, 99, true ), 1, 'G=L', false ) + + array( 'batchcomplete' => true ); $this->checkC( $data, $mk( 1, true, 1, true ), 5, 'G=L-1t1t' ); $this->checkC( $data, $mk( 2, true, 2, true ), 3, 'G=L-2t2t' ); diff --git a/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php b/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php index bce62685..ce2f70de 100644 --- a/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php +++ b/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php @@ -21,9 +21,6 @@ * * @file */ - -require_once 'ApiQueryTestBase.php'; - abstract class ApiQueryContinueTestBase extends ApiQueryTestBase { /** @@ -62,6 +59,8 @@ abstract class ApiQueryContinueTestBase extends ApiQueryTestBase { } if ( $useContinue && !isset( $params['continue'] ) ) { $params['continue'] = ''; + } else { + $params['rawcontinue'] = '1'; } $count = 0; $result = array(); diff --git a/tests/phpunit/includes/api/query/ApiQueryTest.php b/tests/phpunit/includes/api/query/ApiQueryTest.php index bba22c77..5f061b50 100644 --- a/tests/phpunit/includes/api/query/ApiQueryTest.php +++ b/tests/phpunit/includes/api/query/ApiQueryTest.php @@ -7,32 +7,21 @@ * @covers ApiQuery */ class ApiQueryTest extends ApiTestCase { - /** - * @var array Storage for $wgHooks - */ - protected $hooks; - protected function setUp() { - global $wgHooks; - parent::setUp(); $this->doLogin(); - // Setup en: as interwiki prefix - $this->hooks = $wgHooks; - $wgHooks['InterwikiLoadPrefix'][] = function ( $prefix, &$data ) { - if ( $prefix == 'apiquerytestiw' ) { - $data = array( 'iw_url' => 'wikipedia' ); - } - return false; - }; - } - - protected function tearDown() { - global $wgHooks; - $wgHooks = $this->hooks; - - parent::tearDown(); + // Setup apiquerytestiw: as interwiki prefix + $this->setMwGlobals( 'wgHooks', array( + 'InterwikiLoadPrefix' => array( + function ( $prefix, &$data ) { + if ( $prefix == 'apiquerytestiw' ) { + $data = array( 'iw_url' => 'wikipedia' ); + } + return false; + } + ) + ) ); } public function testTitlesGetNormalized() { @@ -127,4 +116,27 @@ class ApiQueryTest extends ApiTestCase { array( 'apiquerytestiw:foo', NS_MAIN, null, true ), ); } + + /** + * Test if all classes in the query module manager exists + */ + public function testClassNamesInModuleManager() { + global $wgAutoloadLocalClasses, $wgAutoloadClasses; + + // wgAutoloadLocalClasses has precedence, just like in includes/AutoLoader.php + $classes = $wgAutoloadLocalClasses + $wgAutoloadClasses; + + $api = new ApiMain( + new FauxRequest( array( 'action' => 'query', 'meta' => 'siteinfo' ) ) + ); + $queryApi = new ApiQuery( $api, 'query' ); + $modules = $queryApi->getModuleManager()->getNamesWithClasses(); + foreach( $modules as $name => $class ) { + $this->assertArrayHasKey( + $class, + $classes, + 'Class ' . $class . ' for api module ' . $name . ' not in autoloader (with exact case)' + ); + } + } } diff --git a/tests/phpunit/includes/api/query/ApiQueryTestBase.php b/tests/phpunit/includes/api/query/ApiQueryTestBase.php index 56c15b23..dabf72e0 100644 --- a/tests/phpunit/includes/api/query/ApiQueryTestBase.php +++ b/tests/phpunit/includes/api/query/ApiQueryTestBase.php @@ -88,19 +88,26 @@ STR; /** * Checks that the request's result matches the expected results. * @param array $values Array is a two element array( request, expected_results ) - * @throws Exception + * @param array $session + * @param bool $appendModule + * @param User $user */ - protected function check( $values ) { + protected function check( $values, array $session = null, + $appendModule = false, User $user = null + ) { list( $req, $exp ) = $this->validateRequestExpectedPair( $values ); if ( !array_key_exists( 'action', $req ) ) { $req['action'] = 'query'; } + if ( !array_key_exists( 'continue', $req ) ) { + $req['rawcontinue'] = '1'; + } foreach ( $req as &$val ) { if ( is_array( $val ) ) { $val = implode( '|', array_unique( $val ) ); } } - $result = $this->doApiRequest( $req ); + $result = $this->doApiRequest( $req, $session, $appendModule, $user ); $this->assertResult( array( 'query' => $exp ), $result[0], $req ); } @@ -113,9 +120,16 @@ STR; if ( is_array( $message ) ) { $message = http_build_query( $message ); } + + // FIXME: once we migrate to phpunit 4.1+, hardcode ComparisonFailure exception use + $compEx = 'SebastianBergmann\Comparator\ComparisonFailure'; + if ( !class_exists( $compEx ) ) { + $compEx = 'PHPUnit_Framework_ComparisonFailure'; + } + throw new PHPUnit_Framework_ExpectationFailedException( $e->getMessage() . "\nRequest: $message", - new PHPUnit_Framework_ComparisonFailure( + new $compEx( $exp, $result, print_r( $exp, true ), |