diff options
Diffstat (limited to 'tests/phpunit/includes')
145 files changed, 7889 insertions, 1535 deletions
diff --git a/tests/phpunit/includes/BlockTest.php b/tests/phpunit/includes/BlockTest.php index 19741621..7b0de866 100644 --- a/tests/phpunit/includes/BlockTest.php +++ b/tests/phpunit/includes/BlockTest.php @@ -38,9 +38,13 @@ class BlockTest extends MediaWikiLangTestCase { $oldBlock->delete(); } - $this->block = new Block( 'UTBlockee', $user->getID(), 0, - 'Parce que', 0, false, time() + 100500 + $blockOptions = array( + 'address' => 'UTBlockee', + 'user' => $user->getID(), + 'reason' => 'Parce que', + 'expiry' => time() + 100500, ); + $this->block = new Block( $blockOptions ); $this->madeAt = wfTimestamp( TS_MW ); $this->block->insert(); @@ -151,22 +155,19 @@ class BlockTest extends MediaWikiLangTestCase { ); // Foreign perspective (blockee not on current wiki)... - $block = new Block( - /* $address */ $username, - /* $user */ 14146, - /* $by */ 0, - /* $reason */ 'crosswiki block...', - /* $timestamp */ wfTimestampNow(), - /* $auto */ false, - /* $expiry */ $this->db->getInfinity(), - /* anonOnly */ false, - /* $createAccount */ true, - /* $enableAutoblock */ true, - /* $hideName (ipb_deleted) */ true, - /* $blockEmail */ true, - /* $allowUsertalk */ false, - /* $byName */ 'MetaWikiUser' + $blockOptions = array( + 'address' => $username, + 'user' => 14146, + 'reason' => 'crosswiki block...', + 'timestamp' => wfTimestampNow(), + 'expiry' => $this->db->getInfinity(), + 'createAccount' => true, + 'enableAutoblock' => true, + 'hideName' => true, + 'blockEmail' => true, + 'byText' => 'MetaWikiUser', ); + $block = new Block( $blockOptions ); $block->insert(); // Reload block from DB @@ -208,22 +209,19 @@ class BlockTest extends MediaWikiLangTestCase { $this->db->update( 'user', array( 'user_id' => 14146 ), array( 'user_id' => $user->getId() ) ); // Foreign perspective (blockee not on current wiki)... - $block = new Block( - /* $address */ 'UserOnForeignWiki', - /* $user */ 14146, - /* $by */ 0, - /* $reason */ 'crosswiki block...', - /* $timestamp */ wfTimestampNow(), - /* $auto */ false, - /* $expiry */ $this->db->getInfinity(), - /* anonOnly */ false, - /* $createAccount */ true, - /* $enableAutoblock */ true, - /* $hideName (ipb_deleted) */ true, - /* $blockEmail */ true, - /* $allowUsertalk */ false, - /* $byName */ 'MetaWikiUser' + $blockOptions = array( + 'address' => 'UserOnForeignWiki', + 'user' => 14146, + 'reason' => 'crosswiki block...', + 'timestamp' => wfTimestampNow(), + 'expiry' => $this->db->getInfinity(), + 'createAccount' => true, + 'enableAutoblock' => true, + 'hideName' => true, + 'blockEmail' => true, + 'byText' => 'MetaWikiUser', ); + $block = new Block( $blockOptions ); $res = $block->insert( $this->db ); $this->assertTrue( (bool)$res['id'], 'Block succeeded' ); @@ -367,4 +365,56 @@ class BlockTest extends MediaWikiLangTestCase { $block = Block::chooseBlock( $xffblocks, $list ); $this->assertEquals( $exResult, $block->mReason, 'Correct block type for XFF header ' . $xff ); } + + public function testDeprecatedConstructor() { + $this->hideDeprecated( 'Block::__construct with multiple arguments' ); + $username = 'UnthinkablySecretRandomUsername'; + $reason = 'being irrational'; + + # Set up the target + $u = User::newFromName( $username ); + if ( $u->getID() == 0 ) { + $u->setPassword( 'TotallyObvious' ); + $u->addToDatabase(); + } + unset( $u ); + + # Make sure the user isn't blocked + $this->assertNull( + Block::newFromTarget( $username ), + "$username should not be blocked" + ); + + # Perform the block + $block = new Block( + /* address */ $username, + /* user */ 0, + /* by */ 0, + /* reason */ $reason, + /* timestamp */ 0, + /* auto */ false, + /* expiry */ 0 + ); + $block->insert(); + + # Check target + $this->assertEquals( + $block->getTarget()->getName(), + $username, + "Target should be set properly" + ); + + # Check supplied parameter + $this->assertEquals( + $block->mReason, + $reason, + "Reason should be non-default" + ); + + # Check default parameter + $this->assertFalse( + (bool)$block->prevents( 'createaccount' ), + "Account creation should not be blocked by default" + ); + } } diff --git a/tests/phpunit/includes/ConsecutiveParametersMatcher.php b/tests/phpunit/includes/ConsecutiveParametersMatcher.php new file mode 100644 index 00000000..adf74bb4 --- /dev/null +++ b/tests/phpunit/includes/ConsecutiveParametersMatcher.php @@ -0,0 +1,123 @@ +<?php +/* + * This file is part of the PHPUnit_MockObject package. + * + * (c) Sebastian Bergmann <sebastian@phpunit.de> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Invocation matcher which looks for sets of specific parameters in the invocations. + * + * Checks the parameters of the incoming invocations, the parameter list is + * checked against the defined constraints in $parameters. If the constraint + * is met it will return true in matches(). + * + * It takes a list of match groups and and increases a call index after each invocation. + * So the first invocation uses the first group of constraints, the second the next and so on. + */ +class PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters extends PHPUnit_Framework_MockObject_Matcher_StatelessInvocation +{ + /** + * @var array + */ + private $_parameterGroups = array(); + + /** + * @var array + */ + private $_invocations = array(); + + /** + * @param array $parameterGroups + */ + public function __construct(array $parameterGroups) + { + foreach ($parameterGroups as $index => $parameters) { + foreach ($parameters as $parameter) { + if (!($parameter instanceof \PHPUnit_Framework_Constraint)) { + $parameter = new \PHPUnit_Framework_Constraint_IsEqual($parameter); + } + $this->_parameterGroups[$index][] = $parameter; + } + } + } + + /** + * @return string + */ + public function toString() + { + $text = 'with consecutive parameters'; + + return $text; + } + + /** + * @param PHPUnit_Framework_MockObject_Invocation $invocation + * @return bool + */ + public function matches(PHPUnit_Framework_MockObject_Invocation $invocation) + { + $this->_invocations[] = $invocation; + $callIndex = count($this->_invocations) - 1; + $this->verifyInvocation($invocation, $callIndex); + + return false; + } + + public function verify() + { + foreach ($this->_invocations as $callIndex => $invocation) { + $this->verifyInvocation($invocation, $callIndex); + } + } + + /** + * Verify a single invocation + * + * @param PHPUnit_Framework_MockObject_Invocation $invocation + * @param int $callIndex + * @throws PHPUnit_Framework_ExpectationFailedException + */ + private function verifyInvocation(PHPUnit_Framework_MockObject_Invocation $invocation, $callIndex) + { + + if (isset($this->_parameterGroups[$callIndex])) { + $parameters = $this->_parameterGroups[$callIndex]; + } else { + // no parameter assertion for this call index + return; + } + + if ($invocation === null) { + throw new PHPUnit_Framework_ExpectationFailedException( + 'Mocked method does not exist.' + ); + } + + if (count($invocation->parameters) < count($parameters)) { + throw new PHPUnit_Framework_ExpectationFailedException( + sprintf( + 'Parameter count for invocation %s is too low.', + $invocation->toString() + ) + ); + } + + foreach ($parameters as $i => $parameter) { + $parameter->evaluate( + $invocation->parameters[$i], + sprintf( + 'Parameter %s for invocation #%d %s does not match expected ' . + 'value.', + $i, + $callIndex, + $invocation->toString() + ) + ); + } + } +} diff --git a/tests/phpunit/includes/EditPageTest.php b/tests/phpunit/includes/EditPageTest.php index 15778e40..27959b1d 100644 --- a/tests/phpunit/includes/EditPageTest.php +++ b/tests/phpunit/includes/EditPageTest.php @@ -11,6 +11,28 @@ */ class EditPageTest extends MediaWikiLangTestCase { + protected function setUp() { + global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang; + + parent::setUp(); + + $this->setMwGlobals( array( + 'wgExtraNamespaces' => $wgExtraNamespaces, + 'wgNamespaceContentModels' => $wgNamespaceContentModels, + 'wgContentHandlers' => $wgContentHandlers, + 'wgContLang' => $wgContLang, + ) ); + + $wgExtraNamespaces[12312] = 'Dummy'; + $wgExtraNamespaces[12313] = 'Dummy_talk'; + + $wgNamespaceContentModels[12312] = "testing"; + $wgContentHandlers["testing"] = 'DummyContentHandlerForTesting'; + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache + } + /** * @dataProvider provideExtractSectionTitle * @covers EditPage::extractSectionTitle @@ -499,4 +521,37 @@ hello $this->assertEdit( 'EditPageTest_testAutoMerge', null, 'Berta', $bertasEdit, $expectedCode, $expectedText, $message ); } + + /** + * @depends testAutoMerge + */ + public function testCheckDirectEditingDisallowed_forNonTextContent() { + $title = Title::newFromText( 'Dummy:NonTextPageForEditPage' ); + $page = WikiPage::factory( $title ); + + $article = new Article( $title ); + $article->getContext()->setTitle( $title ); + $ep = new EditPage( $article ); + $ep->setContextTitle( $title ); + + $user = $GLOBALS['wgUser']; + + $edit = array( + 'wpTextbox1' => serialize( 'non-text content' ), + 'wpEditToken' => $user->getEditToken(), + 'wpEdittime' => '', + 'wpStarttime' => wfTimestampNow() + ); + + $req = new FauxRequest( $edit, true ); + $ep->importFormData( $req ); + + $this->setExpectedException( + 'MWException', + 'This content model is not supported: testing' + ); + + $ep->internalAttemptSave( $result, false ); + } + } diff --git a/tests/phpunit/includes/ExtraParserTest.php b/tests/phpunit/includes/ExtraParserTest.php index 4a4130e0..7b60fb3f 100644 --- a/tests/phpunit/includes/ExtraParserTest.php +++ b/tests/phpunit/includes/ExtraParserTest.php @@ -2,6 +2,8 @@ /** * Parser-related tests that don't suit for parserTests.txt + * + * @group Database */ class ExtraParserTest extends MediaWikiTestCase { @@ -20,7 +22,6 @@ class ExtraParserTest extends MediaWikiTestCase { 'wgContLang' => $contLang, 'wgLang' => Language::factory( 'en' ), 'wgMemc' => new EmptyBagOStuff, - 'wgAlwaysUseTidy' => false, 'wgCleanSignatures' => true, ) ); diff --git a/tests/phpunit/includes/FauxRequestTest.php b/tests/phpunit/includes/FauxRequestTest.php index 745a5b42..07214b21 100644 --- a/tests/phpunit/includes/FauxRequestTest.php +++ b/tests/phpunit/includes/FauxRequestTest.php @@ -6,13 +6,46 @@ class FauxRequestTest extends MediaWikiTestCase { * @covers FauxRequest::getHeader */ public function testGetSetHeader() { - $value = 'test/test'; + $value = 'text/plain, text/html'; $request = new FauxRequest(); - $request->setHeader( 'Content-Type', $value ); + $request->setHeader( 'Accept', $value ); - $this->assertEquals( $request->getHeader( 'Content-Type' ), $value ); - $this->assertEquals( $request->getHeader( 'CONTENT-TYPE' ), $value ); - $this->assertEquals( $request->getHeader( 'content-type' ), $value ); + $this->assertEquals( $request->getHeader( 'Nonexistent' ), false ); + $this->assertEquals( $request->getHeader( 'Accept' ), $value ); + $this->assertEquals( $request->getHeader( 'ACCEPT' ), $value ); + $this->assertEquals( $request->getHeader( 'accept' ), $value ); + $this->assertEquals( + $request->getHeader( 'Accept', WebRequest::GETHEADER_LIST ), + array( 'text/plain', 'text/html' ) + ); + } + + /** + * @covers FauxRequest::getAllHeaders + */ + public function testGetAllHeaders() { + $_SERVER['HTTP_TEST'] = 'Example'; + + $request = new FauxRequest(); + + $this->assertEquals( + array(), + $request->getAllHeaders() + ); + } + + /** + * @covers FauxRequest::getHeader + */ + public function testGetHeader() { + $_SERVER['HTTP_TEST'] = 'Example'; + + $request = new FauxRequest(); + + $this->assertEquals( + false, + $request->getHeader( 'test' ) + ); } } diff --git a/tests/phpunit/includes/FauxResponseTest.php b/tests/phpunit/includes/FauxResponseTest.php index 4a974ba2..39a0effa 100644 --- a/tests/phpunit/includes/FauxResponseTest.php +++ b/tests/phpunit/includes/FauxResponseTest.php @@ -108,6 +108,13 @@ class FauxResponseTest extends MediaWikiTestCase { 'Third parameter overrides the HTTP/... header' ); + $this->response->statusHeader( 210 ); + $this->assertEquals( + 210, + $this->response->getStatusCode(), + 'Handle statusHeader method' + ); + $this->response->header( 'Location: http://localhost/', false, 206 ); $this->assertEquals( 206, diff --git a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php index 1e30273e..e89e36f6 100644 --- a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php +++ b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php @@ -666,9 +666,9 @@ class GlobalTest extends MediaWikiTestCase { public function testWfMkdirParents() { // Should not return true if file exists instead of directory $fname = $this->getNewTempFile(); - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $ok = wfMkdirParents( $fname ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); $this->assertFalse( $ok ); } @@ -687,6 +687,105 @@ class GlobalTest extends MediaWikiTestCase { $this->assertEquals( $expected, $actual, $description ); } + public function wfWikiID() { + $this->setMwGlobals( array( + 'wgDBname' => 'example', + 'wgDBprefix' => '', + ) ); + $this->assertEquals( + wfWikiID(), + 'example' + ); + + $this->setMwGlobals( array( + 'wgDBname' => 'example', + 'wgDBprefix' => 'mw_', + ) ); + $this->assertEquals( + wfWikiID(), + 'example-mw_' + ); + } + + public function testWfMemcKey() { + // Just assert the exact output so we can catch unintentional changes to key + // construction, which would effectively invalidate all existing cache. + + $this->setMwGlobals( array( + 'wgCachePrefix' => false, + 'wgDBname' => 'example', + 'wgDBprefix' => '', + ) ); + $this->assertEquals( + wfMemcKey( 'foo', '123', 'bar' ), + 'example:foo:123:bar' + ); + + $this->setMwGlobals( array( + 'wgCachePrefix' => false, + 'wgDBname' => 'example', + 'wgDBprefix' => 'mw_', + ) ); + $this->assertEquals( + wfMemcKey( 'foo', '123', 'bar' ), + 'example-mw_:foo:123:bar' + ); + + $this->setMwGlobals( array( + 'wgCachePrefix' => 'custom', + 'wgDBname' => 'example', + 'wgDBprefix' => 'mw_', + ) ); + $this->assertEquals( + wfMemcKey( 'foo', '123', 'bar' ), + 'custom:foo:123:bar' + ); + } + + public function testWfForeignMemcKey() { + $this->setMwGlobals( array( + 'wgCachePrefix' => false, + 'wgDBname' => 'example', + 'wgDBprefix' => '', + ) ); + $local = wfMemcKey( 'foo', 'bar' ); + + $this->setMwGlobals( array( + 'wgDBname' => 'other', + 'wgDBprefix' => 'mw_', + ) ); + $this->assertEquals( + wfForeignMemcKey( 'example', '', 'foo', 'bar' ), + $local, + 'Match output of wfMemcKey from local wiki' + ); + } + + public function testWfGlobalCacheKey() { + $this->setMwGlobals( array( + 'wgCachePrefix' => 'ignored', + 'wgDBname' => 'example', + 'wgDBprefix' => '' + ) ); + $one = wfGlobalCacheKey( 'some', 'thing' ); + $this->assertEquals( + $one, + 'global:some:thing' + ); + + $this->setMwGlobals( array( + 'wgDBname' => 'other', + 'wgDBprefix' => 'mw_' + ) ); + $two = wfGlobalCacheKey( 'some', 'thing' ); + + $this->assertEquals( + $one, + $two, + 'Not fragmented by wiki id' + ); + } + public static function provideWfShellWikiCmdList() { global $wgPhpCli; diff --git a/tests/phpunit/includes/GlobalFunctions/wfArrayPlus2dTest.php b/tests/phpunit/includes/GlobalFunctions/wfArrayPlus2dTest.php new file mode 100644 index 00000000..88875bb0 --- /dev/null +++ b/tests/phpunit/includes/GlobalFunctions/wfArrayPlus2dTest.php @@ -0,0 +1,94 @@ +<?php +/** + * @group GlobalFunctions + * @covers ::wfArrayPlus2d + */ +class WfArrayPlus2dTest extends MediaWikiTestCase { + /** + * @dataProvider provideArrays + */ + public function testWfArrayPlus2d( $baseArray, $newValues, $expected, $testName ) { + $this->assertEquals( + $expected, + wfArrayPlus2d( $baseArray, $newValues ), + $testName + ); + } + + /** + * Provider for testing wfArrayPlus2d + * + * @return array + */ + public static function provideArrays() { + return array( + // target array, new values array, expected result + array( + array( 0 => '1dArray' ), + array( 1 => '1dArray' ), + array( 0 => '1dArray', 1 => '1dArray' ), + "Test simple union of two arrays with different keys", + ), + array( + array( + 0 => array( 0 => '2dArray' ), + ), + array( + 0 => array( 1 => '2dArray' ), + ), + array( + 0 => array( 0 => '2dArray', 1 => '2dArray' ), + ), + "Test union of 2d arrays with different keys in the value array", + ), + array( + array( + 0 => array( 0 => '2dArray' ), + ), + array( + 0 => array( 0 => '1dArray' ), + ), + array( + 0 => array( 0 => '2dArray' ), + ), + "Test union of 2d arrays with same keys in the value array", + ), + array( + array( + 0 => array( 0 => array( 0 => '3dArray' ) ), + ), + array( + 0 => array( 0 => array( 1 => '2dArray' ) ), + ), + array( + 0 => array( 0 => array( 0 => '3dArray' ) ), + ), + "Test union of 3d array with different keys", + ), + array( + array( + 0 => array( 0 => array( 0 => '3dArray' ) ), + ), + array( + 0 => array( 1 => array( 0 => '2dArray' ) ), + ), + array( + 0 => array( 0 => array( 0 => '3dArray' ), 1 => array( 0 => '2dArray' ) ), + ), + "Test union of 3d array with different keys in the value array", + ), + array( + array( + 0 => array( 0 => array( 0 => '3dArray' ) ), + ), + array( + 0 => array( 0 => array( 0 => '2dArray' ) ), + ), + array( + 0 => array( 0 => array( 0 => '3dArray' ) ), + ), + "Test union of 3d array with same keys in the value array", + ), + ); + } +} diff --git a/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php b/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php index bea496c4..4ce51c6a 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php +++ b/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php @@ -21,6 +21,7 @@ class WfTimestampTest extends MediaWikiTestCase { array( -30281104, TS_MW, '19690115123456', 'Negative TS_UNIX to TS_MW' ), array( $t, TS_UNIX, 979562096, 'TS_UNIX to TS_UNIX' ), array( $t, TS_DB, '2001-01-15 12:34:56', 'TS_UNIX to TS_DB' ), + array( $t + .01, TS_MW, '20010115123456', 'TS_UNIX float to TS_MW' ), array( $t, TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_ISO_8601_BASIC to TS_DB' ), diff --git a/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php b/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php index d11668b7..d4df7b00 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php +++ b/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php @@ -112,6 +112,8 @@ class WfUrlencodeTest extends MediaWikiTestCase { ### Other tests // slash remain unchanged. %2F seems to break things array( '/', '/' ), + // T105265 + array( '~', '~' ), // Other 'funnies' chars array( '[]', '%5B%5D' ), diff --git a/tests/phpunit/includes/ImportLinkCacheIntegrationTest.php b/tests/phpunit/includes/ImportLinkCacheIntegrationTest.php new file mode 100644 index 00000000..1433b898 --- /dev/null +++ b/tests/phpunit/includes/ImportLinkCacheIntegrationTest.php @@ -0,0 +1,112 @@ +<?php +/** + * Integration test that checks import success and + * LinkCache integration. + * + * @group medium + * @group Database + * + * @author mwjames + */ +class ImportLinkCacheIntegrationTest extends MediaWikiTestCase { + + private $importStreamSource; + + protected function setUp() { + parent::setUp(); + + $file = dirname( __DIR__ ) . '/data/import/ImportLinkCacheIntegrationTest.xml'; + + $this->importStreamSource = ImportStreamSource::newFromFile( $file ); + + if ( !$this->importStreamSource->isGood() ) { + throw new Exception( "Import source for {$file} failed" ); + } + } + + public function testImportForImportSource() { + + $this->doImport( $this->importStreamSource ); + + // Imported title + $loremIpsum = Title::newFromText( 'Lorem ipsum' ); + + $this->assertSame( + $loremIpsum->getArticleID(), + $loremIpsum->getArticleID( Title::GAID_FOR_UPDATE ) + ); + + $categoryLoremIpsum = Title::newFromText( 'Category:Lorem ipsum' ); + + $this->assertSame( + $categoryLoremIpsum->getArticleID(), + $categoryLoremIpsum->getArticleID( Title::GAID_FOR_UPDATE ) + ); + + $page = new WikiPage( $loremIpsum ); + $page->doDeleteArticle( 'import test: delete page' ); + + $page = new WikiPage( $categoryLoremIpsum ); + $page->doDeleteArticle( 'import test: delete page' ); + } + + /** + * @depends testImportForImportSource + */ + public function testReImportForImportSource() { + + $this->doImport( $this->importStreamSource ); + + // ReImported title + $loremIpsum = Title::newFromText( 'Lorem ipsum' ); + + $this->assertSame( + $loremIpsum->getArticleID(), + $loremIpsum->getArticleID( Title::GAID_FOR_UPDATE ) + ); + + $categoryLoremIpsum = Title::newFromText( 'Category:Lorem ipsum' ); + + $this->assertSame( + $categoryLoremIpsum->getArticleID(), + $categoryLoremIpsum->getArticleID( Title::GAID_FOR_UPDATE ) + ); + } + + private function doImport( $importStreamSource ) { + + $importer = new WikiImporter( + $importStreamSource->value, + ConfigFactory::getDefaultInstance()->makeConfig( 'main' ) + ); + $importer->setDebug( true ); + + $reporter = new ImportReporter( + $importer, + false, + '', + false + ); + + $reporter->setContext( new RequestContext() ); + $reporter->open(); + $exception = false; + + try { + $importer->doImport(); + } catch ( Exception $e ) { + $exception = $e; + } + + $result = $reporter->close(); + + $this->assertFalse( + $exception + ); + + $this->assertTrue( + $result->isGood() + ); + } + +} diff --git a/tests/phpunit/includes/LinkFilterTest.php b/tests/phpunit/includes/LinkFilterTest.php index f2c9cb43..68081059 100644 --- a/tests/phpunit/includes/LinkFilterTest.php +++ b/tests/phpunit/includes/LinkFilterTest.php @@ -76,7 +76,7 @@ class LinkFilterTest extends MediaWikiLangTestCase { array( 'https://', '*.com', 'https://name:pass@secure.com/index.html' ), array( 'http://', 'name:pass@test.com', 'http://test.com' ), array( 'http://', 'test.com', 'http://name:pass@test.com' ), - array( 'http://', '*.test.com', 'http://a.b.c.test.com/dir/dir/file?a=6'), + array( 'http://', '*.test.com', 'http://a.b.c.test.com/dir/dir/file?a=6' ), array( null, 'http://*.test.com', 'http://www.test.com' ), array( 'mailto:', 'name@mail.test123.com', 'mailto:name@mail.test123.com' ), array( '', @@ -122,8 +122,8 @@ class LinkFilterTest extends MediaWikiLangTestCase { array( '', 'git://github.com/prwef/abc-def.git', 'git://github.com/prwef/abc-def.git' ), array( 'git://', 'github.com/', 'git://github.com/prwef/abc-def.git' ), array( 'git://', '*.github.com/', 'git://a.b.c.d.e.f.github.com/prwef/abc-def.git' ), - array( '', 'gopher://*.test.com/', 'gopher://gopher.test.com/0/v2/vstat'), - array( 'telnet://', '*.test.com', 'telnet://shell.test.com/~home/'), + array( '', 'gopher://*.test.com/', 'gopher://gopher.test.com/0/v2/vstat' ), + array( 'telnet://', '*.test.com', 'telnet://shell.test.com/~home/' ), // // The following only work in PHP >= 5.3.7, due to a bug in parse_url which eats @@ -243,10 +243,10 @@ class LinkFilterTest extends MediaWikiLangTestCase { array( 'http://*.test.*' ), array( 'http://*test.com' ), array( 'https://*' ), - array( '*://test.com'), + array( '*://test.com' ), array( 'mailto:name:pass@t*est.com' ), - array( 'http://*:888/'), - array( '*http://'), + array( 'http://*:888/' ), + array( '*http://' ), array( 'test.com/*/index' ), array( 'test.com/dir/index?arg=*' ), ); diff --git a/tests/phpunit/includes/LinkerTest.php b/tests/phpunit/includes/LinkerTest.php index 823c9330..a3efbb8d 100644 --- a/tests/phpunit/includes/LinkerTest.php +++ b/tests/phpunit/includes/LinkerTest.php @@ -93,12 +93,26 @@ class LinkerTest extends MediaWikiLangTestCase { * @covers Linker::formatAutocomments * @covers Linker::formatLinksInComment */ - public function testFormatComment( $expected, $comment, $title = false, $local = false ) { + public function testFormatComment( $expected, $comment, $title = false, $local = false, $wikiId = null ) { + $conf = new SiteConfiguration(); + $conf->settings = array( + 'wgServer' => array( + 'enwiki' => '//en.example.org', + 'dewiki' => '//de.example.org', + ), + 'wgArticlePath' => array( + 'enwiki' => '/w/$1', + 'dewiki' => '/w/$1', + ), + ); + $conf->suffixes = array( 'wiki' ); + $this->setMwGlobals( array( 'wgScript' => '/wiki/index.php', 'wgArticlePath' => '/wiki/$1', 'wgWellFormedXml' => true, 'wgCapitalLinks' => true, + 'wgConf' => $conf, ) ); if ( $title === false ) { @@ -108,11 +122,13 @@ class LinkerTest extends MediaWikiLangTestCase { $this->assertEquals( $expected, - Linker::formatComment( $comment, $title, $local ) + Linker::formatComment( $comment, $title, $local, $wikiId ) ); } - public static function provideCasesForFormatComment() { + public function provideCasesForFormatComment() { + $wikiId = 'enwiki'; // $wgConf has a fake entry for this + return array( // Linker::formatComment array( @@ -127,6 +143,10 @@ class LinkerTest extends MediaWikiLangTestCase { "'''not bolded'''", "'''not bolded'''", ), + array( + "try <script>evil</scipt> things", + "try <script>evil</scipt> things", + ), // Linker::formatAutocomments array( '<a href="/wiki/Special:BlankPage#autocomment" title="Special:BlankPage">→</a><span dir="auto"><span class="autocomment">autocomment</span></span>', @@ -157,6 +177,14 @@ class LinkerTest extends MediaWikiLangTestCase { "/* autocomment containing /* */ T70361" ), array( + '<a href="/wiki/Special:BlankPage#autocomment_containing_.22quotes.22" title="Special:BlankPage">→</a><span dir="auto"><span class="autocomment">autocomment containing "quotes"</span></span>', + "/* autocomment containing \"quotes\" */" + ), + array( + '<a href="/wiki/Special:BlankPage#autocomment_containing_.3Cscript.3Etags.3C.2Fscript.3E" title="Special:BlankPage">→</a><span dir="auto"><span class="autocomment">autocomment containing <script>tags</script></span></span>', + "/* autocomment containing <script>tags</script> */" + ), + array( '<a href="#autocomment">→</a><span dir="auto"><span class="autocomment">autocomment</span></span>', "/* autocomment */", false, true @@ -166,6 +194,16 @@ class LinkerTest extends MediaWikiLangTestCase { "/* autocomment */", null ), + array( + '<a href="/wiki/Special:BlankPage#autocomment" title="Special:BlankPage">→</a><span dir="auto"><span class="autocomment">autocomment</span></span>', + "/* autocomment */", + false, false + ), + array( + '<a class="external" rel="nofollow" href="//en.example.org/w/Special:BlankPage#autocomment">→</a><span dir="auto"><span class="autocomment">autocomment</span></span>', + "/* autocomment */", + false, false, $wikiId + ), // Linker::formatLinksInComment array( 'abc <a href="/wiki/index.php?title=Link&action=edit&redlink=1" class="new" title="Link (page does not exist)">link</a> def', @@ -191,6 +229,28 @@ class LinkerTest extends MediaWikiLangTestCase { 'abc <a href="/wiki/index.php?title=/subpage&action=edit&redlink=1" class="new" title="/subpage (page does not exist)">/subpage</a> def', "abc [[/subpage]] def", ), + array( + 'abc <a href="/wiki/index.php?title=%22evil!%22&action=edit&redlink=1" class="new" title=""evil!" (page does not exist)">"evil!"</a> def', + "abc [[\"evil!\"]] def", + ), + array( + 'abc [[<script>very evil</script>]] def', + "abc [[<script>very evil</script>]] def", + ), + array( + 'abc [[|]] def', + "abc [[|]] def", + ), + array( + 'abc <a href="/wiki/index.php?title=Link&action=edit&redlink=1" class="new" title="Link (page does not exist)">link</a> def', + "abc [[link]] def", + false, false + ), + array( + 'abc <a class="external" rel="nofollow" href="//en.example.org/w/Link">link</a> def', + "abc [[link]] def", + false, false, $wikiId + ) ); } diff --git a/tests/phpunit/includes/MediaWikiTest.php b/tests/phpunit/includes/MediaWikiTest.php index df94d3e3..e1962436 100644 --- a/tests/phpunit/includes/MediaWikiTest.php +++ b/tests/phpunit/includes/MediaWikiTest.php @@ -34,7 +34,7 @@ class MediaWikiTest extends MediaWikiTestCase { 'url' => 'http://example.org/w/index.php?title=Foo_Bar', 'query' => array( 'title' => 'Foo_Bar' ), 'title' => 'Foo_Bar', - 'redirect' => false, + 'redirect' => 'http://example.org/wiki/Foo_Bar', ), array( // View: Script path with implicit title from page id @@ -76,21 +76,21 @@ class MediaWikiTest extends MediaWikiTestCase { 'url' => 'http://example.org/w/?title=Foo_Bar', 'query' => array( 'title' => 'Foo_Bar' ), 'title' => 'Foo_Bar', - 'redirect' => false, + 'redirect' => 'http://example.org/wiki/Foo_Bar', ), array( // View: Root path with escaped title 'url' => 'http://example.org/?title=Foo_Bar', 'query' => array( 'title' => 'Foo_Bar' ), 'title' => 'Foo_Bar', - 'redirect' => false, + 'redirect' => 'http://example.org/wiki/Foo_Bar', ), array( // View: Canonical with redundant query 'url' => 'http://example.org/wiki/Foo_Bar?action=view', 'query' => array( 'action' => 'view' ), 'title' => 'Foo_Bar', - 'redirect' => false, + 'redirect' => 'http://example.org/wiki/Foo_Bar', ), array( // Edit: Canonical view url with action query @@ -104,7 +104,7 @@ class MediaWikiTest extends MediaWikiTestCase { 'url' => 'http://example.org/w/index.php?title=Foo_Bar&action=view', 'query' => array( 'title' => 'Foo_Bar', 'action' => 'view' ), 'title' => 'Foo_Bar', - 'redirect' => false, + 'redirect' => 'http://example.org/wiki/Foo_Bar', ), array( // Edit: Index with action query diff --git a/tests/phpunit/includes/MessageTest.php b/tests/phpunit/includes/MessageTest.php index 99ec2e42..9c953a6d 100644 --- a/tests/phpunit/includes/MessageTest.php +++ b/tests/phpunit/includes/MessageTest.php @@ -21,6 +21,17 @@ class MessageTest extends MediaWikiLangTestCase { $this->assertEquals( $key, $message->getKey() ); $this->assertEquals( $params, $message->getParams() ); $this->assertEquals( $expectedLang, $message->getLanguage() ); + + $messageSpecifier = $this->getMockForAbstractClass( 'MessageSpecifier' ); + $messageSpecifier->expects( $this->any() ) + ->method( 'getKey' )->will( $this->returnValue( $key ) ); + $messageSpecifier->expects( $this->any() ) + ->method( 'getParams' )->will( $this->returnValue( $params ) ); + $message = new Message( $messageSpecifier, array(), $language ); + + $this->assertEquals( $key, $message->getKey() ); + $this->assertEquals( $params, $message->getParams() ); + $this->assertEquals( $expectedLang, $message->getLanguage() ); } public static function provideConstructor() { @@ -548,4 +559,26 @@ class MessageTest extends MediaWikiLangTestCase { public function testInLanguageThrows() { wfMessage( 'foo' )->inLanguage( 123 ); } + + /** + * @covers Message::serialize + * @covers Message::unserialize + */ + public function testSerialization() { + $msg = new Message( 'parentheses' ); + $msg->rawParams( '<a>foo</a>' ); + $msg->title( Title::newFromText( 'Testing' ) ); + $this->assertEquals( '(<a>foo</a>)', $msg->parse(), 'Sanity check' ); + $msg = unserialize( serialize( $msg ) ); + $this->assertEquals( '(<a>foo</a>)', $msg->parse() ); + $title = TestingAccessWrapper::newFromObject( $msg )->title; + $this->assertInstanceOf( 'Title', $title ); + $this->assertEquals( 'Testing', $title->getFullText() ); + + $msg = new Message( 'mainpage' ); + $msg->inLanguage( 'de' ); + $this->assertEquals( 'Hauptseite', $msg->plain(), 'Sanity check' ); + $msg = unserialize( serialize( $msg ) ); + $this->assertEquals( 'Hauptseite', $msg->plain() ); + } } diff --git a/tests/phpunit/includes/MimeMagicTest.php b/tests/phpunit/includes/MimeMagicTest.php index 742d3827..3c45f305 100644 --- a/tests/phpunit/includes/MimeMagicTest.php +++ b/tests/phpunit/includes/MimeMagicTest.php @@ -1,5 +1,5 @@ <?php -class MimeMagicTest extends MediaWikiTestCase { +class MimeMagicTest extends PHPUnit_Framework_TestCase { /** @var MimeMagic */ private $mimeMagic; diff --git a/tests/phpunit/includes/MovePageTest.php b/tests/phpunit/includes/MovePageTest.php index 9501e452..0ef2fa63 100644 --- a/tests/phpunit/includes/MovePageTest.php +++ b/tests/phpunit/includes/MovePageTest.php @@ -57,7 +57,7 @@ class MovePageTest extends MediaWikiTestCase { WikiPage::factory( $oldTitle )->getRevision() ); $this->assertNotNull( - WikiPage::factory( $newTitle)->getRevision() + WikiPage::factory( $newTitle )->getRevision() ); } } diff --git a/tests/phpunit/includes/OutputPageTest.php b/tests/phpunit/includes/OutputPageTest.php index 6c6d95ee..f0d905e5 100644 --- a/tests/phpunit/includes/OutputPageTest.php +++ b/tests/phpunit/includes/OutputPageTest.php @@ -141,53 +141,36 @@ class OutputPageTest extends MediaWikiTestCase { // Load module script only array( array( 'test.foo', ResourceLoaderModule::TYPE_SCRIPTS ), - '<script>if(window.mw){ -document.write("\u003Cscript src=\"http://127.0.0.1:8080/w/load.php?debug=false\u0026amp;lang=en\u0026amp;modules=test.foo\u0026amp;only=scripts\u0026amp;skin=fallback\u0026amp;*\"\u003E\u003C/script\u003E"); -}</script> -' + "<script>window.RLQ = window.RLQ || []; window.RLQ.push( function () {\n" + . 'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.foo\u0026only=scripts\u0026skin=fallback");' + . "\n} );</script>" ), array( // Don't condition wrap raw modules (like the startup module) array( 'test.raw', ResourceLoaderModule::TYPE_SCRIPTS ), - '<script src="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.raw&only=scripts&skin=fallback&*"></script> -' + '<script async src="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.raw&only=scripts&skin=fallback"></script>' ), // Load module styles only // This also tests the order the modules are put into the url array( array( array( 'test.baz', 'test.foo', 'test.bar' ), ResourceLoaderModule::TYPE_STYLES ), - '<link rel=stylesheet href="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.bar%2Cbaz%2Cfoo&only=styles&skin=fallback&*"> -' + + '<link rel=stylesheet href="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.bar%2Cbaz%2Cfoo&only=styles&skin=fallback">' ), // Load private module (only=scripts) array( array( 'test.quux', ResourceLoaderModule::TYPE_SCRIPTS ), - '<script>if(window.mw){ -mw.test.baz({token:123});mw.loader.state({"test.quux":"ready"}); - -}</script> -' + "<script>window.RLQ = window.RLQ || []; window.RLQ.push( function () {\n" + . "mw.test.baz({token:123});mw.loader.state({\"test.quux\":\"ready\"});\n" + . "} );</script>" ), // Load private module (combined) array( array( 'test.quux', ResourceLoaderModule::TYPE_COMBINED ), - '<script>if(window.mw){ -mw.loader.implement("test.quux",function($,jQuery){mw.test.baz({token:123});},{"css":[".mw-icon{transition:none}\n"]}); - -}</script> -' - ), - // Load module script with ESI - array( - array( 'test.foo', ResourceLoaderModule::TYPE_SCRIPTS, true ), - '<script><esi:include src="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.foo&only=scripts&skin=fallback&*" /></script> -' - ), - // Load module styles with ESI - array( - array( 'test.foo', ResourceLoaderModule::TYPE_STYLES, true ), - '<style><esi:include src="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.foo&only=styles&skin=fallback&*" /></style> -', + "<script>window.RLQ = window.RLQ || []; window.RLQ.push( function () {\n" + . "mw.loader.implement(\"test.quux\",function($,jQuery){" + . "mw.test.baz({token:123});},{\"css\":[\".mw-icon{transition:none}" + . "\"]});\n} );</script>" ), // Load no modules array( @@ -197,19 +180,17 @@ mw.loader.implement("test.quux",function($,jQuery){mw.test.baz({token:123});},{" // noscript group array( array( 'test.noscript', ResourceLoaderModule::TYPE_STYLES ), - '<noscript><link rel=stylesheet href="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.noscript&only=styles&skin=fallback&*"></noscript> -' + '<noscript><link rel=stylesheet href="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.noscript&only=styles&skin=fallback"></noscript>' ), // Load two modules in separate groups array( array( array( 'test.group.foo', 'test.group.bar' ), ResourceLoaderModule::TYPE_COMBINED ), - '<script>if(window.mw){ -document.write("\u003Cscript src=\"http://127.0.0.1:8080/w/load.php?debug=false\u0026amp;lang=en\u0026amp;modules=test.group.bar\u0026amp;skin=fallback\u0026amp;*\"\u003E\u003C/script\u003E"); -}</script> -<script>if(window.mw){ -document.write("\u003Cscript src=\"http://127.0.0.1:8080/w/load.php?debug=false\u0026amp;lang=en\u0026amp;modules=test.group.foo\u0026amp;skin=fallback\u0026amp;*\"\u003E\u003C/script\u003E"); -}</script> -' + "<script>window.RLQ = window.RLQ || []; window.RLQ.push( function () {\n" + . 'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.group.bar\u0026skin=fallback");' + . "\n} );</script>\n" + . "<script>window.RLQ = window.RLQ || []; window.RLQ.push( function () {\n" + . 'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.group.foo\u0026skin=fallback");' + . "\n} );</script>" ), ); } @@ -226,7 +207,6 @@ document.write("\u003Cscript src=\"http://127.0.0.1:8080/w/load.php?debug=false\ public function testMakeResourceLoaderLink( $args, $expectedHtml ) { $this->setMwGlobals( array( 'wgResourceLoaderDebug' => false, - 'wgResourceLoaderUseESI' => true, 'wgLoadScript' => 'http://127.0.0.1:8080/w/load.php', // Affects whether CDATA is inserted 'wgWellFormedXml' => false, @@ -244,63 +224,141 @@ document.write("\u003Cscript src=\"http://127.0.0.1:8080/w/load.php?debug=false\ 'test.foo' => new ResourceLoaderTestModule( array( 'script' => 'mw.test.foo( { a: true } );', 'styles' => '.mw-test-foo { content: "style"; }', - )), + ) ), 'test.bar' => new ResourceLoaderTestModule( array( 'script' => 'mw.test.bar( { a: true } );', 'styles' => '.mw-test-bar { content: "style"; }', - )), + ) ), 'test.baz' => new ResourceLoaderTestModule( array( 'script' => 'mw.test.baz( { a: true } );', 'styles' => '.mw-test-baz { content: "style"; }', - )), + ) ), 'test.quux' => new ResourceLoaderTestModule( array( 'script' => 'mw.test.baz( { token: 123 } );', 'styles' => '/* pref-animate=off */ .mw-icon { transition: none; }', 'group' => 'private', - )), + ) ), 'test.raw' => new ResourceLoaderTestModule( array( 'script' => 'mw.test.baz( { token: 123 } );', 'isRaw' => true, - )), + ) ), 'test.noscript' => new ResourceLoaderTestModule( array( 'styles' => '.mw-test-noscript { content: "style"; }', 'group' => 'noscript', - )), + ) ), 'test.group.bar' => new ResourceLoaderTestModule( array( 'styles' => '.mw-group-bar { content: "style"; }', 'group' => 'bar', - )), + ) ), 'test.group.foo' => new ResourceLoaderTestModule( array( 'styles' => '.mw-group-foo { content: "style"; }', 'group' => 'foo', - )), + ) ), ) ); $links = $method->invokeArgs( $out, $args ); - // Strip comments to avoid variation due to wgDBname in WikiID and cache key - $actualHtml = preg_replace( '#/\*[^*]+\*/#', '', $links['html'] ); + $actualHtml = implode( "\n", $links['html'] ); $this->assertEquals( $expectedHtml, $actualHtml ); } + + /** + * @dataProvider provideVaryHeaders + * @covers OutputPage::addVaryHeader + * @covers OutputPage::getVaryHeader + * @covers OutputPage::getXVO + */ + public function testVaryHeaders( $calls, $vary, $xvo ) { + // get rid of default Vary fields + $outputPage = $this->getMockBuilder( 'OutputPage' ) + ->setConstructorArgs( array( new RequestContext() ) ) + ->setMethods( array( 'getCacheVaryCookies' ) ) + ->getMock(); + $outputPage->expects( $this->any() ) + ->method( 'getCacheVaryCookies' ) + ->will( $this->returnValue( array() ) ); + TestingAccessWrapper::newFromObject( $outputPage )->mVaryHeader = array(); + + foreach ( $calls as $call ) { + call_user_func_array( array( $outputPage, 'addVaryHeader' ), $call ); + } + $this->assertEquals( $vary, $outputPage->getVaryHeader(), 'Vary:' ); + $this->assertEquals( $xvo, $outputPage->getXVO(), 'X-Vary-Options:' ); + } + + public function provideVaryHeaders() { + // note: getXVO() automatically adds Vary: Cookie + return array( + array( // single header + array( + array( 'Cookie' ), + ), + 'Vary: Cookie', + 'X-Vary-Options: Cookie', + ), + array( // non-unique headers + array( + array( 'Cookie' ), + array( 'Accept-Language' ), + array( 'Cookie' ), + ), + 'Vary: Cookie, Accept-Language', + 'X-Vary-Options: Cookie,Accept-Language', + ), + array( // two headers with single options + array( + array( 'Cookie', array( 'string-contains=phpsessid' ) ), + array( 'Accept-Language', array( 'string-contains=en' ) ), + ), + 'Vary: Cookie, Accept-Language', + 'X-Vary-Options: Cookie;string-contains=phpsessid,Accept-Language;string-contains=en', + ), + array( // one header with multiple options + array( + array( 'Cookie', array( 'string-contains=phpsessid', 'string-contains=userId' ) ), + ), + 'Vary: Cookie', + 'X-Vary-Options: Cookie;string-contains=phpsessid;string-contains=userId', + ), + array( // Duplicate option + array( + array( 'Cookie', array( 'string-contains=phpsessid' ) ), + array( 'Cookie', array( 'string-contains=phpsessid' ) ), + array( 'Accept-Language', array( 'string-contains=en', 'string-contains=en' ) ), + + + ), + 'Vary: Cookie, Accept-Language', + 'X-Vary-Options: Cookie;string-contains=phpsessid,Accept-Language;string-contains=en', + ), + array( // Same header, different options + array( + array( 'Cookie', array( 'string-contains=phpsessid' ) ), + array( 'Cookie', array( 'string-contains=userId' ) ), + ), + 'Vary: Cookie', + 'X-Vary-Options: Cookie;string-contains=phpsessid;string-contains=userId', + ), + ); + } } /** * MessageBlobStore that doesn't do anything */ class NullMessageBlobStore extends MessageBlobStore { - public function get ( ResourceLoader $resourceLoader, $modules, $lang ) { + public function get( ResourceLoader $resourceLoader, $modules, $lang ) { return array(); } - public function insertMessageBlob ( $name, ResourceLoaderModule $module, $lang ) { + public function insertMessageBlob( $name, ResourceLoaderModule $module, $lang ) { return false; } - public function updateModule ( $name, ResourceLoaderModule $module, $lang ) { + public function updateModule( $name, ResourceLoaderModule $module, $lang ) { return; } - public function updateMessage ( $key ) { + public function updateMessage( $key ) { } public function clear() { } } - diff --git a/tests/phpunit/includes/PrefixSearchTest.php b/tests/phpunit/includes/PrefixSearchTest.php index d63541b7..afd10e9a 100644 --- a/tests/phpunit/includes/PrefixSearchTest.php +++ b/tests/phpunit/includes/PrefixSearchTest.php @@ -6,6 +6,11 @@ class PrefixSearchTest extends MediaWikiLangTestCase { public function addDBData() { + if ( !$this->isWikitextNS( NS_MAIN ) ) { + // tests are skipped if NS_MAIN is not wikitext + return; + } + $this->insertPage( 'Sandbox' ); $this->insertPage( 'Bar' ); $this->insertPage( 'Example' ); diff --git a/tests/phpunit/includes/SanitizerTest.php b/tests/phpunit/includes/SanitizerTest.php index c615c460..d3dc512b 100644 --- a/tests/phpunit/includes/SanitizerTest.php +++ b/tests/phpunit/includes/SanitizerTest.php @@ -6,6 +6,11 @@ */ class SanitizerTest extends MediaWikiTestCase { + protected function tearDown() { + MWTidy::destroySingleton(); + parent::tearDown(); + } + /** * @covers Sanitizer::decodeCharReferences */ @@ -93,9 +98,7 @@ class SanitizerTest extends MediaWikiTestCase { * @param bool $escaped Whether sanitizer let the tag in or escape it (ie: '<video>') */ public function testRemovehtmltagsOnHtml5Tags( $tag, $escaped ) { - $this->setMwGlobals( array( - 'wgUseTidy' => false - ) ); + MWTidy::setInstance( false ); if ( $escaped ) { $this->assertEquals( "<$tag>", @@ -157,7 +160,7 @@ class SanitizerTest extends MediaWikiTestCase { * @covers Sanitizer::removeHTMLtags */ public function testRemoveHTMLtags( $input, $output, $msg = null ) { - $GLOBALS['wgUseTidy'] = false; + MWTidy::setInstance( false ); $this->assertEquals( $output, Sanitizer::removeHTMLtags( $input ), $msg ); } @@ -360,5 +363,4 @@ class SanitizerTest extends MediaWikiTestCase { array( '<script>foo</script>', '<script>foo</script>' ), ); } - } diff --git a/tests/phpunit/includes/SanitizerValidateEmailTest.php b/tests/phpunit/includes/SanitizerValidateEmailTest.php index 14911f04..f47e74e2 100644 --- a/tests/phpunit/includes/SanitizerValidateEmailTest.php +++ b/tests/phpunit/includes/SanitizerValidateEmailTest.php @@ -5,7 +5,7 @@ * @todo all test methods in this class should be refactored and... * use a single test method and a single data provider... */ -class SanitizerValidateEmailTest extends MediaWikiTestCase { +class SanitizerValidateEmailTest extends PHPUnit_Framework_TestCase { private function checkEmail( $addr, $expected = true, $msg = '' ) { if ( $msg == '' ) { diff --git a/tests/phpunit/includes/StatusTest.php b/tests/phpunit/includes/StatusTest.php index c013f4fc..291ed315 100644 --- a/tests/phpunit/includes/StatusTest.php +++ b/tests/phpunit/includes/StatusTest.php @@ -372,7 +372,7 @@ class StatusTest extends MediaWikiLangTestCase { ); $status = new Status(); - $status->warning( new Message( 'fooBar!', array( 'foo', 'bar' ) ) ); + $status->warning( new Message( 'fooBar!', array( 'foo', 'bar' ) ) ); $testCases['1MessageWarning'] = array( $status, "<fooBar!>", @@ -449,7 +449,7 @@ class StatusTest extends MediaWikiLangTestCase { // ); $status = new Status(); - $status->warning( new Message( 'fooBar!', array( 'foo', 'bar' ) ) ); + $status->warning( new Message( 'fooBar!', array( 'foo', 'bar' ) ) ); $testCases['1MessageWarning'] = array( $status, array( 'foo', 'bar' ), diff --git a/tests/phpunit/includes/TemplateParserTest.php b/tests/phpunit/includes/TemplateParserTest.php index 81854ff3..3b37f4a4 100644 --- a/tests/phpunit/includes/TemplateParserTest.php +++ b/tests/phpunit/includes/TemplateParserTest.php @@ -57,7 +57,20 @@ class TemplateParserTest extends MediaWikiTestCase { array(), false, 'RuntimeException', - ) + ), + array( + 'has_partial', + array( + 'planet' => 'world', + ), + "Partial hello world!\n in here\n", + ), + array( + 'bad_partial', + array(), + false, + 'Exception', + ), ); } } diff --git a/tests/phpunit/includes/TestingAccessWrapper.php b/tests/phpunit/includes/TestingAccessWrapper.php index 84c0f9b5..63d89719 100644 --- a/tests/phpunit/includes/TestingAccessWrapper.php +++ b/tests/phpunit/includes/TestingAccessWrapper.php @@ -34,16 +34,42 @@ class TestingAccessWrapper { return $methodReflection->invokeArgs( $this->object, $args ); } - public function __set( $name, $value ) { + /** + * ReflectionClass::getProperty() fails if the private property is defined + * in a parent class. This works more like ReflectionClass::getMethod(). + */ + private function getProperty( $name ) { $classReflection = new ReflectionClass( $this->object ); - $propertyReflection = $classReflection->getProperty( $name ); + try { + return $classReflection->getProperty( $name ); + } catch ( ReflectionException $ex ) { + while ( true ) { + $classReflection = $classReflection->getParentClass(); + if ( !$classReflection ) { + throw $ex; + } + try { + $propertyReflection = $classReflection->getProperty( $name ); + } catch ( ReflectionException $ex2 ) { + continue; + } + if ( $propertyReflection->isPrivate() ) { + return $propertyReflection; + } else { + throw $ex; + } + } + } + } + + public function __set( $name, $value ) { + $propertyReflection = $this->getProperty( $name ); $propertyReflection->setAccessible( true ); $propertyReflection->setValue( $this->object, $value ); } public function __get( $name ) { - $classReflection = new ReflectionClass( $this->object ); - $propertyReflection = $classReflection->getProperty( $name ); + $propertyReflection = $this->getProperty( $name ); $propertyReflection->setAccessible( true ); return $propertyReflection->getValue( $this->object ); } diff --git a/tests/phpunit/includes/TestingAccessWrapperTest.php b/tests/phpunit/includes/TestingAccessWrapperTest.php index 7e5b91a1..fc54afae 100644 --- a/tests/phpunit/includes/TestingAccessWrapperTest.php +++ b/tests/phpunit/includes/TestingAccessWrapperTest.php @@ -14,18 +14,36 @@ class TestingAccessWrapperTest extends MediaWikiTestCase { function testGetProperty() { $this->assertSame( 1, $this->wrapped->property ); + $this->assertSame( 42, $this->wrapped->privateProperty ); + $this->assertSame( 9000, $this->wrapped->privateParentProperty ); } function testSetProperty() { $this->wrapped->property = 10; $this->assertSame( 10, $this->wrapped->property ); $this->assertSame( 10, $this->raw->getProperty() ); + + $this->wrapped->privateProperty = 11; + $this->assertSame( 11, $this->wrapped->privateProperty ); + $this->assertSame( 11, $this->raw->getPrivateProperty() ); + + $this->wrapped->privateParentProperty = 12; + $this->assertSame( 12, $this->wrapped->privateParentProperty ); + $this->assertSame( 12, $this->raw->getPrivateParentProperty() ); } function testCallMethod() { $this->wrapped->incrementPropertyValue(); $this->assertSame( 2, $this->wrapped->property ); $this->assertSame( 2, $this->raw->getProperty() ); + + $this->wrapped->incrementPrivatePropertyValue(); + $this->assertSame( 43, $this->wrapped->privateProperty ); + $this->assertSame( 43, $this->raw->getPrivateProperty() ); + + $this->wrapped->incrementPrivateParentPropertyValue(); + $this->assertSame( 9001, $this->wrapped->privateParentProperty ); + $this->assertSame( 9001, $this->raw->getPrivateParentProperty() ); } function testCallMethodTwoArgs() { diff --git a/tests/phpunit/includes/TitleArrayFromResultTest.php b/tests/phpunit/includes/TitleArrayFromResultTest.php index 0f7069ae..6654a5b6 100644 --- a/tests/phpunit/includes/TitleArrayFromResultTest.php +++ b/tests/phpunit/includes/TitleArrayFromResultTest.php @@ -4,7 +4,7 @@ * @author Adam Shorland * @covers TitleArrayFromResult */ -class TitleArrayFromResultTest extends MediaWikiTestCase { +class TitleArrayFromResultTest extends PHPUnit_Framework_TestCase { private function getMockResultWrapper( $row = null, $numRows = 1 ) { $resultWrapper = $this->getMockBuilder( 'ResultWrapper' ) diff --git a/tests/phpunit/includes/TitlePermissionTest.php b/tests/phpunit/includes/TitlePermissionTest.php index 022c7d53..f588ed63 100644 --- a/tests/phpunit/includes/TitlePermissionTest.php +++ b/tests/phpunit/includes/TitlePermissionTest.php @@ -664,7 +664,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->setTitle( NS_MAIN, "test page" ); $this->title->mTitleProtection['permission'] = ''; $this->title->mTitleProtection['user'] = $this->user->getID(); - $this->title->mTitleProtection['expiry'] = wfGetDB( DB_SLAVE )->getInfinity(); + $this->title->mTitleProtection['expiry'] = 'infinity'; $this->title->mTitleProtection['reason'] = 'test'; $this->title->mCascadeRestriction = false; @@ -753,8 +753,14 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $prev = time(); $now = time() + 120; $this->user->mBlockedby = $this->user->getId(); - $this->user->mBlock = new Block( '127.0.8.1', 0, $this->user->getId(), - 'no reason given', $prev + 3600, 1, 0 ); + $this->user->mBlock = new Block( array( + 'address' => '127.0.8.1', + 'by' => $this->user->getId(), + 'reason' => 'no reason given', + 'timestamp' => $prev + 3600, + 'auto' => true, + 'expiry' => 0 + ) ); $this->user->mBlock->mTimestamp = 0; $this->assertEquals( array( array( 'autoblockedtext', '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1', @@ -770,8 +776,14 @@ class TitlePermissionTest extends MediaWikiLangTestCase { global $wgLocalTZoffset; $wgLocalTZoffset = -60; $this->user->mBlockedby = $this->user->getName(); - $this->user->mBlock = new Block( '127.0.8.1', 0, $this->user->getId(), - 'no reason given', $now, 0, 10 ); + $this->user->mBlock = new Block( array( + 'address' => '127.0.8.1', + 'by' => $this->user->getId(), + 'reason' => 'no reason given', + 'timestamp' => $now, + 'auto' => false, + 'expiry' => 10, + ) ); $this->assertEquals( array( array( 'blockedtext', '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1', 'Useruser', null, '23:00, 31 December 1969', '127.0.8.1', diff --git a/tests/phpunit/includes/TitleTest.php b/tests/phpunit/includes/TitleTest.php index d55f958b..a2c6f23d 100644 --- a/tests/phpunit/includes/TitleTest.php +++ b/tests/phpunit/includes/TitleTest.php @@ -1,6 +1,7 @@ <?php /** + * @group Database * @group Title */ class TitleTest extends MediaWikiTestCase { @@ -79,22 +80,22 @@ class TitleTest extends MediaWikiTestCase { public static function provideInvalidSecureAndSplit() { return array( - array( '' ), - array( ':' ), - array( '__ __' ), - array( ' __ ' ), + array( '', 'title-invalid-empty' ), + array( ':', 'title-invalid-empty' ), + array( '__ __', 'title-invalid-empty' ), + array( ' __ ', 'title-invalid-empty' ), // Bad characters forbidden regardless of wgLegalTitleChars - array( 'A [ B' ), - array( 'A ] B' ), - array( 'A { B' ), - array( 'A } B' ), - array( 'A < B' ), - array( 'A > B' ), - array( 'A | B' ), + array( 'A [ B', 'title-invalid-characters' ), + array( 'A ] B', 'title-invalid-characters' ), + array( 'A { B', 'title-invalid-characters' ), + array( 'A } B', 'title-invalid-characters' ), + array( 'A < B', 'title-invalid-characters' ), + array( 'A > B', 'title-invalid-characters' ), + array( 'A | B', 'title-invalid-characters' ), // URL encoding - array( 'A%20B' ), - array( 'A%23B' ), - array( 'A%2523B' ), + array( 'A%20B', 'title-invalid-characters' ), + array( 'A%23B', 'title-invalid-characters' ), + array( 'A%2523B', 'title-invalid-characters' ), // XML/HTML character entity references // Note: Commented out because they are not marked invalid by the PHP test as // Title::newFromText runs Sanitizer::decodeCharReferencesAndNormalize first. @@ -102,29 +103,30 @@ class TitleTest extends MediaWikiTestCase { //'A é B', //'A é B', // Subject of NS_TALK does not roundtrip to NS_MAIN - array( 'Talk:File:Example.svg' ), + array( 'Talk:File:Example.svg', 'title-invalid-talk-namespace' ), // Directory navigation - array( '.' ), - array( '..' ), - array( './Sandbox' ), - array( '../Sandbox' ), - array( 'Foo/./Sandbox' ), - array( 'Foo/../Sandbox' ), - array( 'Sandbox/.' ), - array( 'Sandbox/..' ), + array( '.', 'title-invalid-relative' ), + array( '..', 'title-invalid-relative' ), + array( './Sandbox', 'title-invalid-relative' ), + array( '../Sandbox', 'title-invalid-relative' ), + array( 'Foo/./Sandbox', 'title-invalid-relative' ), + array( 'Foo/../Sandbox', 'title-invalid-relative' ), + array( 'Sandbox/.', 'title-invalid-relative' ), + array( 'Sandbox/..', 'title-invalid-relative' ), // Tilde - array( 'A ~~~ Name' ), - array( 'A ~~~~ Signature' ), - array( 'A ~~~~~ Timestamp' ), - array( str_repeat( 'x', 256 ) ), + array( 'A ~~~ Name', 'title-invalid-magic-tilde' ), + array( 'A ~~~~ Signature', 'title-invalid-magic-tilde' ), + array( 'A ~~~~~ Timestamp', 'title-invalid-magic-tilde' ), + // Length + array( str_repeat( 'x', 256 ), 'title-invalid-too-long' ), // Namespace prefix without actual title - array( 'Talk:' ), - array( 'Talk:#' ), - array( 'Category: ' ), - array( 'Category: #bar' ), + array( 'Talk:', 'title-invalid-empty' ), + array( 'Talk:#', 'title-invalid-empty' ), + array( 'Category: ', 'title-invalid-empty' ), + array( 'Category: #bar', 'title-invalid-empty' ), // interwiki prefix - array( 'localtestiw: Talk: # anchor' ), - array( 'localtestiw: Talk:' ) + array( 'localtestiw: Talk: # anchor', 'title-invalid-empty' ), + array( 'localtestiw: Talk:', 'title-invalid-empty' ) ); } @@ -143,7 +145,7 @@ class TitleTest extends MediaWikiTestCase { } ) ) - )); + ) ); } /** @@ -163,9 +165,14 @@ class TitleTest extends MediaWikiTestCase { * @dataProvider provideInvalidSecureAndSplit * @note This mainly tests MediaWikiTitleCodec::parseTitle(). */ - public function testSecureAndSplitInvalid( $text ) { + public function testSecureAndSplitInvalid( $text, $expectedErrorMessage ) { $this->secureAndSplitGlobals(); - $this->assertNull( Title::newFromText( $text ), "Invalid: $text" ); + try { + Title::newFromTextThrow( $text ); // should throw + $this->assertTrue( false, "Invalid: $text" ); + } catch ( MalformedTitleException $ex ) { + $this->assertEquals( $expectedErrorMessage, $ex->getErrorMessage(), "Invalid: $text" ); + } } public static function provideConvertByteClassToUnicodeClass() { @@ -631,4 +638,26 @@ class TitleTest extends MediaWikiTestCase { $title = Title::makeTitle( NS_MAIN, 'Interwiki link', '', 'externalwiki' ); $this->assertTrue( $title->isAlwaysKnown() ); } + + /** + * @covers Title::exists + */ + public function testExists() { + $title = Title::makeTitle( NS_PROJECT, 'New page' ); + $linkCache = LinkCache::singleton(); + + $article = new Article( $title ); + $page = $article->getPage(); + $page->doEditContent( new WikitextContent( 'Some [[link]]' ), 'summary' ); + + // Tell Title it doesn't know whether it exists + $title->mArticleID = -1; + + // Tell the link cache it doesn't exists when it really does + $linkCache->clearLink( $title ); + $linkCache->addBadLinkObj( $title ); + + $this->assertEquals( false, $title->exists(), 'exists() should rely on link cache unless GAID_FOR_UPDATE is used' ); + $this->assertEquals( true, $title->exists( Title::GAID_FOR_UPDATE ), 'exists() should re-query database when GAID_FOR_UPDATE is used' ); + } } diff --git a/tests/phpunit/includes/UserTest.php b/tests/phpunit/includes/UserTest.php index b74a7ead..77132bbb 100644 --- a/tests/phpunit/includes/UserTest.php +++ b/tests/phpunit/includes/UserTest.php @@ -307,9 +307,30 @@ class UserTest extends MediaWikiTestCase { */ public function testCheckPasswordValidity() { $this->setMwGlobals( array( - 'wgMinimalPasswordLength' => 6, - 'wgMaximalPasswordLength' => 30, + 'wgPasswordPolicy' => array( + 'policies' => array( + 'sysop' => array( + 'MinimalPasswordLength' => 8, + 'MinimumPasswordLengthToLogin' => 1, + 'PasswordCannotMatchUsername' => 1, + ), + 'default' => array( + 'MinimalPasswordLength' => 6, + 'PasswordCannotMatchUsername' => true, + 'PasswordCannotMatchBlacklist' => true, + 'MaximalPasswordLength' => 30, + ), + ), + 'checks' => array( + 'MinimalPasswordLength' => 'PasswordPolicyChecks::checkMinimalPasswordLength', + 'MinimumPasswordLengthToLogin' => 'PasswordPolicyChecks::checkMinimumPasswordLengthToLogin', + 'PasswordCannotMatchUsername' => 'PasswordPolicyChecks::checkPasswordCannotMatchUsername', + 'PasswordCannotMatchBlacklist' => 'PasswordPolicyChecks::checkPasswordCannotMatchBlacklist', + 'MaximalPasswordLength' => 'PasswordPolicyChecks::checkMaximalPasswordLength', + ), + ), ) ); + $user = User::newFromName( 'Useruser' ); // Sanity $this->assertTrue( $user->isValidPassword( 'Password1234' ) ); @@ -425,4 +446,109 @@ class UserTest extends MediaWikiTestCase { $this->assertFalse( $user->isLoggedIn() ); $this->assertTrue( $user->isAnon() ); } + + /** + * @covers User::checkAndSetTouched + */ + public function testCheckAndSetTouched() { + $user = TestingAccessWrapper::newFromObject( User::newFromName( 'UTSysop' ) ); + $this->assertTrue( $user->isLoggedIn() ); + + $touched = $user->getDBTouched(); + $this->assertTrue( + $user->checkAndSetTouched(), "checkAndSetTouched() succeded" ); + $this->assertGreaterThan( + $touched, $user->getDBTouched(), "user_touched increased with casOnTouched()" ); + + $touched = $user->getDBTouched(); + $this->assertTrue( + $user->checkAndSetTouched(), "checkAndSetTouched() succeded #2" ); + $this->assertGreaterThan( + $touched, $user->getDBTouched(), "user_touched increased with casOnTouched() #2" ); + } + + public static function setExtendedLoginCookieDataProvider() { + $data = array(); + $now = time(); + + $secondsInDay = 86400; + + // Arbitrary durations, in units of days, to ensure it chooses the + // right one. There is a 5-minute grace period (see testSetExtendedLoginCookie) + // to work around slow tests, since we're not currently mocking time() for PHP. + + $durationOne = $secondsInDay * 5; + $durationTwo = $secondsInDay * 29; + $durationThree = $secondsInDay * 17; + + // If $wgExtendedLoginCookieExpiration is null, then the expiry passed to + // set cookie is time() + $wgCookieExpiration + $data[] = array( + null, + $durationOne, + $now + $durationOne, + ); + + // If $wgExtendedLoginCookieExpiration isn't null, then the expiry passed to + // set cookie is $now + $wgExtendedLoginCookieExpiration + $data[] = array( + $durationTwo, + $durationThree, + $now + $durationTwo, + ); + + return $data; + } + + /** + * @dataProvider setExtendedLoginCookieDataProvider + * @covers User::getRequest + * @covers User::setCookie + * @backupGlobals enabled + */ + public function testSetExtendedLoginCookie( + $extendedLoginCookieExpiration, + $cookieExpiration, + $expectedExpiry + ) { + $this->setMwGlobals( array( + 'wgExtendedLoginCookieExpiration' => $extendedLoginCookieExpiration, + 'wgCookieExpiration' => $cookieExpiration, + ) ); + + $response = $this->getMock( 'WebResponse' ); + $setcookieSpy = $this->any(); + $response->expects( $setcookieSpy ) + ->method( 'setcookie' ); + + $request = new MockWebRequest( $response ); + $user = new UserProxy( User::newFromSession( $request ) ); + $user->setExtendedLoginCookie( 'name', 'value', true ); + + $setcookieInvocations = $setcookieSpy->getInvocations(); + $setcookieInvocation = end( $setcookieInvocations ); + $actualExpiry = $setcookieInvocation->parameters[ 2 ]; + + // TODO: ± 300 seconds compensates for + // slow-running tests. However, the dependency on the time + // function should be removed. This requires some way + // to mock/isolate User->setExtendedLoginCookie's call to time() + $this->assertEquals( $expectedExpiry, $actualExpiry, '', 300 ); + } +} + +class UserProxy extends User { + + /** + * @var User + */ + protected $user; + + public function __construct( User $user ) { + $this->user = $user; + } + + public function setExtendedLoginCookie( $name, $value, $secure ) { + $this->user->setExtendedLoginCookie( $name, $value, $secure ); + } } diff --git a/tests/phpunit/includes/WikiMapTest.php b/tests/phpunit/includes/WikiMapTest.php new file mode 100644 index 00000000..9233416c --- /dev/null +++ b/tests/phpunit/includes/WikiMapTest.php @@ -0,0 +1,108 @@ +<?php + +/** + * @covers WikiMap + */ + +class WikiMapTest extends MediaWikiLangTestCase { + + public function setUp() { + parent::setUp(); + + $conf = new SiteConfiguration(); + $conf->settings = array( + 'wgServer' => array( + 'enwiki' => 'http://en.example.org', + 'ruwiki' => '//ru.example.org', + ), + 'wgArticlePath' => array( + 'enwiki' => '/w/$1', + 'ruwiki' => '/wiki/$1', + ), + ); + $conf->suffixes = array( 'wiki' ); + $this->setMwGlobals( array( + 'wgConf' => $conf, + ) ); + } + + public function provideGetWiki() { + $enwiki = new WikiReference( 'wiki', 'en', 'http://en.example.org', '/w/$1' ); + $ruwiki = new WikiReference( 'wiki', 'ru', '//ru.example.org', '/wiki/$1' ); + + return array( + 'unknown' => array( false, 'xyzzy' ), + 'enwiki' => array( $enwiki, 'enwiki' ), + 'ruwiki' => array( $ruwiki, 'ruwiki' ), + ); + } + + /** + * @dataProvider provideGetWiki + */ + public function testGetWiki( $expected, $wikiId ) { + $this->assertEquals( $expected, WikiMap::getWiki( $wikiId ) ); + } + + public function provideGetWikiName() { + return array( + 'unknown' => array( 'xyzzy', 'xyzzy' ), + 'enwiki' => array( 'en.example.org', 'enwiki' ), + 'ruwiki' => array( 'ru.example.org', 'ruwiki' ), + ); + } + + /** + * @dataProvider provideGetWikiName + */ + public function testGetWikiName( $expected, $wikiId ) { + $this->assertEquals( $expected, WikiMap::getWikiName( $wikiId ) ); + } + + public function provideMakeForeignLink() { + return array( + 'unknown' => array( false, 'xyzzy', 'Foo' ), + 'enwiki' => array( '<a class="external" rel="nofollow" href="http://en.example.org/w/Foo">Foo</a>', 'enwiki', 'Foo', ), + 'ruwiki' => array( '<a class="external" rel="nofollow" href="//ru.example.org/wiki/%D0%A4%D1%83">вар</a>', 'ruwiki', 'Фу', 'вар' ), + ); + } + + /** + * @dataProvider provideMakeForeignLink + */ + public function testMakeForeignLink( $expected, $wikiId, $page, $text = null ) { + $this->assertEquals( $expected, WikiMap::makeForeignLink( $wikiId, $page, $text ) ); + } + + public function provideForeignUserLink() { + return array( + 'unknown' => array( false, 'xyzzy', 'Foo' ), + 'enwiki' => array( '<a class="external" rel="nofollow" href="http://en.example.org/w/User:Foo">User:Foo</a>', 'enwiki', 'Foo', ), + 'ruwiki' => array( '<a class="external" rel="nofollow" href="//ru.example.org/wiki/User:%D0%A4%D1%83">вар</a>', 'ruwiki', 'Фу', 'вар' ), + ); + } + + /** + * @dataProvider provideForeignUserLink + */ + public function testForeignUserLink( $expected, $wikiId, $user, $text = null ) { + $this->assertEquals( $expected, WikiMap::foreignUserLink( $wikiId, $user, $text ) ); + } + + public function provideGetForeignURL() { + return array( + 'unknown' => array( false, 'xyzzy', 'Foo' ), + 'enwiki' => array( 'http://en.example.org/w/Foo', 'enwiki', 'Foo', ), + 'ruwiki with fragement' => array( '//ru.example.org/wiki/%D0%A4%D1%83#%D0%B2%D0%B0%D1%80', 'ruwiki', 'Фу', 'вар' ), + ); + } + + /** + * @dataProvider provideGetForeignURL + */ + public function testGetForeignURL( $expected, $wikiId, $page, $fragment = null ) { + $this->assertEquals( $expected, WikiMap::getForeignURL( $wikiId, $page, $fragment ) ); + } + +} + diff --git a/tests/phpunit/includes/WikiReferenceTest.php b/tests/phpunit/includes/WikiReferenceTest.php new file mode 100644 index 00000000..4fe2e855 --- /dev/null +++ b/tests/phpunit/includes/WikiReferenceTest.php @@ -0,0 +1,80 @@ +<?php + +/** + * @covers WikiReference + */ + +class WikiReferenceTest extends PHPUnit_Framework_TestCase { + + public function provideGetDisplayName() { + return array( + 'http' => array( 'foo.bar', 'http://foo.bar' ), + 'https' => array( 'foo.bar', 'http://foo.bar' ), + + // apparently, this is the expected behavior + 'invalid' => array( 'purple kittens', 'purple kittens' ), + ); + } + + /** + * @dataProvider provideGetDisplayName + */ + public function testGetDisplayName( $expected, $canonicalServer ) { + $reference = new WikiReference( 'wiki', 'xx', $canonicalServer, '/wiki/$1' ); + $this->assertEquals( $expected, $reference->getDisplayName() ); + } + + public function testGetCanonicalServer() { + $reference = new WikiReference( 'wiki', 'xx', 'https://acme.com', '/wiki/$1', '//acme.com' ); + $this->assertEquals( 'https://acme.com', $reference->getCanonicalServer() ); + } + + public function provideGetCanonicalUrl() { + return array( + 'no fragement' => array( 'https://acme.com/wiki/Foo', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo', null ), + 'empty fragement' => array( 'https://acme.com/wiki/Foo', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo', '' ), + 'fragment' => array( 'https://acme.com/wiki/Foo#Bar', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo', 'Bar' ), + 'double fragment' => array( 'https://acme.com/wiki/Foo#Bar%23Xus', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo', 'Bar#Xus' ), + 'escaped fragement' => array( 'https://acme.com/wiki/Foo%23Bar', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo#Bar', null ), + 'empty path' => array( 'https://acme.com/Foo', 'https://acme.com', '//acme.com', '/$1', 'Foo', null ), + ); + } + + /** + * @dataProvider provideGetCanonicalUrl + */ + public function testGetCanonicalUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) { + $reference = new WikiReference( 'wiki', 'xx', $canonicalServer, $path, $server ); + $this->assertEquals( $expected, $reference->getCanonicalUrl( $page, $fragmentId ) ); + } + + /** + * @dataProvider provideGetCanonicalUrl + * @note getUrl is an alias for getCanonicalUrl + */ + public function testGetUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) { + $reference = new WikiReference( 'wiki', 'xx', $canonicalServer, $path, $server ); + $this->assertEquals( $expected, $reference->getUrl( $page, $fragmentId ) ); + } + + public function provideGetFullUrl() { + return array( + 'no fragement' => array( '//acme.com/wiki/Foo', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo', null ), + 'empty fragement' => array( '//acme.com/wiki/Foo', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo', '' ), + 'fragment' => array( '//acme.com/wiki/Foo#Bar', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo', 'Bar' ), + 'double fragment' => array( '//acme.com/wiki/Foo#Bar%23Xus', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo', 'Bar#Xus' ), + 'escaped fragement' => array( '//acme.com/wiki/Foo%23Bar', 'https://acme.com', '//acme.com', '/wiki/$1', 'Foo#Bar', null ), + 'empty path' => array( '//acme.com/Foo', 'https://acme.com', '//acme.com', '/$1', 'Foo', null ), + ); + } + + /** + * @dataProvider provideGetFullUrl + */ + public function testGetFullUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) { + $reference = new WikiReference( 'wiki', 'xx', $canonicalServer, $path, $server ); + $this->assertEquals( $expected, $reference->getFullUrl( $page, $fragmentId ) ); + } + +} + diff --git a/tests/phpunit/includes/XmlJsTest.php b/tests/phpunit/includes/XmlJsTest.php index 0dbb0109..21819b7e 100644 --- a/tests/phpunit/includes/XmlJsTest.php +++ b/tests/phpunit/includes/XmlJsTest.php @@ -3,7 +3,7 @@ /** * @group Xml */ -class XmlJs extends MediaWikiTestCase { +class XmlJs extends PHPUnit_Framework_TestCase { /** * @covers XmlJsCode::__construct diff --git a/tests/phpunit/includes/XmlTest.php b/tests/phpunit/includes/XmlTest.php index 382e3d89..bea338de 100644 --- a/tests/phpunit/includes/XmlTest.php +++ b/tests/phpunit/includes/XmlTest.php @@ -154,7 +154,7 @@ class XmlTest extends MediaWikiTestCase { '<label for="year">From year (and earlier):</label> ' . '<input id="year" maxlength="4" size="7" type="number" value="2011" name="year" /> ' . '<label for="month">From month (and earlier):</label> ' . - '<select id="month" name="month" class="mw-month-selector">' . + '<select name="month" id="month" class="mw-month-selector">' . '<option value="-1">all</option>' . "\n" . '<option value="1">January</option>' . "\n" . '<option value="2" selected="">February</option>' . "\n" . @@ -175,7 +175,7 @@ class XmlTest extends MediaWikiTestCase { '<label for="year">From year (and earlier):</label> ' . '<input id="year" maxlength="4" size="7" type="number" value="2011" name="year" /> ' . '<label for="month">From month (and earlier):</label> ' . - '<select id="month" name="month" class="mw-month-selector">' . + '<select name="month" id="month" class="mw-month-selector">' . '<option value="-1">all</option>' . "\n" . '<option value="1">January</option>' . "\n" . '<option value="2">February</option>' . "\n" . @@ -209,7 +209,7 @@ class XmlTest extends MediaWikiTestCase { '<label for="year">From year (and earlier):</label> ' . '<input id="year" maxlength="4" size="7" type="number" name="year" /> ' . '<label for="month">From month (and earlier):</label> ' . - '<select id="month" name="month" class="mw-month-selector">' . + '<select name="month" id="month" class="mw-month-selector">' . '<option value="-1">all</option>' . "\n" . '<option value="1">January</option>' . "\n" . '<option value="2">February</option>' . "\n" . diff --git a/tests/phpunit/includes/api/ApiBlockTest.php b/tests/phpunit/includes/api/ApiBlockTest.php index d98eec6a..575efd6d 100644 --- a/tests/phpunit/includes/api/ApiBlockTest.php +++ b/tests/phpunit/includes/api/ApiBlockTest.php @@ -53,7 +53,7 @@ class ApiBlockTest extends ApiTestCase { 'action' => 'block', 'user' => 'UTApiBlockee', 'reason' => 'Some reason', - 'token' => $tokens['blocktoken'] ), null, false, self::$users['sysop']->user ); + 'token' => $tokens['blocktoken'] ), null, false, self::$users['sysop']->getUser() ); $block = Block::newFromTarget( 'UTApiBlockee' ); @@ -68,7 +68,7 @@ class ApiBlockTest extends ApiTestCase { * @expectedException UsageException * @expectedExceptionMessage The token parameter must be set */ - public function testBlockingActionWithNoToken( ) { + public function testBlockingActionWithNoToken() { $this->doApiRequest( array( 'action' => 'block', @@ -77,7 +77,7 @@ class ApiBlockTest extends ApiTestCase { ), null, false, - self::$users['sysop']->user + self::$users['sysop']->getUser() ); } } diff --git a/tests/phpunit/includes/api/ApiEditPageTest.php b/tests/phpunit/includes/api/ApiEditPageTest.php index 3179a452..61a8ad11 100644 --- a/tests/phpunit/includes/api/ApiEditPageTest.php +++ b/tests/phpunit/includes/api/ApiEditPageTest.php @@ -27,9 +27,14 @@ class ApiEditPageTest extends ApiTestCase { $wgExtraNamespaces[12312] = 'Dummy'; $wgExtraNamespaces[12313] = 'Dummy_talk'; + $wgExtraNamespaces[12314] = 'DummyNonText'; + $wgExtraNamespaces[12315] = 'DummyNonText_talk'; $wgNamespaceContentModels[12312] = "testing"; + $wgNamespaceContentModels[12314] = "testing-nontext"; + $wgContentHandlers["testing"] = 'DummyContentHandlerForTesting'; + $wgContentHandlers["testing-nontext"] = 'DummyNonTextContentHandler'; MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache $wgContLang->resetNamespaces(); # reset namespace cache @@ -96,33 +101,6 @@ class ApiEditPageTest extends ApiTestCase { ); } - public function testNonTextEdit() { - $name = 'Dummy:ApiEditPageTest_testNonTextEdit'; - $data = serialize( 'some bla bla text' ); - - // -- test new page -------------------------------------------- - $apiResult = $this->doApiRequestWithToken( array( - 'action' => 'edit', - 'title' => $name, - 'text' => $data, ) ); - $apiResult = $apiResult[0]; - - // Validate API result data - $this->assertArrayHasKey( 'edit', $apiResult ); - $this->assertArrayHasKey( 'result', $apiResult['edit'] ); - $this->assertEquals( 'Success', $apiResult['edit']['result'] ); - - $this->assertArrayHasKey( 'new', $apiResult['edit'] ); - $this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] ); - - $this->assertArrayHasKey( 'pageid', $apiResult['edit'] ); - - // validate resulting revision - $page = WikiPage::factory( Title::newFromText( $name ) ); - $this->assertEquals( "testing", $page->getContentModel() ); - $this->assertEquals( $data, $page->getContent()->serialize() ); - } - /** * @return array */ @@ -240,7 +218,7 @@ class ApiEditPageTest extends ApiTestCase { 'section' => 'new', 'text' => 'test', 'summary' => 'header', - )); + ) ); $this->assertEquals( 'Success', $re['edit']['result'] ); // Check the page text is correct @@ -257,7 +235,7 @@ class ApiEditPageTest extends ApiTestCase { 'section' => 'new', 'text' => 'test', 'summary' => 'header', - )); + ) ); $this->assertEquals( 'Success', $re2['edit']['result'] ); $text = WikiPage::factory( Title::newFromText( $name ) ) @@ -284,18 +262,18 @@ class ApiEditPageTest extends ApiTestCase { // base edit for content $page->doEditContent( new WikitextContent( "Foo" ), - "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() ); $this->forceRevisionDate( $page, '20120101000000' ); $baseTime = $page->getRevision()->getTimestamp(); // base edit for redirect $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]" ), - "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() ); $this->forceRevisionDate( $rpage, '20120101000000' ); // conflicting edit to redirect $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]\n\n[[Category:Test]]" ), - "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user ); + "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() ); $this->forceRevisionDate( $rpage, '20120101020202' ); // try to save edit, following the redirect @@ -306,7 +284,7 @@ class ApiEditPageTest extends ApiTestCase { 'basetimestamp' => $baseTime, 'section' => 'new', 'redirect' => true, - ), null, self::$users['sysop']->user ); + ), null, self::$users['sysop']->getUser() ); $this->assertEquals( 'Success', $re['edit']['result'], "no problems expected when following redirect" ); @@ -330,18 +308,18 @@ class ApiEditPageTest extends ApiTestCase { // base edit for content $page->doEditContent( new WikitextContent( "Foo" ), - "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() ); $this->forceRevisionDate( $page, '20120101000000' ); $baseTime = $page->getRevision()->getTimestamp(); // base edit for redirect $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]" ), - "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() ); $this->forceRevisionDate( $rpage, '20120101000000' ); // conflicting edit to redirect $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]\n\n[[Category:Test]]" ), - "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user ); + "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() ); $this->forceRevisionDate( $rpage, '20120101020202' ); // try to save edit, following the redirect but without creating a section @@ -352,7 +330,7 @@ class ApiEditPageTest extends ApiTestCase { 'text' => 'nix bar!', 'basetimestamp' => $baseTime, 'redirect' => true, - ), null, self::$users['sysop']->user ); + ), null, self::$users['sysop']->getUser() ); $this->fail( 'redirect-appendonly error expected' ); } catch ( UsageException $ex ) { @@ -372,13 +350,13 @@ class ApiEditPageTest extends ApiTestCase { // base edit $page->doEditContent( new WikitextContent( "Foo" ), - "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() ); $this->forceRevisionDate( $page, '20120101000000' ); $baseTime = $page->getRevision()->getTimestamp(); // conflicting edit $page->doEditContent( new WikitextContent( "Foo bar" ), - "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user ); + "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() ); $this->forceRevisionDate( $page, '20120101020202' ); // try to save edit, expect conflict @@ -388,7 +366,7 @@ class ApiEditPageTest extends ApiTestCase { 'title' => $name, 'text' => 'nix bar!', 'basetimestamp' => $baseTime, - ), null, self::$users['sysop']->user ); + ), null, self::$users['sysop']->getUser() ); $this->fail( 'edit conflict expected' ); } catch ( UsageException $ex ) { @@ -411,13 +389,13 @@ class ApiEditPageTest extends ApiTestCase { // base edit $page->doEditContent( new WikitextContent( "Foo" ), - "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() ); $this->forceRevisionDate( $page, '20120101000000' ); $baseTime = $page->getRevision()->getTimestamp(); // conflicting edit $page->doEditContent( new WikitextContent( "Foo bar" ), - "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user ); + "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() ); $this->forceRevisionDate( $page, '20120101020202' ); // try to save edit, expect no conflict @@ -427,7 +405,7 @@ class ApiEditPageTest extends ApiTestCase { 'text' => 'nix bar!', 'basetimestamp' => $baseTime, 'section' => 'new', - ), null, self::$users['sysop']->user ); + ), null, self::$users['sysop']->getUser() ); $this->assertEquals( 'Success', $re['edit']['result'], "no edit conflict expected here" ); @@ -454,17 +432,17 @@ class ApiEditPageTest extends ApiTestCase { // base edit for content $page->doEditContent( new WikitextContent( "Foo" ), - "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() ); $this->forceRevisionDate( $page, '20120101000000' ); // base edit for redirect $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]" ), - "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() ); $this->forceRevisionDate( $rpage, '20120101000000' ); // new edit to content $page->doEditContent( new WikitextContent( "Foo bar" ), - "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user ); + "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() ); $this->forceRevisionDate( $rpage, '20120101020202' ); // try to save edit; should work, following the redirect. @@ -474,7 +452,7 @@ class ApiEditPageTest extends ApiTestCase { 'text' => 'nix bar!', 'section' => 'new', 'redirect' => true, - ), null, self::$users['sysop']->user ); + ), null, self::$users['sysop']->getUser() ); $this->assertEquals( 'Success', $re['edit']['result'], "no edit conflict expected here" ); @@ -493,4 +471,45 @@ class ApiEditPageTest extends ApiTestCase { $page->clear(); } + + public function testCheckDirectApiEditingDisallowed_forNonTextContent() { + $this->setExpectedException( + 'UsageException', + 'Direct editing via API is not supported for content model testing used by Dummy:ApiEditPageTest_nonTextPageEdit' + ); + + $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => 'Dummy:ApiEditPageTest_nonTextPageEdit', + 'text' => '{"animals":["kittens!"]}' + ) ); + } + + public function testSupportsDirectApiEditing_withContentHandlerOverride() { + $name = 'DummyNonText:ApiEditPageTest_testNonTextEdit'; + $data = serialize( 'some bla bla text' ); + + $result = $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $name, + 'text' => $data, + ) ); + + $apiResult = $result[0]; + + // Validate API result data + $this->assertArrayHasKey( 'edit', $apiResult ); + $this->assertArrayHasKey( 'result', $apiResult['edit'] ); + $this->assertEquals( 'Success', $apiResult['edit']['result'] ); + + $this->assertArrayHasKey( 'new', $apiResult['edit'] ); + $this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] ); + + $this->assertArrayHasKey( 'pageid', $apiResult['edit'] ); + + // validate resulting revision + $page = WikiPage::factory( Title::newFromText( $name ) ); + $this->assertEquals( "testing-nontext", $page->getContentModel() ); + $this->assertEquals( $data, $page->getContent()->serialize() ); + } } diff --git a/tests/phpunit/includes/api/ApiLoginTest.php b/tests/phpunit/includes/api/ApiLoginTest.php index 88a99e9b..7dfd14f3 100644 --- a/tests/phpunit/includes/api/ApiLoginTest.php +++ b/tests/phpunit/includes/api/ApiLoginTest.php @@ -23,7 +23,7 @@ class ApiLoginTest extends ApiTestCase { global $wgServer; $user = self::$users['sysop']; - $user->user->logOut(); + $user->getUser()->logOut(); if ( !isset( $wgServer ) ) { $this->markTestIncomplete( 'This test needs $wgServer to be set in LocalSettings.php' ); @@ -68,7 +68,7 @@ class ApiLoginTest extends ApiTestCase { } $user = self::$users['sysop']; - $user->user->logOut(); + $user->getUser()->logOut(); $ret = $this->doApiRequest( array( "action" => "login", diff --git a/tests/phpunit/includes/api/ApiMainTest.php b/tests/phpunit/includes/api/ApiMainTest.php index e8ef1804..94b741dc 100644 --- a/tests/phpunit/includes/api/ApiMainTest.php +++ b/tests/phpunit/includes/api/ApiMainTest.php @@ -71,7 +71,7 @@ class ApiMainTest extends ApiTestCase { new FauxRequest( array( 'action' => 'query', 'meta' => 'siteinfo' ) ) ); $modules = $api->getModuleManager()->getNamesWithClasses(); - foreach( $modules as $name => $class ) { + foreach ( $modules as $name => $class ) { $this->assertArrayHasKey( $class, $classes, @@ -79,4 +79,173 @@ class ApiMainTest extends ApiTestCase { ); } } + + /** + * Test HTTP precondition headers + * + * @covers ApiMain::checkConditionalRequestHeaders + * @dataProvider provideCheckConditionalRequestHeaders + * @param array $headers HTTP headers + * @param array $conditions Return data for ApiBase::getConditionalRequestData + * @param int $status Expected response status + * @param bool $post Request is a POST + */ + public function testCheckConditionalRequestHeaders( $headers, $conditions, $status, $post = false ) { + $request = new FauxRequest( array( 'action' => 'query', 'meta' => 'siteinfo' ), $post ); + $request->setHeaders( $headers ); + $request->response()->statusHeader( 200 ); // Why doesn't it default? + + $api = new ApiMain( $request ); + $priv = TestingAccessWrapper::newFromObject( $api ); + $priv->mInternalMode = false; + + $module = $this->getMockBuilder( 'ApiBase' ) + ->setConstructorArgs( array( $api, 'mock' ) ) + ->setMethods( array( 'getConditionalRequestData' ) ) + ->getMockForAbstractClass(); + $module->expects( $this->any() ) + ->method( 'getConditionalRequestData' ) + ->will( $this->returnCallback( function ( $condition ) use ( $conditions ) { + return isset( $conditions[$condition] ) ? $conditions[$condition] : null; + } ) ); + + $ret = $priv->checkConditionalRequestHeaders( $module ); + + $this->assertSame( $status, $request->response()->getStatusCode() ); + $this->assertSame( $status === 200, $ret ); + } + + public static function provideCheckConditionalRequestHeaders() { + $now = time(); + + return array( + // Non-existing from module is ignored + array( array( 'If-None-Match' => '"foo", "bar"' ), array(), 200 ), + array( array( 'If-Modified-Since' => 'Tue, 18 Aug 2015 00:00:00 GMT' ), array(), 200 ), + + // No headers + array( + array(), + array( + 'etag' => '""', + 'last-modified' => '20150815000000', + ), + 200 + ), + + // Basic If-None-Match + array( array( 'If-None-Match' => '"foo", "bar"' ), array( 'etag' => '"bar"' ), 304 ), + array( array( 'If-None-Match' => '"foo", "bar"' ), array( 'etag' => '"baz"' ), 200 ), + array( array( 'If-None-Match' => '"foo"' ), array( 'etag' => 'W/"foo"' ), 304 ), + array( array( 'If-None-Match' => 'W/"foo"' ), array( 'etag' => '"foo"' ), 304 ), + array( array( 'If-None-Match' => 'W/"foo"' ), array( 'etag' => 'W/"foo"' ), 304 ), + + // Pointless, but supported + array( array( 'If-None-Match' => '*' ), array(), 304 ), + + // Basic If-Modified-Since + array( array( 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ), + array( 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ), 304 ), + array( array( 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ), + array( 'last-modified' => wfTimestamp( TS_MW, $now ) ), 304 ), + array( array( 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ), + array( 'last-modified' => wfTimestamp( TS_MW, $now + 1 ) ), 200 ), + + // If-Modified-Since ignored when If-None-Match is given too + array( array( 'If-None-Match' => '""', 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ), + array( 'etag' => '"x"', 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ), 200 ), + array( array( 'If-None-Match' => '""', 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ), + array( 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ), 304 ), + + // Ignored for POST + array( array( 'If-None-Match' => '"foo", "bar"' ), array( 'etag' => '"bar"' ), 200, true ), + array( array( 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) ), + array( 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ), 200, true ), + + // Other date formats allowed by the RFC + array( array( 'If-Modified-Since' => gmdate( 'l, d-M-y H:i:s', $now ) . ' GMT' ), + array( 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ), 304 ), + array( array( 'If-Modified-Since' => gmdate( 'D M j H:i:s Y', $now ) ), + array( 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ), 304 ), + + // Old browser extension to HTTP/1.0 + array( array( 'If-Modified-Since' => wfTimestamp( TS_RFC2822, $now ) . '; length=123' ), + array( 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ), 304 ), + + // Invalid date formats should be ignored + array( array( 'If-Modified-Since' => gmdate( 'Y-m-d H:i:s', $now ) . ' GMT' ), + array( 'last-modified' => wfTimestamp( TS_MW, $now - 1 ) ), 200 ), + ); + } + + /** + * Test conditional headers output + * @dataProvider provideConditionalRequestHeadersOutput + * @param array $conditions Return data for ApiBase::getConditionalRequestData + * @param array $headers Expected output headers + * @param bool $isError $isError flag + * @param bool $post Request is a POST + */ + public function testConditionalRequestHeadersOutput( $conditions, $headers, $isError = false, $post = false ) { + $request = new FauxRequest( array( 'action' => 'query', 'meta' => 'siteinfo' ), $post ); + $response = $request->response(); + + $api = new ApiMain( $request ); + $priv = TestingAccessWrapper::newFromObject( $api ); + $priv->mInternalMode = false; + + $module = $this->getMockBuilder( 'ApiBase' ) + ->setConstructorArgs( array( $api, 'mock' ) ) + ->setMethods( array( 'getConditionalRequestData' ) ) + ->getMockForAbstractClass(); + $module->expects( $this->any() ) + ->method( 'getConditionalRequestData' ) + ->will( $this->returnCallback( function ( $condition ) use ( $conditions ) { + return isset( $conditions[$condition] ) ? $conditions[$condition] : null; + } ) ); + $priv->mModule = $module; + + $priv->sendCacheHeaders( $isError ); + + foreach ( array( 'Last-Modified', 'ETag' ) as $header ) { + $this->assertEquals( + isset( $headers[$header] ) ? $headers[$header] : null, + $response->getHeader( $header ), + $header + ); + } + } + + public static function provideConditionalRequestHeadersOutput() { + return array( + array( + array(), + array() + ), + array( + array( 'etag' => '"foo"' ), + array( 'ETag' => '"foo"' ) + ), + array( + array( 'last-modified' => '20150818000102' ), + array( 'Last-Modified' => 'Tue, 18 Aug 2015 00:01:02 GMT' ) + ), + array( + array( 'etag' => '"foo"', 'last-modified' => '20150818000102' ), + array( 'ETag' => '"foo"', 'Last-Modified' => 'Tue, 18 Aug 2015 00:01:02 GMT' ) + ), + array( + array( 'etag' => '"foo"', 'last-modified' => '20150818000102' ), + array(), + true, + ), + array( + array( 'etag' => '"foo"', 'last-modified' => '20150818000102' ), + array(), + false, + true, + ), + ); + } + } diff --git a/tests/phpunit/includes/api/ApiMessageTest.php b/tests/phpunit/includes/api/ApiMessageTest.php index 6c3ce60d..08a984eb 100644 --- a/tests/phpunit/includes/api/ApiMessageTest.php +++ b/tests/phpunit/includes/api/ApiMessageTest.php @@ -14,9 +14,13 @@ class ApiMessageTest extends MediaWikiTestCase { $msg = TestingAccessWrapper::newFromObject( $msg ); $msg2 = TestingAccessWrapper::newFromObject( $msg2 ); - foreach ( array( 'interface', 'useDatabase', 'title' ) as $key ) { - $this->assertSame( $msg->$key, $msg2->$key, $key ); - } + $this->assertSame( $msg->interface, $msg2->interface, 'interface' ); + $this->assertSame( $msg->useDatabase, $msg2->useDatabase, 'useDatabase' ); + $this->assertSame( + $msg->title ? $msg->title->getFullText() : null, + $msg2->title ? $msg2->title->getFullText() : null, + 'title' + ); } /** @@ -30,6 +34,11 @@ class ApiMessageTest extends MediaWikiTestCase { $this->assertEquals( 'code', $msg2->getApiCode() ); $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + $msg2 = unserialize( serialize( $msg2 ) ); + $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 ); @@ -63,6 +72,11 @@ class ApiMessageTest extends MediaWikiTestCase { $this->assertEquals( 'code', $msg2->getApiCode() ); $this->assertEquals( array( 'data' ), $msg2->getApiData() ); + $msg2 = unserialize( serialize( $msg2 ) ); + $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 ); diff --git a/tests/phpunit/includes/api/ApiQueryAllPagesTest.php b/tests/phpunit/includes/api/ApiQueryAllPagesTest.php index 124988f3..0ac00eea 100644 --- a/tests/phpunit/includes/api/ApiQueryAllPagesTest.php +++ b/tests/phpunit/includes/api/ApiQueryAllPagesTest.php @@ -13,9 +13,11 @@ class ApiQueryAllPagesTest extends ApiTestCase { } /** - * @todo give this test a real name explaining what is being tested here + *Test bug 25702 + *Prefixes of API search requests are not handled with case sensitivity and may result + *in wrong search results */ - public function testBug25702() { + public function testPrefixNormalizationSearchBug() { $title = Title::newFromText( 'Category:Template:xyz' ); $page = WikiPage::factory( $title ); $page->doEdit( 'Some text', 'inserting content' ); diff --git a/tests/phpunit/includes/api/ApiResultTest.php b/tests/phpunit/includes/api/ApiResultTest.php index f0d84552..2f31677e 100644 --- a/tests/phpunit/includes/api/ApiResultTest.php +++ b/tests/phpunit/includes/api/ApiResultTest.php @@ -181,6 +181,19 @@ class ApiResultTest extends MediaWikiTestCase { ); } + ApiResult::setValue( $arr, null, NAN, ApiResult::NO_VALIDATE ); + + try { + ApiResult::setValue( $arr, null, NAN, ApiResult::NO_SIZE_CHECK ); + $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' ); @@ -408,6 +421,19 @@ class ApiResultTest extends MediaWikiTestCase { ); } + $result->addValue( null, null, NAN, ApiResult::NO_VALIDATE ); + + try { + $result->addValue( null, null, NAN, ApiResult::NO_SIZE_CHECK ); + $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( @@ -444,6 +470,12 @@ class ApiResultTest extends MediaWikiTestCase { $result->removeValue( null, 'foo' ); $this->assertTrue( $result->addValue( null, 'foo', '1' ) ); + $result = new ApiResult( 10 ); + $obj = new ApiResultTestSerializableObject( 'ok' ); + $obj->foobar = 'foobaz'; + $this->assertTrue( $result->addValue( null, 'foo', $obj ) ); + $this->assertSame( 2, $result->getSize() ); + $result = new ApiResult( 8388608 ); $result2 = new ApiResult( 8388608 ); $result2->addValue( null, 'foo', 'bar' ); @@ -674,6 +706,10 @@ class ApiResultTest extends MediaWikiTestCase { ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key', ), + 'kvpmerge' => array( 'x' => 'a', 'y' => array( 'b' ), 'z' => array( 'c' => 'd' ), + ApiResult::META_TYPE => 'kvp', + ApiResult::META_KVP_MERGE => true, + ), 'emptyDefault' => array( '_dummy' => 1 ), 'emptyAssoc' => array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), '_dummy' => 1, @@ -858,6 +894,13 @@ class ApiResultTest extends MediaWikiTestCase { ApiResult::META_TYPE => 'assoc', ApiResult::META_KVP_KEY_NAME => 'key', ), + 'kvpmerge' => array( + 'x' => 'a', + 'y' => array( 'b', ApiResult::META_TYPE => 'array' ), + 'z' => array( 'c' => 'd', ApiResult::META_TYPE => 'assoc' ), + ApiResult::META_TYPE => 'assoc', + ApiResult::META_KVP_MERGE => true, + ), 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), 'emptyAssoc' => array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), '_dummy' => 1, @@ -871,8 +914,12 @@ class ApiResultTest extends MediaWikiTestCase { 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' ), + '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' ), @@ -885,6 +932,13 @@ class ApiResultTest extends MediaWikiTestCase { ApiResult::META_TYPE => 'assoc', ApiResult::META_KVP_KEY_NAME => 'key', ), + 'kvpmerge' => (object)array( + 'x' => 'a', + 'y' => array( 'b', ApiResult::META_TYPE => 'array' ), + 'z' => (object)array( 'c' => 'd', ApiResult::META_TYPE => 'assoc' ), + ApiResult::META_TYPE => 'assoc', + ApiResult::META_KVP_MERGE => true, + ), 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), 'emptyAssoc' => (object)array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), '_dummy' => 1, @@ -916,6 +970,13 @@ class ApiResultTest extends MediaWikiTestCase { ApiResult::META_TYPE => 'array', ApiResult::META_KVP_KEY_NAME => 'key', ), + 'kvpmerge' => array( + $kvp( 'name', 'x', 'value', 'a' ), + $kvp( 'name', 'y', 'value', array( 'b', ApiResult::META_TYPE => 'array' ) ), + array( 'name' => 'z', 'c' => 'd', ApiResult::META_TYPE => 'assoc', ApiResult::META_PRESERVE_KEYS => array( 'name' ) ), + ApiResult::META_TYPE => 'array', + ApiResult::META_KVP_MERGE => true, + ), 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), 'emptyAssoc' => array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), '_dummy' => 1, @@ -947,6 +1008,13 @@ class ApiResultTest extends MediaWikiTestCase { ApiResult::META_TYPE => 'array', ApiResult::META_KVP_KEY_NAME => 'key', ), + 'kvpmerge' => array( + $kvp( 'name', 'x', '*', 'a' ), + $kvp( 'name', 'y', '*', array( 'b', ApiResult::META_TYPE => 'array' ) ), + array( 'name' => 'z', 'c' => 'd', ApiResult::META_TYPE => 'assoc', ApiResult::META_PRESERVE_KEYS => array( 'name' ) ), + ApiResult::META_TYPE => 'array', + ApiResult::META_KVP_MERGE => true, + ), 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), 'emptyAssoc' => array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), '_dummy' => 1, @@ -960,8 +1028,12 @@ class ApiResultTest extends MediaWikiTestCase { 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' ), + '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' ), @@ -978,6 +1050,13 @@ class ApiResultTest extends MediaWikiTestCase { ApiResult::META_TYPE => 'array', ApiResult::META_KVP_KEY_NAME => 'key', ), + 'kvpmerge' => array( + (object)$kvp( 'name', 'x', 'value', 'a' ), + (object)$kvp( 'name', 'y', 'value', array( 'b', ApiResult::META_TYPE => 'array' ) ), + (object)array( 'name' => 'z', 'c' => 'd', ApiResult::META_TYPE => 'assoc', ApiResult::META_PRESERVE_KEYS => array( 'name' ) ), + ApiResult::META_TYPE => 'array', + ApiResult::META_KVP_MERGE => true, + ), 'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ), 'emptyAssoc' => (object)array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ), '_dummy' => 1, @@ -1017,6 +1096,11 @@ class ApiResultTest extends MediaWikiTestCase { (object)array( 'key' => 'x', 'value' => 'a' ), (object)array( 'key' => 'y', 'value' => 'b' ), ), + 'kvpmerge' => array( + (object)array( 'name' => 'x', 'value' => 'a' ), + (object)array( 'name' => 'y', 'value' => array( 'b' ) ), + (object)array( 'name' => 'z', 'c' => 'd' ), + ), 'emptyDefault' => array(), 'emptyAssoc' => (object)array(), '_dummy' => 1, @@ -1127,13 +1211,84 @@ class ApiResultTest extends MediaWikiTestCase { /** * @covers ApiResult */ + public function testAddMetadataToResultVars() { + $arr = array( + 'a' => "foo", + 'b' => false, + 'c' => 10, + 'sequential_numeric_keys' => array( 'a', 'b', 'c' ), + 'non_sequential_numeric_keys' => array( 'a', 'b', 4 => 'c' ), + 'string_keys' => array( + 'one' => 1, + 'two' => 2 + ), + 'object_sequential_keys' => (object)array( 'a', 'b', 'c' ), + '_type' => "should be overwritten in result", + ); + $this->assertSame( array( + ApiResult::META_TYPE => 'kvp', + ApiResult::META_KVP_KEY_NAME => 'key', + ApiResult::META_PRESERVE_KEYS => array( + 'a', 'b', 'c', + 'sequential_numeric_keys', 'non_sequential_numeric_keys', + 'string_keys', 'object_sequential_keys' + ), + ApiResult::META_BC_BOOLS => array( 'b' ), + ApiResult::META_INDEXED_TAG_NAME => 'var', + 'a' => "foo", + 'b' => false, + 'c' => 10, + 'sequential_numeric_keys' => array( + ApiResult::META_TYPE => 'array', + ApiResult::META_BC_BOOLS => array(), + ApiResult::META_INDEXED_TAG_NAME => 'value', + 0 => 'a', + 1 => 'b', + 2 => 'c', + ), + 'non_sequential_numeric_keys' => array( + ApiResult::META_TYPE => 'kvp', + ApiResult::META_KVP_KEY_NAME => 'key', + ApiResult::META_PRESERVE_KEYS => array( 0, 1, 4 ), + ApiResult::META_BC_BOOLS => array(), + ApiResult::META_INDEXED_TAG_NAME => 'var', + 0 => 'a', + 1 => 'b', + 4 => 'c', + ), + 'string_keys' => array( + ApiResult::META_TYPE => 'kvp', + ApiResult::META_KVP_KEY_NAME => 'key', + ApiResult::META_PRESERVE_KEYS => array( 'one', 'two' ), + ApiResult::META_BC_BOOLS => array(), + ApiResult::META_INDEXED_TAG_NAME => 'var', + 'one' => 1, + 'two' => 2, + ), + 'object_sequential_keys' => array( + ApiResult::META_TYPE => 'kvp', + ApiResult::META_KVP_KEY_NAME => 'key', + ApiResult::META_PRESERVE_KEYS => array( 0, 1, 2 ), + ApiResult::META_BC_BOOLS => array(), + ApiResult::META_INDEXED_TAG_NAME => 'var', + 0 => 'a', + 1 => 'b', + 2 => 'c', + ), + ), ApiResult::addMetadataToResultVars( $arr ) ); + } + + /** + * @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 ) ) { + if ( preg_match( '/Use of ApiMain to ApiResult::__construct ' . + 'was deprecated in MediaWiki \d+.\d+\./', $errstr ) ) { return true; } return false; @@ -1166,17 +1321,6 @@ class ApiResultTest extends MediaWikiTestCase { ), '*' => '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' ); @@ -1451,7 +1595,8 @@ class ApiResultTest extends MediaWikiTestCase { $result = new ApiResult( 8388608 ); $result->setMainForContinuation( $main ); - $result->beginContinuation( '||mock2', array_slice( $allModules, 0, 2 ), array( 'mock1', 'mock2' ) ); + $result->beginContinuation( '||mock2', array_slice( $allModules, 0, 2 ), + array( 'mock1', 'mock2' ) ); try { $result->setContinueParam( $allModules[1], 'm2continue', 1 ); $this->fail( 'Expected exception not thrown' ); @@ -1467,7 +1612,8 @@ class ApiResultTest extends MediaWikiTestCase { $this->fail( 'Expected exception not thrown' ); } catch ( UnexpectedValueException $ex ) { $this->assertSame( - 'Module \'mocklist\' called ApiContinuationManager::addContinueParam but was not passed to ApiContinuationManager::__construct', + 'Module \'mocklist\' called ApiContinuationManager::addContinueParam ' . + 'but was not passed to ApiContinuationManager::__construct', $ex->getMessage(), 'Expected exception' ); @@ -1495,13 +1641,14 @@ class ApiResultTest extends MediaWikiTestCase { try { $arr = array(); - ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( + 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', + 'ApiResultTestSerializableObject::serializeForApiResult() ' . + 'returned an object of class ApiResultTestStringifiableObject', $ex->getMessage(), 'Expected exception' ); @@ -1509,18 +1656,19 @@ class ApiResultTest extends MediaWikiTestCase { try { $arr = array(); - ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( NAN ) ); + 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', + '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( + ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( array( 'one' => new ApiResultTestStringifiableObject( '1' ), 'two' => new ApiResultTestSerializableObject( 2 ), diff --git a/tests/phpunit/includes/api/ApiRevisionDeleteTest.php b/tests/phpunit/includes/api/ApiRevisionDeleteTest.php index b03836eb..362d647f 100644 --- a/tests/phpunit/includes/api/ApiRevisionDeleteTest.php +++ b/tests/phpunit/includes/api/ApiRevisionDeleteTest.php @@ -25,7 +25,7 @@ class ApiRevisionDeleteTest extends ApiTestCase { } public function testHidingRevisions() { - $user = self::$users['sysop']->user; + $user = self::$users['sysop']->getUser(); $revid = array_shift( $this->revs ); $out = $this->doApiRequest( array( 'action' => 'revisiondelete', @@ -80,7 +80,7 @@ class ApiRevisionDeleteTest extends ApiTestCase { } public function testUnhidingOutput() { - $user = self::$users['sysop']->user; + $user = self::$users['sysop']->getUser(); $revid = array_shift( $this->revs ); // Hide revisions $this->doApiRequest( array( diff --git a/tests/phpunit/includes/api/ApiTestCase.php b/tests/phpunit/includes/api/ApiTestCase.php index da62bb0a..21345ac1 100644 --- a/tests/phpunit/includes/api/ApiTestCase.php +++ b/tests/phpunit/includes/api/ApiTestCase.php @@ -105,6 +105,7 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { $wgRequest = new FauxRequest( $params, true, $session ); RequestContext::getMain()->setRequest( $wgRequest ); + RequestContext::getMain()->setUser( $wgUser ); // set up local environment $context = $this->apiContext->newTestContext( $wgRequest, $wgUser ); diff --git a/tests/phpunit/includes/api/ApiUnblockTest.php b/tests/phpunit/includes/api/ApiUnblockTest.php index 2c2370a8..a374f094 100644 --- a/tests/phpunit/includes/api/ApiUnblockTest.php +++ b/tests/phpunit/includes/api/ApiUnblockTest.php @@ -16,7 +16,7 @@ class ApiUnblockTest extends ApiTestCase { /** * @expectedException UsageException */ - public function testWithNoToken( ) { + public function testWithNoToken() { $this->doApiRequest( array( 'action' => 'unblock', @@ -25,7 +25,7 @@ class ApiUnblockTest extends ApiTestCase { ), null, false, - self::$users['sysop']->user + self::$users['sysop']->getUser() ); } } diff --git a/tests/phpunit/includes/api/ApiUploadTest.php b/tests/phpunit/includes/api/ApiUploadTest.php index f74fc354..c852d72b 100644 --- a/tests/phpunit/includes/api/ApiUploadTest.php +++ b/tests/phpunit/includes/api/ApiUploadTest.php @@ -80,7 +80,7 @@ class ApiUploadTest extends ApiTestCaseUpload { try { $this->doApiRequestWithToken( array( 'action' => 'upload', - ), $session, self::$users['uploader']->user ); + ), $session, self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $exception = true; $this->assertEquals( "One of the parameters filekey, file, url, statuskey is required", @@ -126,7 +126,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { list( $result, , ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); + self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $exception = true; } @@ -165,7 +165,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { - $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->user ); + $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $this->assertContains( 'The file you submitted was empty', $e->getMessage() ); $exception = true; @@ -215,7 +215,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); + self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $exception = true; } @@ -232,7 +232,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { list( $result, , ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); // FIXME: leaks a temporary file + self::$users['uploader']->getUser() ); // FIXME: leaks a temporary file } catch ( UsageException $e ) { $exception = true; } @@ -286,7 +286,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); + self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $exception = true; } @@ -311,7 +311,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { list( $result ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); // FIXME: leaks a temporary file + self::$users['uploader']->getUser() ); // FIXME: leaks a temporary file } catch ( UsageException $e ) { $exception = true; } @@ -331,7 +331,7 @@ class ApiUploadTest extends ApiTestCaseUpload { */ public function testUploadStash( $session ) { $this->setMwGlobals( array( - 'wgUser' => self::$users['uploader']->user, // @todo FIXME: still used somewhere + 'wgUser' => self::$users['uploader']->getUser(), // @todo FIXME: still used somewhere ) ); $extension = 'png'; @@ -368,7 +368,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); // FIXME: leaks a temporary file + self::$users['uploader']->getUser() ); // FIXME: leaks a temporary file } catch ( UsageException $e ) { $exception = true; } @@ -397,7 +397,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { list( $result ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); + self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $exception = true; } @@ -415,7 +415,7 @@ class ApiUploadTest extends ApiTestCaseUpload { public function testUploadChunks( $session ) { $this->setMwGlobals( array( // @todo FIXME: still used somewhere - 'wgUser' => self::$users['uploader']->user, + 'wgUser' => self::$users['uploader']->getUser(), ) ); $chunkSize = 1048576; @@ -451,9 +451,9 @@ class ApiUploadTest extends ApiTestCaseUpload { $chunkSessionKey = false; $resultOffset = 0; // Open the file: - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $handle = fopen( $filePath, "r" ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( $handle === false ) { $this->markTestIncomplete( "could not open file: $filePath" ); @@ -461,9 +461,9 @@ class ApiUploadTest extends ApiTestCaseUpload { while ( !feof( $handle ) ) { // Get the current chunk - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $chunkData = fread( $handle, $chunkSize ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); // Upload the current chunk into the $_FILE object: $this->fakeUploadChunk( 'chunk', 'blob', $mimeType, $chunkData ); @@ -473,7 +473,7 @@ class ApiUploadTest extends ApiTestCaseUpload { // Upload fist chunk ( and get the session key ) try { list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); + self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -501,7 +501,7 @@ class ApiUploadTest extends ApiTestCaseUpload { // Upload current chunk try { list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); + self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -541,7 +541,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $exception = false; try { list( $result ) = $this->doApiRequestWithToken( $params, $session, - self::$users['uploader']->user ); + self::$users['uploader']->getUser() ); } catch ( UsageException $e ) { $exception = true; } diff --git a/tests/phpunit/includes/api/format/ApiFormatDumpTest.php b/tests/phpunit/includes/api/format/ApiFormatDumpTest.php deleted file mode 100644 index c0f67f8d..00000000 --- a/tests/phpunit/includes/api/format/ApiFormatDumpTest.php +++ /dev/null @@ -1,63 +0,0 @@ -<?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/ApiFormatWddxTest.php b/tests/phpunit/includes/api/format/ApiFormatWddxTest.php deleted file mode 100644 index 07111300..00000000 --- a/tests/phpunit/includes/api/format/ApiFormatWddxTest.php +++ /dev/null @@ -1,80 +0,0 @@ -<?php - -/** - * @group API - * @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}" ), - ); - } - - /** - * @dataProvider provideEncoding - */ - 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' ) ); - } -} - -class ApiFormatWddxTest_SlowWddx extends ApiFormatWddx { - public static function useSlowPrinter() { - return true; - } -} diff --git a/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php b/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php index ce2f70de..db61bc80 100644 --- a/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php +++ b/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php @@ -57,10 +57,9 @@ abstract class ApiQueryContinueTestBase extends ApiQueryTestBase { } else { $params['action'] = 'query'; } - if ( $useContinue && !isset( $params['continue'] ) ) { + // Silence warning + if ( !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 5f061b50..61b992ba 100644 --- a/tests/phpunit/includes/api/query/ApiQueryTest.php +++ b/tests/phpunit/includes/api/query/ApiQueryTest.php @@ -131,7 +131,7 @@ class ApiQueryTest extends ApiTestCase { ); $queryApi = new ApiQuery( $api, 'query' ); $modules = $queryApi->getModuleManager()->getNamesWithClasses(); - foreach( $modules as $name => $class ) { + foreach ( $modules as $name => $class ) { $this->assertArrayHasKey( $class, $classes, diff --git a/tests/phpunit/includes/api/query/ApiQueryTestBase.php b/tests/phpunit/includes/api/query/ApiQueryTestBase.php index dabf72e0..d5fa4542 100644 --- a/tests/phpunit/includes/api/query/ApiQueryTestBase.php +++ b/tests/phpunit/includes/api/query/ApiQueryTestBase.php @@ -56,12 +56,12 @@ STR; * @return array */ private function validateRequestExpectedPair( $v ) { - $this->assertType( 'array', $v, self::PARAM_ASSERT ); + $this->assertInternalType( 'array', $v, self::PARAM_ASSERT ); $this->assertEquals( 2, count( $v ), self::PARAM_ASSERT ); $this->assertArrayHasKey( 0, $v, self::PARAM_ASSERT ); $this->assertArrayHasKey( 1, $v, self::PARAM_ASSERT ); - $this->assertType( 'array', $v[0], self::PARAM_ASSERT ); - $this->assertType( 'array', $v[1], self::PARAM_ASSERT ); + $this->assertInternalType( 'array', $v[0], self::PARAM_ASSERT ); + $this->assertInternalType( 'array', $v[1], self::PARAM_ASSERT ); return $v; } @@ -87,6 +87,7 @@ STR; /** * Checks that the request's result matches the expected results. + * Assumes no rawcontinue and a complete batch. * @param array $values Array is a two element array( request, expected_results ) * @param array $session * @param bool $appendModule @@ -99,8 +100,9 @@ STR; if ( !array_key_exists( 'action', $req ) ) { $req['action'] = 'query'; } - if ( !array_key_exists( 'continue', $req ) ) { - $req['rawcontinue'] = '1'; + // Silence warning + if ( !isset( $params['continue'] ) ) { + $params['continue'] = ''; } foreach ( $req as &$val ) { if ( is_array( $val ) ) { @@ -108,7 +110,7 @@ STR; } } $result = $this->doApiRequest( $req, $session, $appendModule, $user ); - $this->assertResult( array( 'query' => $exp ), $result[0], $req ); + $this->assertResult( array( 'batchcomplete' => true, 'query' => $exp ), $result[0], $req ); } protected function assertResult( $exp, $result, $message = '' ) { diff --git a/tests/phpunit/includes/cache/MessageCacheTest.php b/tests/phpunit/includes/cache/MessageCacheTest.php index 442e9f9f..5302b363 100644 --- a/tests/phpunit/includes/cache/MessageCacheTest.php +++ b/tests/phpunit/includes/cache/MessageCacheTest.php @@ -52,7 +52,7 @@ class MessageCacheTest extends MediaWikiLangTestCase { $this->makePage( 'MessageCacheTest-FullKeyTest', 'ru' ); // In content language -- get base if no derivative - $this->makePage( 'FallbackLanguageTest-NoDervContLang', 'de', 'de/none', false ); + $this->makePage( 'FallbackLanguageTest-NoDervContLang', 'de', 'de/none' ); } /** @@ -61,15 +61,14 @@ class MessageCacheTest extends MediaWikiLangTestCase { * @param string $title Title of page to be created * @param string $lang Language and content of the created page * @param string|null $content Content of the created page, or null for a generic string - * @param bool $createSubPage Set to false if a root page should be created */ - protected function makePage( $title, $lang, $content = null, $createSubPage = true ) { + protected function makePage( $title, $lang, $content = null ) { global $wgContLang; if ( $content === null ) { $content = $lang; } - if ( $lang !== $wgContLang->getCode() || $createSubPage ) { + if ( $lang !== $wgContLang->getCode() ) { $title = "$title/$lang"; } @@ -125,4 +124,26 @@ class MessageCacheTest extends MediaWikiLangTestCase { ); } + /** + * @dataProvider provideNormalizeKey + */ + public function testNormalizeKey( $key, $expected ) { + $actual = MessageCache::normalizeKey( $key ); + $this->assertEquals( $expected, $actual ); + } + + public function provideNormalizeKey() { + return array( + array( 'Foo', 'foo' ), + array( 'foo', 'foo' ), + array( 'fOo', 'fOo' ), + array( 'FOO', 'fOO' ), + array( 'Foo bar', 'foo_bar' ), + array( 'Ćab', 'ćab' ), + array( 'Ćab_e 3', 'ćab_e_3' ), + array( 'ĆAB', 'ćAB' ), + array( 'ćab', 'ćab' ), + array( 'ćaB', 'ćaB' ), + ); + } } diff --git a/tests/phpunit/includes/changes/RecentChangeTest.php b/tests/phpunit/includes/changes/RecentChangeTest.php index b3cb7b52..4d1a936e 100644 --- a/tests/phpunit/includes/changes/RecentChangeTest.php +++ b/tests/phpunit/includes/changes/RecentChangeTest.php @@ -10,8 +10,8 @@ class RecentChangeTest extends MediaWikiTestCase { protected $user_comment; protected $context; - public function __construct() { - parent::__construct(); + public function setUp() { + parent::setUp(); $this->title = Title::newFromText( 'SomeTitle' ); $this->target = Title::newFromText( 'TestTarget' ); @@ -22,6 +22,26 @@ class RecentChangeTest extends MediaWikiTestCase { } /** + * @covers RecentChange::newFromRow + * @covers RecentChange::loadFromRow + */ + public function testNewFromRow() { + $row = new stdClass(); + $row->rc_foo = 'AAA'; + $row->rc_timestamp = '20150921134808'; + $row->rc_deleted = 'bar'; + + $rc = RecentChange::newFromRow( $row ); + + $expected = array( + 'rc_foo' => 'AAA', + 'rc_timestamp' => '20150921134808', + 'rc_deleted' => 'bar', + ); + $this->assertEquals( $expected, $rc->getAttributes() ); + } + + /** * The testIrcMsgForAction* tests are supposed to cover the hacky * LogFormatter::getIRCActionText / bug 34508 * @@ -46,6 +66,7 @@ class RecentChangeTest extends MediaWikiTestCase { * - protect/protect * - protect/modifyprotect * - protect/unprotect + * - protect/move_prot * - upload/upload * - merge/merge * - import/upload @@ -59,289 +80,95 @@ class RecentChangeTest extends MediaWikiTestCase { */ /** - * @covers LogFormatter::getIRCActionText - */ - public function testIrcMsgForLogTypeBlock() { - $sep = $this->context->msg( 'colon-separator' )->text(); - - # block/block - $this->assertIRCComment( - $this->context->msg( 'blocklogentry', 'SomeTitle', 'duration', '(flags)' )->plain() - . $sep . $this->user_comment, - 'block', 'block', - array( - '5::duration' => 'duration', - '6::flags' => 'flags', - ), - $this->user_comment - ); - # block/unblock - $this->assertIRCComment( - $this->context->msg( 'unblocklogentry', 'SomeTitle' )->plain() . $sep . $this->user_comment, - 'block', 'unblock', - array(), - $this->user_comment - ); - # block/reblock - $this->assertIRCComment( - $this->context->msg( 'reblock-logentry', 'SomeTitle', 'duration', '(flags)' )->plain() - . $sep . $this->user_comment, - 'block', 'reblock', - array( - '5::duration' => 'duration', - '6::flags' => 'flags', - ), - $this->user_comment - ); - } - - /** - * @covers LogFormatter::getIRCActionText - */ - public function testIrcMsgForLogTypeDelete() { - $sep = $this->context->msg( 'colon-separator' )->text(); - - # delete/delete - $this->assertIRCComment( - $this->context->msg( 'deletedarticle', 'SomeTitle' )->plain() . $sep . $this->user_comment, - 'delete', 'delete', - array(), - $this->user_comment - ); - - # delete/restore - $this->assertIRCComment( - $this->context->msg( 'undeletedarticle', 'SomeTitle' )->plain() . $sep . $this->user_comment, - 'delete', 'restore', - array(), - $this->user_comment - ); - } - - /** - * @covers LogFormatter::getIRCActionText - */ - public function testIrcMsgForLogTypeNewusers() { - $this->assertIRCComment( - 'New user account', - 'newusers', 'newusers', - array() - ); - $this->assertIRCComment( - 'New user account', - 'newusers', 'create', - array() - ); - $this->assertIRCComment( - 'created new account SomeTitle', - 'newusers', 'create2', - array() - ); - $this->assertIRCComment( - 'Account created automatically', - 'newusers', 'autocreate', - array() - ); - } - - /** - * @covers LogFormatter::getIRCActionText - */ - public function testIrcMsgForLogTypeMove() { - $move_params = array( - '4::target' => $this->target->getPrefixedText(), - '5::noredir' => 0, - ); - $sep = $this->context->msg( 'colon-separator' )->text(); - - # move/move - $this->assertIRCComment( - $this->context->msg( '1movedto2', 'SomeTitle', 'TestTarget' ) - ->plain() . $sep . $this->user_comment, - 'move', 'move', - $move_params, - $this->user_comment - ); - - # move/move_redir - $this->assertIRCComment( - $this->context->msg( '1movedto2_redir', 'SomeTitle', 'TestTarget' ) - ->plain() . $sep . $this->user_comment, - 'move', 'move_redir', - $move_params, - $this->user_comment - ); - } - - /** - * @covers LogFormatter::getIRCActionText + * @covers RecentChange::parseParams */ - public function testIrcMsgForLogTypePatrol() { - # patrol/patrol - $this->assertIRCComment( - $this->context->msg( 'patrol-log-line', 'revision 777', '[[SomeTitle]]', '' )->plain(), - 'patrol', 'patrol', - array( - '4::curid' => '777', - '5::previd' => '666', - '6::auto' => 0, + public function testParseParams() { + $params = array( + 'root' => array( + 'A' => 1, + 'B' => 'two' ) ); - } - /** - * @covers LogFormatter::getIRCActionText - */ - public function testIrcMsgForLogTypeProtect() { - $protectParams = array( - '[edit=sysop] (indefinite) [move=sysop] (indefinite)' + $this->assertParseParams( + $params, + 'a:1:{s:4:"root";a:2:{s:1:"A";i:1;s:1:"B";s:3:"two";}}' ); - $sep = $this->context->msg( 'colon-separator' )->text(); - # protect/protect - $this->assertIRCComment( - $this->context->msg( 'protectedarticle', 'SomeTitle ' . $protectParams[0] ) - ->plain() . $sep . $this->user_comment, - 'protect', 'protect', - $protectParams, - $this->user_comment + $this->assertParseParams( + null, + null ); - # protect/unprotect - $this->assertIRCComment( - $this->context->msg( 'unprotectedarticle', 'SomeTitle' )->plain() . $sep . $this->user_comment, - 'protect', 'unprotect', - array(), - $this->user_comment + $this->assertParseParams( + null, + serialize( false ) ); - # protect/modify - $this->assertIRCComment( - $this->context->msg( 'modifiedarticleprotection', 'SomeTitle ' . $protectParams[0] ) - ->plain() . $sep . $this->user_comment, - 'protect', 'modify', - $protectParams, - $this->user_comment + $this->assertParseParams( + null, + 'not-an-array' ); } /** - * @covers LogFormatter::getIRCActionText + * @param array $expectedParseParams + * @param string|null $rawRcParams */ - public function testIrcMsgForLogTypeUpload() { - $sep = $this->context->msg( 'colon-separator' )->text(); + protected function assertParseParams( $expectedParseParams, $rawRcParams ) { + $rc = new RecentChange; + $rc->setAttribs( array( 'rc_params' => $rawRcParams ) ); - # upload/upload - $this->assertIRCComment( - $this->context->msg( 'uploadedimage', 'SomeTitle' )->plain() . $sep . $this->user_comment, - 'upload', 'upload', - array(), - $this->user_comment - ); + $actualParseParams = $rc->parseParams(); - # upload/overwrite - $this->assertIRCComment( - $this->context->msg( 'overwroteimage', 'SomeTitle' )->plain() . $sep . $this->user_comment, - 'upload', 'overwrite', - array(), - $this->user_comment - ); + $this->assertEquals( $expectedParseParams, $actualParseParams ); } /** - * @covers LogFormatter::getIRCActionText + * 50 mins and 100 mins are used here as the tests never take that long! + * @return array */ - public function testIrcMsgForLogTypeMerge() { - $sep = $this->context->msg( 'colon-separator' )->text(); - - # merge/merge - $this->assertIRCComment( - $this->context->msg( 'pagemerge-logentry', 'SomeTitle', 'Dest', 'timestamp' )->plain() - . $sep . $this->user_comment, - 'merge', 'merge', - array( - '4::dest' => 'Dest', - '5::mergepoint' => 'timestamp', - ), - $this->user_comment + public function provideIsInRCLifespan() { + return array( + array( 6000, time() - 3000, 0, true ), + array( 3000, time() - 6000, 0, false ), + array( 6000, time() - 3000, 6000, true ), + array( 3000, time() - 6000, 6000, true ), ); } /** - * @covers LogFormatter::getIRCActionText + * @covers RecentChange::isInRCLifespan + * @dataProvider provideIsInRCLifespan */ - public function testIrcMsgForLogTypeImport() { - $sep = $this->context->msg( 'colon-separator' )->text(); - - # import/upload - $this->assertIRCComment( - $this->context->msg( 'import-logentry-upload', 'SomeTitle' )->plain() . $sep . $this->user_comment, - 'import', 'upload', - array(), - $this->user_comment - ); + public function testIsInRCLifespan( $maxAge, $timestamp, $tolerance, $expected ) { + $this->setMwGlobals( 'wgRCMaxAge', $maxAge ); + $this->assertEquals( $expected, RecentChange::isInRCLifespan( $timestamp, $tolerance ) ); + } - # import/interwiki - $this->assertIRCComment( - $this->context->msg( 'import-logentry-interwiki', 'SomeTitle' )->plain() . $sep . $this->user_comment, - 'import', 'interwiki', - array(), - $this->user_comment + public function provideRCTypes() { + return array( + array( RC_EDIT, 'edit' ), + array( RC_NEW, 'new' ), + array( RC_LOG, 'log' ), + array( RC_EXTERNAL, 'external' ), ); } /** - * @todo Emulate these edits somehow and extract - * raw edit summary from RecentChange object - * -- + * @dataProvider provideRCTypes + * @covers RecentChange::parseFromRCType */ - /* - public function testIrcMsgForBlankingAES() { - // $this->context->msg( 'autosumm-blank', .. ); - } - - public function testIrcMsgForReplaceAES() { - // $this->context->msg( 'autosumm-replace', .. ); - } - - public function testIrcMsgForRollbackAES() { - // $this->context->msg( 'revertpage', .. ); + public function testParseFromRCType( $rcType, $type ) { + $this->assertEquals( $type, RecentChange::parseFromRCType( $rcType ) ); } - public function testIrcMsgForUndoAES() { - // $this->context->msg( 'undo-summary', .. ); - } - */ - /** - * @param string $expected Expected IRC text without colors codes - * @param string $type Log type (move, delete, suppress, patrol ...) - * @param string $action A log type action - * @param array $params - * @param string $comment (optional) A comment for the log action - * @param string $msg (optional) A message for PHPUnit :-) + * @dataProvider provideRCTypes + * @covers RecentChange::parseToRCType */ - protected function assertIRCComment( $expected, $type, $action, $params, - $comment = null, $msg = '' - ) { - $logEntry = new ManualLogEntry( $type, $action ); - $logEntry->setPerformer( $this->user ); - $logEntry->setTarget( $this->title ); - if ( $comment !== null ) { - $logEntry->setComment( $comment ); - } - $logEntry->setParameters( $params ); - - $formatter = LogFormatter::newFromEntry( $logEntry ); - $formatter->setContext( $this->context ); - - // Apply the same transformation as done in IRCColourfulRCFeedFormatter::getLine for rc_comment - $ircRcComment = IRCColourfulRCFeedFormatter::cleanupForIRC( $formatter->getIRCActionComment() ); - - $this->assertEquals( - $expected, - $ircRcComment, - $msg - ); + public function testParseToRCType( $rcType, $type ) { + $this->assertEquals( $rcType, RecentChange::parseToRCType( $type ) ); } + } diff --git a/tests/phpunit/includes/config/HashConfigTest.php b/tests/phpunit/includes/config/HashConfigTest.php index 06973b09..4aa3e30c 100644 --- a/tests/phpunit/includes/config/HashConfigTest.php +++ b/tests/phpunit/includes/config/HashConfigTest.php @@ -30,7 +30,7 @@ class HashConfigTest extends MediaWikiTestCase { public function testGet() { $conf = new HashConfig( array( 'one' => '1', - )); + ) ); $this->assertEquals( '1', $conf->get( 'one' ) ); $this->setExpectedException( 'ConfigException', 'HashConfig::get: undefined option' ); $conf->get( 'two' ); diff --git a/tests/phpunit/includes/content/ContentHandlerTest.php b/tests/phpunit/includes/content/ContentHandlerTest.php index 988a59ee..8178c12e 100644 --- a/tests/phpunit/includes/content/ContentHandlerTest.php +++ b/tests/phpunit/includes/content/ContentHandlerTest.php @@ -22,6 +22,7 @@ class ContentHandlerTest extends MediaWikiTestCase { 'wgContentHandlers' => array( CONTENT_MODEL_WIKITEXT => 'WikitextContentHandler', CONTENT_MODEL_JAVASCRIPT => 'JavaScriptContentHandler', + CONTENT_MODEL_JSON => 'JsonContentHandler', CONTENT_MODEL_CSS => 'CssContentHandler', CONTENT_MODEL_TEXT => 'TextContentHandler', 'testing' => 'DummyContentHandlerForTesting', @@ -51,19 +52,27 @@ class ContentHandlerTest extends MediaWikiTestCase { return array( array( 'Help:Foo', CONTENT_MODEL_WIKITEXT ), array( 'Help:Foo.js', CONTENT_MODEL_WIKITEXT ), + array( 'Help:Foo.css', CONTENT_MODEL_WIKITEXT ), + array( 'Help:Foo.json', CONTENT_MODEL_WIKITEXT ), array( 'Help:Foo/bar.js', CONTENT_MODEL_WIKITEXT ), array( 'User:Foo', CONTENT_MODEL_WIKITEXT ), array( 'User:Foo.js', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo.css', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo.json', CONTENT_MODEL_WIKITEXT ), array( 'User:Foo/bar.js', CONTENT_MODEL_JAVASCRIPT ), array( 'User:Foo/bar.css', CONTENT_MODEL_CSS ), + array( 'User:Foo/bar.json', CONTENT_MODEL_JSON ), + array( 'User:Foo/bar.json.nope', CONTENT_MODEL_WIKITEXT ), array( 'User talk:Foo/bar.css', CONTENT_MODEL_WIKITEXT ), array( 'User:Foo/bar.js.xxx', CONTENT_MODEL_WIKITEXT ), array( 'User:Foo/bar.xxx', CONTENT_MODEL_WIKITEXT ), array( 'MediaWiki:Foo.js', CONTENT_MODEL_JAVASCRIPT ), - array( 'MediaWiki:Foo.css', CONTENT_MODEL_CSS ), array( 'MediaWiki:Foo.JS', CONTENT_MODEL_WIKITEXT ), - array( 'MediaWiki:Foo.CSS', CONTENT_MODEL_WIKITEXT ), + array( 'MediaWiki:Foo.css', CONTENT_MODEL_CSS ), array( 'MediaWiki:Foo.css.xxx', CONTENT_MODEL_WIKITEXT ), + array( 'MediaWiki:Foo.CSS', CONTENT_MODEL_WIKITEXT ), + array( 'MediaWiki:Foo.json', CONTENT_MODEL_JSON ), + array( 'MediaWiki:Foo.JSON', CONTENT_MODEL_WIKITEXT ), ); } @@ -338,6 +347,11 @@ class ContentHandlerTest extends MediaWikiTestCase { } */ + public function testSupportsDirectEditing() { + $handler = new DummyContentHandlerForTesting( CONTENT_MODEL_JSON ); + $this->assertFalse( $handler->supportsDirectEditing(), 'direct editing is not supported' ); + } + /** * @covers ContentHandler::runLegacyHooks */ @@ -365,164 +379,3 @@ class ContentHandlerTest extends MediaWikiTestCase { return true; } } - -class DummyContentHandlerForTesting extends ContentHandler { - - public function __construct( $dataModel ) { - parent::__construct( $dataModel, array( "testing" ) ); - } - - /** - * @see ContentHandler::serializeContent - * - * @param Content $content - * @param string $format - * - * @return string - */ - public function serializeContent( Content $content, $format = null ) { - return $content->serialize(); - } - - /** - * @see ContentHandler::unserializeContent - * - * @param string $blob - * @param string $format Unused. - * - * @return Content - */ - public function unserializeContent( $blob, $format = null ) { - $d = unserialize( $blob ); - - return new DummyContentForTesting( $d ); - } - - /** - * Creates an empty Content object of the type supported by this ContentHandler. - * - */ - public function makeEmptyContent() { - return new DummyContentForTesting( '' ); - } -} - -class DummyContentForTesting extends AbstractContent { - - public function __construct( $data ) { - parent::__construct( "testing" ); - - $this->data = $data; - } - - public function serialize( $format = null ) { - return serialize( $this->data ); - } - - /** - * @return string A string representing the content in a way useful for - * building a full text search index. If no useful representation exists, - * this method returns an empty string. - */ - public function getTextForSearchIndex() { - return ''; - } - - /** - * @return string|bool The wikitext to include when another page includes this content, - * or false if the content is not includable in a wikitext page. - */ - public function getWikitextForTransclusion() { - return false; - } - - /** - * Returns a textual representation of the content suitable for use in edit - * summaries and log messages. - * - * @param int $maxlength Maximum length of the summary text. - * @return string The summary text. - */ - public function getTextForSummary( $maxlength = 250 ) { - return ''; - } - - /** - * Returns native represenation of the data. Interpretation depends on the data model used, - * as given by getDataModel(). - * - * @return mixed The native representation of the content. Could be a string, a nested array - * structure, an object, a binary blob... anything, really. - */ - public function getNativeData() { - return $this->data; - } - - /** - * returns the content's nominal size in bogo-bytes. - * - * @return int - */ - public function getSize() { - return strlen( $this->data ); - } - - /** - * Return a copy of this Content object. The following must be true for the object returned - * if $copy = $original->copy() - * - * * get_class($original) === get_class($copy) - * * $original->getModel() === $copy->getModel() - * * $original->equals( $copy ) - * - * If and only if the Content object is imutable, the copy() method can and should - * return $this. That is, $copy === $original may be true, but only for imutable content - * objects. - * - * @return Content A copy of this object - */ - public function copy() { - return $this; - } - - /** - * Returns true if this content is countable as a "real" wiki page, provided - * that it's also in a countable location (e.g. a current revision in the main namespace). - * - * @param bool $hasLinks If it is known whether this content contains links, - * provide this information here, to avoid redundant parsing to find out. - * @return bool - */ - public function isCountable( $hasLinks = null ) { - return false; - } - - /** - * @param Title $title - * @param int $revId Unused. - * @param null|ParserOptions $options - * @param bool $generateHtml Whether to generate Html (default: true). If false, the result - * of calling getText() on the ParserOutput object returned by this method is undefined. - * - * @return ParserOutput - */ - public function getParserOutput( Title $title, $revId = null, - ParserOptions $options = null, $generateHtml = true - ) { - return new ParserOutput( $this->getNativeData() ); - } - - /** - * @see AbstractContent::fillParserOutput() - * - * @param Title $title Context title for parsing - * @param int|null $revId Revision ID (for {{REVISIONID}}) - * @param ParserOptions $options Parser options - * @param bool $generateHtml Whether or not to generate HTML - * @param ParserOutput &$output The output object to fill (reference). - */ - protected function fillParserOutput( Title $title, $revId, - ParserOptions $options, $generateHtml, ParserOutput &$output ) { - $output = new ParserOutput( $this->getNativeData() ); - } -} diff --git a/tests/phpunit/includes/content/CssContentHandlerTest.php b/tests/phpunit/includes/content/CssContentHandlerTest.php new file mode 100644 index 00000000..e1785a96 --- /dev/null +++ b/tests/phpunit/includes/content/CssContentHandlerTest.php @@ -0,0 +1,30 @@ +<?php + +class CssContentHandlerTest extends MediaWikiTestCase { + + /** + * @dataProvider provideMakeRedirectContent + * @covers CssContentHandler::makeRedirectContent + */ + public function testMakeRedirectContent( $title, $expected ) { + $this->setMwGlobals( array( + 'wgServer' => '//example.org', + 'wgScript' => '/w/index.php', + ) ); + $ch = new CssContentHandler(); + $content = $ch->makeRedirectContent( Title::newFromText( $title ) ); + $this->assertInstanceOf( 'CssContent', $content ); + $this->assertEquals( $expected, $content->serialize( CONTENT_FORMAT_CSS ) ); + } + + /** + * Keep this in sync with CssContentTest::provideGetRedirectTarget() + */ + public static function provideMakeRedirectContent() { + return array( + array( 'MediaWiki:MonoBook.css', "/* #REDIRECT */@import url(//example.org/w/index.php?title=MediaWiki:MonoBook.css&action=raw&ctype=text/css);" ), + array( 'User:FooBar/common.css', "/* #REDIRECT */@import url(//example.org/w/index.php?title=User:FooBar/common.css&action=raw&ctype=text/css);" ), + array( 'Gadget:FooBaz.css', "/* #REDIRECT */@import url(//example.org/w/index.php?title=Gadget:FooBaz.css&action=raw&ctype=text/css);" ), + ); + } +} diff --git a/tests/phpunit/includes/content/CssContentTest.php b/tests/phpunit/includes/content/CssContentTest.php index 40484d3a..c4d87c24 100644 --- a/tests/phpunit/includes/content/CssContentTest.php +++ b/tests/phpunit/includes/content/CssContentTest.php @@ -4,6 +4,8 @@ * @group ContentHandler * @group Database * ^--- needed, because we do need the database to test link updates + * + * @FIXME this should not extend JavaScriptContentTest. */ class CssContentTest extends JavaScriptContentTest { @@ -68,7 +70,48 @@ class CssContentTest extends JavaScriptContentTest { $this->assertEquals( CONTENT_MODEL_CSS, $content->getContentHandler()->getModelID() ); } - public static function dataEquals() { + /** + * Redirects aren't supported + */ + public static function provideUpdateRedirect() { + return array( + array( + '#REDIRECT [[Someplace]]', + '#REDIRECT [[Someplace]]', + ), + ); + } + + /** + * @dataProvider provideGetRedirectTarget + */ + public function testGetRedirectTarget( $title, $text ) { + $this->setMwGlobals( array( + 'wgServer' => '//example.org', + 'wgScriptPath' => '/w', + 'wgScript' => '/w/index.php', + ) ); + $content = new CssContent( $text ); + $target = $content->getRedirectTarget(); + $this->assertEquals( $title, $target ? $target->getPrefixedText() : null ); + } + + /** + * Keep this in sync with CssContentHandlerTest::provideMakeRedirectContent() + */ + public static function provideGetRedirectTarget() { + return array( + array( 'MediaWiki:MonoBook.css', "/* #REDIRECT */@import url(//example.org/w/index.php?title=MediaWiki:MonoBook.css&action=raw&ctype=text/css);" ), + array( 'User:FooBar/common.css', "/* #REDIRECT */@import url(//example.org/w/index.php?title=User:FooBar/common.css&action=raw&ctype=text/css);" ), + array( 'Gadget:FooBaz.css', "/* #REDIRECT */@import url(//example.org/w/index.php?title=Gadget:FooBaz.css&action=raw&ctype=text/css);" ), + # No #REDIRECT comment + array( null, "@import url(//example.org/w/index.php?title=Gadget:FooBaz.css&action=raw&ctype=text/css);" ), + # Wrong domain + array( null, "/* #REDIRECT */@import url(//example.com/w/index.php?title=Gadget:FooBaz.css&action=raw&ctype=text/css);" ), + ); + } + + public static function dataEquals() { return array( array( new CssContent( 'hallo' ), null, false ), array( new CssContent( 'hallo' ), new CssContent( 'hallo' ), true ), diff --git a/tests/phpunit/includes/content/JavaScriptContentHandlerTest.php b/tests/phpunit/includes/content/JavaScriptContentHandlerTest.php new file mode 100644 index 00000000..0f41020f --- /dev/null +++ b/tests/phpunit/includes/content/JavaScriptContentHandlerTest.php @@ -0,0 +1,30 @@ +<?php + +class JavaScriptContentHandlerTest extends MediaWikiTestCase { + + /** + * @dataProvider provideMakeRedirectContent + * @covers JavaScriptContentHandler::makeRedirectContent + */ + public function testMakeRedirectContent( $title, $expected ) { + $this->setMwGlobals( array( + 'wgServer' => '//example.org', + 'wgScript' => '/w/index.php', + ) ); + $ch = new JavaScriptContentHandler(); + $content = $ch->makeRedirectContent( Title::newFromText( $title ) ); + $this->assertInstanceOf( 'JavaScriptContent', $content ); + $this->assertEquals( $expected, $content->serialize( CONTENT_FORMAT_JAVASCRIPT ) ); + } + + /** + * Keep this in sync with JavaScriptContentTest::provideGetRedirectTarget() + */ + public static function provideMakeRedirectContent() { + return array( + array( 'MediaWiki:MonoBook.js', '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=MediaWiki:MonoBook.js\u0026action=raw\u0026ctype=text/javascript");' ), + array( 'User:FooBar/common.js', '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=User:FooBar/common.js\u0026action=raw\u0026ctype=text/javascript");' ), + array( 'Gadget:FooBaz.js', '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=Gadget:FooBaz.js\u0026action=raw\u0026ctype=text/javascript");' ), + ); + } +} diff --git a/tests/phpunit/includes/content/JavaScriptContentTest.php b/tests/phpunit/includes/content/JavaScriptContentTest.php index 7193ec9f..0ee27129 100644 --- a/tests/phpunit/includes/content/JavaScriptContentTest.php +++ b/tests/phpunit/includes/content/JavaScriptContentTest.php @@ -251,16 +251,31 @@ class JavaScriptContentTest extends TextContentTest { /** * @covers JavaScriptContent::updateRedirect + * @dataProvider provideUpdateRedirect */ - public function testUpdateRedirect() { + public function testUpdateRedirect( $oldText, $expectedText) { + $this->setMwGlobals( array( + 'wgServer' => '//example.org', + 'wgScriptPath' => '/w/index.php', + ) ); $target = Title::newFromText( "testUpdateRedirect_target" ); - $content = $this->newContent( "#REDIRECT [[Someplace]]" ); + $content = new JavaScriptContent( $oldText ); $newContent = $content->updateRedirect( $target ); - $this->assertTrue( - $content->equals( $newContent ), - "content should be unchanged since it's not wikitext" + $this->assertEquals( $expectedText, $newContent->getNativeData() ); + } + + public static function provideUpdateRedirect() { + return array( + array( + '#REDIRECT [[Someplace]]', + '#REDIRECT [[Someplace]]', + ), + array( + '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=MediaWiki:MonoBook.js\u0026action=raw\u0026ctype=text/javascript");', + '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=TestUpdateRedirect_target\u0026action=raw\u0026ctype=text/javascript");' + ) ); } @@ -290,4 +305,32 @@ class JavaScriptContentTest extends TextContentTest { array( new JavaScriptContent( "hallo" ), new JavaScriptContent( "HALLO" ), false ), ); } + + /** + * @dataProvider provideGetRedirectTarget + */ + public function testGetRedirectTarget( $title, $text ) { + $this->setMwGlobals( array( + 'wgServer' => '//example.org', + 'wgScriptPath' => '/w/index.php', + ) ); + $content = new JavaScriptContent( $text ); + $target = $content->getRedirectTarget(); + $this->assertEquals( $title, $target ? $target->getPrefixedText() : null ); + } + + /** + * Keep this in sync with JavaScriptContentHandlerTest::provideMakeRedirectContent() + */ + public static function provideGetRedirectTarget() { + return array( + array( 'MediaWiki:MonoBook.js', '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=MediaWiki:MonoBook.js\u0026action=raw\u0026ctype=text/javascript");' ), + array( 'User:FooBar/common.js', '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=User:FooBar/common.js\u0026action=raw\u0026ctype=text/javascript");' ), + array( 'Gadget:FooBaz.js', '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=Gadget:FooBaz.js\u0026action=raw\u0026ctype=text/javascript");' ), + // No #REDIRECT comment + array( null, 'mw.loader.load("//example.org/w/index.php?title=MediaWiki:NoRedirect.js\u0026action=raw\u0026ctype=text/javascript");' ), + // Different domain + array( null, '/* #REDIRECT */mw.loader.load("//example.com/w/index.php?title=MediaWiki:OtherWiki.js\u0026action=raw\u0026ctype=text/javascript");' ), + ); + } } diff --git a/tests/phpunit/includes/content/JsonContentTest.php b/tests/phpunit/includes/content/JsonContentTest.php index cccfe7b1..8a9d2ab0 100644 --- a/tests/phpunit/includes/content/JsonContentTest.php +++ b/tests/phpunit/includes/content/JsonContentTest.php @@ -138,7 +138,7 @@ class JsonContentTest extends MediaWikiLangTestCase { '<tr><th>0</th><td class="value">"bar"</td></tr></tbody></table>' ), array( - (object)array( '<script>alert("evil!")</script>'), + (object)array( '<script>alert("evil!")</script>' ), '<table class="mw-json"><tbody><tr><th>0</th><td class="value">"' . '<script>alert("evil!")</script>"' . '</td></tr></tbody></table>', diff --git a/tests/phpunit/includes/content/TextContentHandlerTest.php b/tests/phpunit/includes/content/TextContentHandlerTest.php new file mode 100644 index 00000000..492fec6b --- /dev/null +++ b/tests/phpunit/includes/content/TextContentHandlerTest.php @@ -0,0 +1,12 @@ +<?php + +/** + * @group ContentHandler + */ +class TextContentHandlerTest extends MediaWikiLangTestCase { + public function testSupportsDirectEditing() { + $handler = new TextContentHandler(); + $this->assertTrue( $handler->supportsDirectEditing(), 'direct editing is supported' ); + } + +} diff --git a/tests/phpunit/includes/content/TextContentTest.php b/tests/phpunit/includes/content/TextContentTest.php index dd61f85b..fe263756 100644 --- a/tests/phpunit/includes/content/TextContentTest.php +++ b/tests/phpunit/includes/content/TextContentTest.php @@ -27,10 +27,16 @@ class TextContentTest extends MediaWikiLangTestCase { CONTENT_MODEL_JAVASCRIPT, ), 'wgUseTidy' => false, - 'wgAlwaysUseTidy' => false, 'wgCapitalLinks' => true, 'wgHooks' => array(), // bypass hook ContentGetParserOutput that force custom rendering ) ); + + MWTidy::destroySingleton(); + } + + protected function tearDown() { + MWTidy::destroySingleton(); + parent::tearDown(); } public function newContent( $text ) { diff --git a/tests/phpunit/includes/content/WikitextContentHandlerTest.php b/tests/phpunit/includes/content/WikitextContentHandlerTest.php index 38fb5733..361238b7 100644 --- a/tests/phpunit/includes/content/WikitextContentHandlerTest.php +++ b/tests/phpunit/includes/content/WikitextContentHandlerTest.php @@ -115,6 +115,11 @@ class WikitextContentHandlerTest extends MediaWikiLangTestCase { $this->assertEquals( $supported, $this->handler->isSupportedFormat( $format ) ); } + public function testSupportsDirectEditing() { + $handler = new WikiTextContentHandler(); + $this->assertTrue( $handler->supportsDirectEditing(), 'direct editing is supported' ); + } + public static function dataMerge3() { return array( array( diff --git a/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php b/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php index b4292a60..42ea58e5 100644 --- a/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php +++ b/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php @@ -181,7 +181,7 @@ class DatabaseMysqlBaseTest extends MediaWikiTestCase { array( 'Tables_in_' => 'view2' ), array( 'Tables_in_' => 'myview' ), false # no more rows - )); + ) ); return $db; } /** diff --git a/tests/phpunit/includes/db/DatabaseSqliteTest.php b/tests/phpunit/includes/db/DatabaseSqliteTest.php index 645baf1f..3db9172a 100644 --- a/tests/phpunit/includes/db/DatabaseSqliteTest.php +++ b/tests/phpunit/includes/db/DatabaseSqliteTest.php @@ -1,12 +1,13 @@ <?php -class MockDatabaseSqlite extends DatabaseSqlite { +class DatabaseSqliteMock extends DatabaseSqlite { private $lastQuery; public static function newInstance( array $p = array() ) { $p['dbFilePath'] = ':memory:'; + $p['schema'] = false; - return new self( $p ); + return DatabaseBase::factory( 'SqliteMock', $p ); } function query( $sql, $fname = '', $tempIgnore = false ) { @@ -29,7 +30,7 @@ class MockDatabaseSqlite extends DatabaseSqlite { * @group medium */ class DatabaseSqliteTest extends MediaWikiTestCase { - /** @var MockDatabaseSqlite */ + /** @var DatabaseSqliteMock */ protected $db; protected function setUp() { @@ -38,7 +39,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase { if ( !Sqlite::isPresent() ) { $this->markTestSkipped( 'No SQLite support detected' ); } - $this->db = MockDatabaseSqlite::newInstance(); + $this->db = DatabaseSqliteMock::newInstance(); if ( version_compare( $this->db->getServerVersion(), '3.6.0', '<' ) ) { $this->markTestSkipped( "SQLite at least 3.6 required, {$this->db->getServerVersion()} found" ); } @@ -188,18 +189,34 @@ class DatabaseSqliteTest extends MediaWikiTestCase { public function testDuplicateTableStructure() { $db = DatabaseSqlite::newStandaloneInstance( ':memory:' ); $db->query( 'CREATE TABLE foo(foo, barfoo)' ); + $db->query( 'CREATE INDEX index1 ON foo(foo)' ); + $db->query( 'CREATE UNIQUE INDEX index2 ON foo(barfoo)' ); $db->duplicateTableStructure( 'foo', 'bar' ); $this->assertEquals( 'CREATE TABLE "bar"(foo, barfoo)', $db->selectField( 'sqlite_master', 'sql', array( 'name' => 'bar' ) ), 'Normal table duplication' ); + $indexList = $db->query( 'PRAGMA INDEX_LIST("bar")' ); + $index = $indexList->next(); + $this->assertEquals( 'bar_index1', $index->name ); + $this->assertEquals( '0', $index->unique ); + $index = $indexList->next(); + $this->assertEquals( 'bar_index2', $index->name ); + $this->assertEquals( '1', $index->unique ); $db->duplicateTableStructure( 'foo', 'baz', true ); $this->assertEquals( 'CREATE TABLE "baz"(foo, barfoo)', $db->selectField( 'sqlite_temp_master', 'sql', array( 'name' => 'baz' ) ), 'Creation of temporary duplicate' ); + $indexList = $db->query( 'PRAGMA INDEX_LIST("baz")' ); + $index = $indexList->next(); + $this->assertEquals( 'baz_index1', $index->name ); + $this->assertEquals( '0', $index->unique ); + $index = $indexList->next(); + $this->assertEquals( 'baz_index2', $index->name ); + $this->assertEquals( '1', $index->unique ); $this->assertEquals( 0, $db->selectField( 'sqlite_master', 'COUNT(*)', array( 'name' => 'baz' ) ), 'Create a temporary duplicate only' diff --git a/tests/phpunit/includes/db/ORMTableTest.php b/tests/phpunit/includes/db/ORMTableTest.php index 338d931f..764560d5 100644 --- a/tests/phpunit/includes/db/ORMTableTest.php +++ b/tests/phpunit/includes/db/ORMTableTest.php @@ -68,25 +68,6 @@ class ORMTableTest extends MediaWikiTestCase { $this->assertInstanceOf( $class, $class::singleton() ); $this->assertTrue( $class::singleton() === $class::singleton() ); } - - /** - * @since 1.21 - */ - public function testIgnoreErrorsOverride() { - $table = $this->getTable(); - - $db = $table->getReadDbConnection(); - $db->ignoreErrors( true ); - - try { - $table->rawSelect( "this is invalid" ); - $this->fail( "An invalid query should trigger a DBQueryError even if ignoreErrors is enabled." ); - } catch ( DBQueryError $ex ) { - $this->assertTrue( true, "just making phpunit happy" ); - } - - $db->ignoreErrors( false ); - } } /** diff --git a/tests/phpunit/includes/debug/MWDebugTest.php b/tests/phpunit/includes/debug/MWDebugTest.php index 1abb47e7..7280a97b 100644 --- a/tests/phpunit/includes/debug/MWDebugTest.php +++ b/tests/phpunit/includes/debug/MWDebugTest.php @@ -12,11 +12,11 @@ class MWDebugTest extends MediaWikiTestCase { } /** Clear log before each test */ MWDebug::clearLog(); - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); } protected function tearDown() { - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); parent::tearDown(); } diff --git a/tests/phpunit/includes/debug/logging/LegacyLoggerTest.php b/tests/phpunit/includes/debug/logger/LegacyLoggerTest.php index 415fa045..1b3ce2ca 100644 --- a/tests/phpunit/includes/debug/logging/LegacyLoggerTest.php +++ b/tests/phpunit/includes/debug/logger/LegacyLoggerTest.php @@ -35,6 +35,8 @@ class LegacyLoggerTest extends MediaWikiTestCase { } public function provideInterpolate() { + $e = new \Exception( 'boom!' ); + $d = new \DateTime(); return array( array( 'no-op', @@ -68,6 +70,57 @@ class LegacyLoggerTest extends MediaWikiTestCase { ), '{ not interpolated }', ), + array( + '{null}', + array( + 'null' => null, + ), + '[Null]', + ), + array( + '{bool}', + array( + 'bool' => true, + ), + 'true', + ), + array( + '{float}', + array( + 'float' => 1.23, + ), + '1.23', + ), + array( + '{array}', + array( + 'array' => array( 1, 2, 3 ), + ), + '[Array(3)]', + ), + array( + '{exception}', + array( + 'exception' => $e, + ), + '[Exception ' . get_class( $e ) . '( ' . + $e->getFile() . ':' . $e->getLine() . ') ' . + $e->getMessage() . ']', + ), + array( + '{datetime}', + array( + 'datetime' => $d, + ), + $d->format( 'c' ), + ), + array( + '{object}', + array( + 'object' => new \stdClass, + ), + '[Object stdClass]', + ), ); } diff --git a/tests/phpunit/includes/debug/logger/MonologSpiTest.php b/tests/phpunit/includes/debug/logger/MonologSpiTest.php new file mode 100644 index 00000000..aa0a54ff --- /dev/null +++ b/tests/phpunit/includes/debug/logger/MonologSpiTest.php @@ -0,0 +1,136 @@ +<?php +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +namespace MediaWiki\Logger; + +use MediaWikiTestCase; +use TestingAccessWrapper; + +class MonologSpiTest extends MediaWikiTestCase { + + /** + * @covers MonologSpi::mergeConfig + */ + public function testMergeConfig() { + $base = array( + 'loggers' => array( + '@default' => array( + 'processors' => array( 'constructor' ), + 'handlers' => array( 'constructor' ), + ), + ), + 'processors' => array( + 'constructor' => array( + 'class' => 'constructor', + ), + ), + 'handlers' => array( + 'constructor' => array( + 'class' => 'constructor', + 'formatter' => 'constructor', + ), + ), + 'formatters' => array( + 'constructor' => array( + 'class' => 'constructor', + ), + ), + ); + + $fixture = new MonologSpi( $base ); + $this->assertSame( + $base, + TestingAccessWrapper::newFromObject( $fixture )->config + ); + + $fixture->mergeConfig( array( + 'loggers' => array( + 'merged' => array( + 'processors' => array( 'merged' ), + 'handlers' => array( 'merged' ), + ), + ), + 'processors' => array( + 'merged' => array( + 'class' => 'merged', + ), + ), + 'magic' => array( + 'idkfa' => array( 'xyzzy' ), + ), + 'handlers' => array( + 'merged' => array( + 'class' => 'merged', + 'formatter' => 'merged', + ), + ), + 'formatters' => array( + 'merged' => array( + 'class' => 'merged', + ), + ), + ) ); + $this->assertSame( + array( + 'loggers' => array( + '@default' => array( + 'processors' => array( 'constructor' ), + 'handlers' => array( 'constructor' ), + ), + 'merged' => array( + 'processors' => array( 'merged' ), + 'handlers' => array( 'merged' ), + ), + ), + 'processors' => array( + 'constructor' => array( + 'class' => 'constructor', + ), + 'merged' => array( + 'class' => 'merged', + ), + ), + 'handlers' => array( + 'constructor' => array( + 'class' => 'constructor', + 'formatter' => 'constructor', + ), + 'merged' => array( + 'class' => 'merged', + 'formatter' => 'merged', + ), + ), + 'formatters' => array( + 'constructor' => array( + 'class' => 'constructor', + ), + 'merged' => array( + 'class' => 'merged', + ), + ), + 'magic' => array( + 'idkfa' => array( 'xyzzy' ), + ), + ), + TestingAccessWrapper::newFromObject( $fixture )->config + ); + } + +} diff --git a/tests/phpunit/includes/debug/logger/monolog/AvroFormatterTest.php b/tests/phpunit/includes/debug/logger/monolog/AvroFormatterTest.php new file mode 100644 index 00000000..44242ed2 --- /dev/null +++ b/tests/phpunit/includes/debug/logger/monolog/AvroFormatterTest.php @@ -0,0 +1,64 @@ +<?php +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +namespace MediaWiki\Logger\Monolog; + +use MediaWikiTestCase; +use PHPUnit_Framework_Error_Notice; + +class AvroFormatterTest extends MediaWikiTestCase { + + protected function setUp() { + if ( !class_exists( 'AvroStringIO' ) ) { + $this->markTestSkipped( 'Avro is required for the AvroFormatterTest' ); + } + parent::setUp(); + } + + public function testSchemaNotAvailable() { + $formatter = new AvroFormatter( array() ); + $this->setExpectedException( 'PHPUnit_Framework_Error_Notice', "The schema for channel 'marty' is not available" ); + $formatter->format( array( 'channel' => 'marty' ) ); + } + + public function testSchemaNotAvailableReturnValue() { + $formatter = new AvroFormatter( array() ); + $noticeEnabled = PHPUnit_Framework_Error_Notice::$enabled; + // disable conversion of notices + PHPUnit_Framework_Error_Notice::$enabled = false; + // have to keep the user notice from being output + wfSuppressWarnings(); + $res = $formatter->format( array( 'channel' => 'marty' ) ); + wfRestoreWarnings(); + PHPUnit_Framework_Error_Notice::$enabled = $noticeEnabled; + $this->assertNull( $res ); + } + + public function testDoesSomethingWhenSchemaAvailable() { + $formatter = new AvroFormatter( array( 'string' => array( 'type' => 'string' ) ) ); + $res = $formatter->format( array( + 'channel' => 'string', + 'context' => 'better to be', + ) ); + $this->assertNotNull( $res ); + // basically just tell us if avro changes its string encoding + $this->assertEquals( base64_decode( 'GGJldHRlciB0byBiZQ==' ), $res ); + } +} diff --git a/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php b/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php new file mode 100644 index 00000000..090f439e --- /dev/null +++ b/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php @@ -0,0 +1,207 @@ +<?php +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +namespace MediaWiki\Logger\Monolog; + +use MediaWikiTestCase; +use Monolog\Logger; + +// not available in the version of phpunit mw uses, so copied into repo +require_once __DIR__ . '/../../../ConsecutiveParametersMatcher.php'; + +class KafkaHandlerTest extends MediaWikiTestCase { + + protected function setUp() { + if ( !class_exists( 'Monolog\Handler\AbstractProcessingHandler' ) + || !class_exists( 'Kafka\Produce' ) + ) { + $this->markTestSkipped( 'Monolog and Kafka are required for the KafkaHandlerTest' ); + } + + parent::setUp(); + } + + public function topicNamingProvider() { + return array( + array( array(), 'monolog_foo' ), + array( array( 'alias' => array( 'foo' => 'bar' ) ), 'bar' ) + ); + } + + /** + * @dataProvider topicNamingProvider + */ + public function testTopicNaming( $options, $expect ) { + $produce = $this->getMockBuilder( 'Kafka\Produce' ) + ->disableOriginalConstructor() + ->getMock(); + $produce->expects($this->any()) + ->method('getAvailablePartitions') + ->will($this->returnValue( array( 'A' ) ) ); + $produce->expects($this->once()) + ->method( 'setMessages' ) + ->with( $expect, $this->anything(), $this->anything() ); + + $handler = new KafkaHandler( $produce, $options ); + $handler->handle( array( + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ) ); + } + + public function swallowsExceptionsWhenRequested() { + return array( + // defaults to false + array( array(), true ), + // also try false explicitly + array( array( 'swallowExceptions' => false ), true ), + // turn it on + array( array( 'swallowExceptions' => true ), false ), + ); + } + + /** + * @dataProvider swallowsExceptionsWhenRequested + */ + public function testGetAvailablePartitionsException( $options, $expectException ) { + $produce = $this->getMockBuilder( 'Kafka\Produce' ) + ->disableOriginalConstructor() + ->getMock(); + $produce->expects( $this->any() ) + ->method( 'getAvailablePartitions' ) + ->will( $this->throwException( new \Kafka\Exception ) ); + + if ( $expectException ) { + $this->setExpectedException( 'Kafka\Exception' ); + } + + $handler = new KafkaHandler( $produce, $options ); + $handler->handle( array( + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ) ); + + if ( !$expectException ) { + $this->assertTrue( true, 'no exception was thrown' ); + } + } + + /** + * @dataProvider swallowsExceptionsWhenRequested + */ + public function testSendException( $options, $expectException ) { + $produce = $this->getMockBuilder( 'Kafka\Produce' ) + ->disableOriginalConstructor() + ->getMock(); + $produce->expects( $this->any() ) + ->method( 'getAvailablePartitions' ) + ->will( $this->returnValue( array( 'A' ) ) ); + $produce->expects( $this->any() ) + ->method( 'send' ) + ->will( $this->throwException( new \Kafka\Exception ) ); + + if ( $expectException ) { + $this->setExpectedException( 'Kafka\Exception' ); + } + + $handler = new KafkaHandler( $produce, $options ); + $handler->handle( array( + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ) ); + + if ( !$expectException ) { + $this->assertTrue( true, 'no exception was thrown' ); + } + } + + public function testHandlesNullFormatterResult() { + $produce = $this->getMockBuilder( 'Kafka\Produce' ) + ->disableOriginalConstructor() + ->getMock(); + $produce->expects( $this->any() ) + ->method( 'getAvailablePartitions' ) + ->will( $this->returnValue( array( 'A' ) ) ); + $mockMethod = $produce->expects( $this->exactly( 2 ) ) + ->method( 'setMessages' ); + // evil hax + \TestingAccessWrapper::newFromObject( $mockMethod )->matcher->parametersMatcher = + new \PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters( array( + array( $this->anything(), $this->anything(), array( 'words' ) ), + array( $this->anything(), $this->anything(), array( 'lines' ) ) + ) ); + + $formatter = $this->getMock( 'Monolog\Formatter\FormatterInterface' ); + $formatter->expects( $this->any() ) + ->method( 'format' ) + ->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) ); + + $handler = new KafkaHandler( $produce, array() ); + $handler->setFormatter( $formatter ); + for ( $i = 0; $i < 3; ++$i ) { + $handler->handle( array( + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ) ); + } + } + + + public function testBatchHandlesNullFormatterResult() { + $produce = $this->getMockBuilder( 'Kafka\Produce' ) + ->disableOriginalConstructor() + ->getMock(); + $produce->expects( $this->any() ) + ->method( 'getAvailablePartitions' ) + ->will( $this->returnValue( array( 'A' ) ) ); + $produce->expects( $this->once() ) + ->method( 'setMessages' ) + ->with( $this->anything(), $this->anything(), array( 'words', 'lines' ) ); + + $formatter = $this->getMock( 'Monolog\Formatter\FormatterInterface' ); + $formatter->expects( $this->any() ) + ->method( 'format' ) + ->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) ); + + $handler = new KafkaHandler( $produce, array() ); + $handler->setFormatter( $formatter ); + $handler->handleBatch( array( + array( + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ), + array( + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ), + array( + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ), + ) ); + } +} diff --git a/tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php b/tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php new file mode 100644 index 00000000..be23c4a2 --- /dev/null +++ b/tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php @@ -0,0 +1,75 @@ +<?php +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +namespace MediaWiki\Logger\Monolog; + +use InvalidArgumentException; +use LengthException; +use LogicException; +use MediaWikiTestCase; +use TestingAccessWrapper; + +class LineFormatterTest extends MediaWikiTestCase { + + protected function setUp() { + if ( !class_exists( 'Monolog\Formatter\LineFormatter' ) ) { + $this->markTestSkipped( 'This test requires monolog to be installed' ); + } + parent::setUp(); + } + + /** + * @covers LineFormatter::normalizeException + */ + public function testNormalizeExceptionNoTrace() { + $fixture = new LineFormatter(); + $fixture->includeStacktraces( false ); + $fixture = TestingAccessWrapper::newFromObject( $fixture ); + $boom = new InvalidArgumentException( 'boom', 0, + new LengthException( 'too long', 0, + new LogicException( 'Spock wuz here' ) + ) + ); + $out = $fixture->normalizeException( $boom ); + $this->assertContains( "\n[Exception InvalidArgumentException]", $out ); + $this->assertContains( "\nCaused by: [Exception LengthException]", $out ); + $this->assertContains( "\nCaused by: [Exception LogicException]", $out ); + $this->assertNotContains( "\n #0", $out ); + } + + /** + * @covers LineFormatter::normalizeException + */ + public function testNormalizeExceptionTrace() { + $fixture = new LineFormatter(); + $fixture->includeStacktraces( true ); + $fixture = TestingAccessWrapper::newFromObject( $fixture ); + $boom = new InvalidArgumentException( 'boom', 0, + new LengthException( 'too long', 0, + new LogicException( 'Spock wuz here' ) + ) + ); + $out = $fixture->normalizeException( $boom ); + $this->assertContains( "\n[Exception InvalidArgumentException]", $out ); + $this->assertContains( "\nCaused by: [Exception LengthException]", $out ); + $this->assertContains( "\nCaused by: [Exception LogicException]", $out ); + $this->assertContains( "\n #0", $out ); + } +} diff --git a/tests/phpunit/includes/deferred/DeferredUpdatesTest.php b/tests/phpunit/includes/deferred/DeferredUpdatesTest.php index 5348c854..df4213ab 100644 --- a/tests/phpunit/includes/deferred/DeferredUpdatesTest.php +++ b/tests/phpunit/includes/deferred/DeferredUpdatesTest.php @@ -1,8 +1,9 @@ <?php class DeferredUpdatesTest extends MediaWikiTestCase { + public function testDoUpdatesWeb() { + $this->setMwGlobals( 'wgCommandLineMode', false ); - public function testDoUpdates() { $updates = array( '1' => 'deferred update 1', '2' => 'deferred update 2', @@ -35,4 +36,38 @@ class DeferredUpdatesTest extends MediaWikiTestCase { DeferredUpdates::doUpdates(); } + public function testDoUpdatesCLI() { + $this->setMwGlobals( 'wgCommandLineMode', true ); + + $updates = array( + '1' => 'deferred update 1', + '2' => 'deferred update 2', + '2-1' => 'deferred update 1 within deferred update 2', + '3' => 'deferred update 3', + ); + DeferredUpdates::addCallableUpdate( + function () use ( $updates ) { + echo $updates['1']; + } + ); + DeferredUpdates::addCallableUpdate( + function () use ( $updates ) { + echo $updates['2']; + DeferredUpdates::addCallableUpdate( + function () use ( $updates ) { + echo $updates['2-1']; + } + ); + } + ); + DeferredUpdates::addCallableUpdate( + function () use ( $updates ) { + echo $updates[3]; + } + ); + + $this->expectOutputString( implode( '', $updates ) ); + + DeferredUpdates::doUpdates(); + } } diff --git a/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php b/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php index 3bea9b31..a546bec1 100644 --- a/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php +++ b/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php @@ -69,11 +69,11 @@ class ArrayDiffFormatterTest extends MediaWikiTestCase { $otherTestCases = array(); $otherTestCases[] = array( - $this->getMockDiff( array( $this->getMockDiffOp( 'add', array( ), array( 'a1' ) ) ) ), + $this->getMockDiff( array( $this->getMockDiffOp( 'add', array(), array( 'a1' ) ) ) ), array( array( 'action' => 'add', 'new' => 'a1', 'newline' => 1 ) ), ); $otherTestCases[] = array( - $this->getMockDiff( array( $this->getMockDiffOp( 'add', array( ), array( 'a1', 'a2' ) ) ) ), + $this->getMockDiff( array( $this->getMockDiffOp( 'add', array(), array( 'a1', 'a2' ) ) ) ), array( array( 'action' => 'add', 'new' => 'a1', 'newline' => 1 ), array( 'action' => 'add', 'new' => 'a2', 'newline' => 2 ), diff --git a/tests/phpunit/includes/exception/HttpErrorTest.php b/tests/phpunit/includes/exception/HttpErrorTest.php new file mode 100644 index 00000000..66fe90c9 --- /dev/null +++ b/tests/phpunit/includes/exception/HttpErrorTest.php @@ -0,0 +1,65 @@ +<?php + +/** + * @todo tests for HttpError::report + * + * @covers HttpError + */ +class HttpErrorTest extends MediaWikiTestCase { + + public function testIsLoggable() { + $httpError = new HttpError( 500, 'server error!' ); + $this->assertFalse( $httpError->isLoggable(), 'http error is not loggable' ); + } + + public function testGetStatusCode() { + $httpError = new HttpError( 500, 'server error!' ); + $this->assertEquals( 500, $httpError->getStatusCode() ); + } + + /** + * @dataProvider getHtmlProvider + */ + public function testGetHtml( array $expected, $content, $header ) { + $httpError = new HttpError( 500, $content, $header ); + $errorHtml = $httpError->getHtml(); + + foreach ( $expected as $key => $html ) { + $this->assertContains( $html, $errorHtml, $key ); + } + } + + public function getHtmlProvider() { + return array( + array( + array( + 'head html' => '<head><title>Server Error 123</title></head>', + 'body html' => '<body><h1>Server Error 123</h1>' + . '<p>a server error!</p></body>' + ), + 'a server error!', + 'Server Error 123' + ), + array( + array( + 'head html' => '<head><title>loginerror</title></head>', + 'body html' => '<body><h1>loginerror</h1>' + . '<p>suspicious-userlogout</p></body>' + ), + new RawMessage( 'suspicious-userlogout' ), + new RawMessage( 'loginerror' ) + ), + array( + array( + 'head html' => '<html><head><title>Internal Server Error</title></head>', + 'body html' => '<body><h1>Internal Server Error</h1>' + . '<p>a server error!</p></body></html>' + ), + 'a server error!', + null + ) + ); + } + + +} diff --git a/tests/phpunit/includes/exception/MWExceptionTest.php b/tests/phpunit/includes/exception/MWExceptionTest.php index ef0f2a9e..f11fda32 100644 --- a/tests/phpunit/includes/exception/MWExceptionTest.php +++ b/tests/phpunit/includes/exception/MWExceptionTest.php @@ -178,7 +178,7 @@ class MWExceptionTest extends MediaWikiTestCase { $this->setMwGlobals( array( 'wgLogExceptionBacktrace' => true ) ); $json = json_decode( - MWExceptionHandler::jsonSerializeException( new $exClass()) + MWExceptionHandler::jsonSerializeException( new $exClass() ) ); $this->assertObjectHasAttribute( $key, $json, "JSON serialized exception is missing key '$key'" diff --git a/tests/phpunit/includes/filebackend/FileBackendTest.php b/tests/phpunit/includes/filebackend/FileBackendTest.php index bfca75ad..2e4942f0 100644 --- a/tests/phpunit/includes/filebackend/FileBackendTest.php +++ b/tests/phpunit/includes/filebackend/FileBackendTest.php @@ -2376,7 +2376,7 @@ class FileBackendTest extends MediaWikiTestCase { $status = Status::newGood(); $sl = $this->backend->getScopedFileLocks( $paths, LockManager::LOCK_EX, $status ); - $this->assertType( 'ScopedLock', $sl, + $this->assertInstanceOf( 'ScopedLock', $sl, "Scoped locking of files succeeded ($backendName)." ); $this->assertEquals( array(), $status->errors, "Scoped locking of files succeeded ($backendName)." ); @@ -2392,6 +2392,56 @@ class FileBackendTest extends MediaWikiTestCase { "Scoped unlocking of files succeeded with OK status ($backendName)." ); } + public function testReadAffinity() { + $be = TestingAccessWrapper::newFromObject( + new FileBackendMultiWrite( array( + 'name' => 'localtesting', + 'wikiId' => wfWikiId() . mt_rand(), + 'backends' => array( + array( // backend 0 + 'name' => 'multitesting0', + 'class' => 'MemoryFileBackend', + 'isMultiMaster' => false, + 'readAffinity' => true + ), + array( // backend 1 + 'name' => 'multitesting1', + 'class' => 'MemoryFileBackend', + 'isMultiMaster' => true + ) + ) + ) ) + ); + + $this->assertEquals( + 1, + $be->getReadIndexFromParams( array( 'latest' => 1 ) ), + 'Reads with "latest" flag use backend 1' + ); + $this->assertEquals( + 0, + $be->getReadIndexFromParams( array( 'latest' => 0 ) ), + 'Reads without "latest" flag use backend 0' + ); + + $p = 'container/test-cont/file.txt'; + $be->backends[0]->quickCreate( array( + 'dst' => "mwstore://multitesting0/$p", 'content' => 'cattitude' ) ); + $be->backends[1]->quickCreate( array( + 'dst' => "mwstore://multitesting1/$p", 'content' => 'princess of power' ) ); + + $this->assertEquals( + 'cattitude', + $be->getFileContents( array( 'src' => "mwstore://localtesting/$p" ) ), + "Non-latest read came from backend 0" + ); + $this->assertEquals( + 'princess of power', + $be->getFileContents( array( 'src' => "mwstore://localtesting/$p", 'latest' => 1 ) ), + "Latest read came from backend1" + ); + } + // helper function private function listToArray( $iter ) { return is_array( $iter ) ? $iter : iterator_to_array( $iter ); diff --git a/tests/phpunit/includes/filebackend/SwiftFileBackendTest.php b/tests/phpunit/includes/filebackend/SwiftFileBackendTest.php new file mode 100644 index 00000000..a618889c --- /dev/null +++ b/tests/phpunit/includes/filebackend/SwiftFileBackendTest.php @@ -0,0 +1,148 @@ +<?php + +/** + * @group FileRepo + * @group FileBackend + * @group medium + */ +class SwiftFileBackendTest extends MediaWikiTestCase { + /** @var TestingAccessWrapper Proxy to SwiftFileBackend */ + private $backend; + + protected function setUp() { + parent::setUp(); + + $this->backend = TestingAccessWrapper::newFromObject( + new SwiftFileBackend( array( + 'name' => 'local-swift-testing', + 'class' => 'SwiftFileBackend', + 'wikiId' => 'unit-testing', + 'lockManager' => LockManagerGroup::singleton()->get( 'fsLockManager' ), + 'swiftAuthUrl' => 'http://127.0.0.1:8080/auth', // unused + 'swiftUser' => 'test:tester', + 'swiftKey' => 'testing', + 'swiftTempUrlKey' => 'b3968d0207b54ece87cccc06515a89d4' // unused + ) ) + ); + } + + /** + * @dataProvider provider_testSanitzeHdrs + * @covers SwiftFileBackend::sanitzeHdrs + * @covers SwiftFileBackend::getCustomHeaders + */ + public function testSanitzeHdrs( $raw, $sanitized ) { + $hdrs = $this->backend->sanitizeHdrs( array( 'headers' => $raw ) ); + + $this->assertEquals( $hdrs, $sanitized, 'sanitizeHdrs() has expected result' ); + } + + public static function provider_testSanitzeHdrs() { + return array( + array( + array( + 'content-length' => 345, + 'content-type' => 'image+bitmap/jpeg', + 'content-disposition' => 'inline', + 'content-duration' => 35.6363, + 'content-Custom' => 'hello', + 'x-content-custom' => 'hello' + ), + array( + 'content-disposition' => 'inline', + 'content-duration' => 35.6363, + 'content-custom' => 'hello', + 'x-content-custom' => 'hello' + ) + ), + array( + array( + 'content-length' => 345, + 'content-type' => 'image+bitmap/jpeg', + 'content-Disposition' => 'inline; filename=xxx; ' . str_repeat( 'o', 1024 ), + 'content-duration' => 35.6363, + 'content-custom' => 'hello', + 'x-content-custom' => 'hello' + ), + array( + 'content-disposition' => 'inline;filename=xxx', + 'content-duration' => 35.6363, + 'content-custom' => 'hello', + 'x-content-custom' => 'hello' + ) + ), + array( + array( + 'content-length' => 345, + 'content-type' => 'image+bitmap/jpeg', + 'content-disposition' => 'filename='. str_repeat( 'o', 1024 ) . ';inline', + 'content-duration' => 35.6363, + 'content-custom' => 'hello', + 'x-content-custom' => 'hello' + ), + array( + 'content-disposition' => '', + 'content-duration' => 35.6363, + 'content-custom' => 'hello', + 'x-content-custom' => 'hello' + ) + ) + ); + } + + /** + * @dataProvider provider_testGetMetadataHeaders + * @covers SwiftFileBackend::getMetadataHeaders + */ + public function testGetMetadataHeaders( $raw, $sanitized ) { + $hdrs = $this->backend->getMetadataHeaders( $raw ); + + $this->assertEquals( $hdrs, $sanitized, 'getMetadataHeaders() has expected result' ); + } + + public static function provider_testGetMetadataHeaders() { + return array( + array( + array( + 'content-length' => 345, + 'content-custom' => 'hello', + 'x-content-custom' => 'hello', + 'x-object-meta-custom' => 5, + 'x-object-meta-sha1Base36' => 'a3deadfg...', + ), + array( + 'x-object-meta-custom' => 5, + 'x-object-meta-sha1base36' => 'a3deadfg...', + ) + ) + ); + } + + /** + * @dataProvider provider_testGetMetadata + * @covers SwiftFileBackend::getMetadata + */ + public function testGetMetadata( $raw, $sanitized ) { + $hdrs = $this->backend->getMetadata( $raw ); + + $this->assertEquals( $hdrs, $sanitized, 'getMetadata() has expected result' ); + } + + public static function provider_testGetMetadata() { + return array( + array( + array( + 'content-length' => 345, + 'content-custom' => 'hello', + 'x-content-custom' => 'hello', + 'x-object-meta-custom' => 5, + 'x-object-meta-sha1Base36' => 'a3deadfg...', + ), + array( + 'custom' => 5, + 'sha1base36' => 'a3deadfg...', + ) + ) + ); + } +}
\ No newline at end of file diff --git a/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php b/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php new file mode 100644 index 00000000..681e3681 --- /dev/null +++ b/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php @@ -0,0 +1,138 @@ +<?php + +class FileBackendDBRepoWrapperTest extends MediaWikiTestCase { + protected $backendName = 'foo-backend'; + protected $repoName = 'pureTestRepo'; + + /** + * @dataProvider getBackendPathsProvider + * @covers FileBackendDBRepoWrapper::getBackendPaths + */ + public function testGetBackendPaths( + $mocks, + $latest, + $dbReadsExpected, + $dbReturnValue, + $originalPath, + $expectedBackendPath, + $message ) { + list( $dbMock, $backendMock, $wrapperMock ) = $mocks; + + $dbMock->expects( $dbReadsExpected ) + ->method( 'selectField' ) + ->will( $this->returnValue( $dbReturnValue ) ); + + $newPaths = $wrapperMock->getBackendPaths( array( $originalPath ), $latest ); + + $this->assertEquals( + $expectedBackendPath, + $newPaths[0], + $message ); + } + + public function getBackendPathsProvider() { + $prefix = 'mwstore://' . $this->backendName . '/' . $this->repoName; + $mocksForCaching = $this->getMocks(); + + return array( + array( + $mocksForCaching, + false, + $this->once(), + '96246614d75ba1703bdfd5d7660bb57407aaf5d9', + $prefix . '-public/f/o/foobar.jpg', + $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9', + 'Public path translated correctly', + ), + array( + $mocksForCaching, + false, + $this->never(), + '96246614d75ba1703bdfd5d7660bb57407aaf5d9', + $prefix . '-public/f/o/foobar.jpg', + $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9', + 'LRU cache leveraged', + ), + array( + $this->getMocks(), + true, + $this->once(), + '96246614d75ba1703bdfd5d7660bb57407aaf5d9', + $prefix . '-public/f/o/foobar.jpg', + $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9', + 'Latest obtained', + ), + array( + $this->getMocks(), + true, + $this->never(), + '96246614d75ba1703bdfd5d7660bb57407aaf5d9', + $prefix . '-deleted/f/o/foobar.jpg', + $prefix . '-original/f/o/o/foobar', + 'Deleted path translated correctly', + ), + array( + $this->getMocks(), + true, + $this->once(), + null, + $prefix . '-public/b/a/baz.jpg', + $prefix . '-public/b/a/baz.jpg', + 'Path left untouched if no sha1 can be found', + ), + ); + } + + /** + * @covers FileBackendDBRepoWrapper::getFileContentsMulti + */ + public function testGetFileContentsMulti() { + list( $dbMock, $backendMock, $wrapperMock ) = $this->getMocks(); + + $sha1Path = 'mwstore://' . $this->backendName . '/' . $this->repoName + . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9'; + $filenamePath = 'mwstore://' . $this->backendName . '/' . $this->repoName + . '-public/f/o/foobar.jpg'; + + $dbMock->expects( $this->once() ) + ->method( 'selectField' ) + ->will( $this->returnValue( '96246614d75ba1703bdfd5d7660bb57407aaf5d9' ) ); + + $backendMock->expects( $this->once() ) + ->method( 'getFileContentsMulti') + ->will( $this->returnValue( array( $sha1Path => 'foo' ) ) ); + + $result = $wrapperMock->getFileContentsMulti( array( 'srcs' => array( $filenamePath ) ) ); + + $this->assertEquals( + array( $filenamePath => 'foo' ), + $result, + 'File contents paths translated properly' + ); + } + + protected function getMocks() { + $dbMock = $this->getMockBuilder( 'DatabaseMysql' ) + ->disableOriginalConstructor() + ->getMock(); + + $backendMock = $this->getMock( 'FSFileBackend', + array(), + array( array( + 'name' => $this->backendName, + 'wikiId' => wfWikiId() + ) ) ); + + $wrapperMock = $this->getMock( 'FileBackendDBRepoWrapper', + array( 'getDB' ), + array( array( + 'backend' => $backendMock, + 'repoName' => $this->repoName, + 'dbHandleFactory' => null + ) ) ); + + $wrapperMock->expects( $this->any() )->method( 'getDB' )->will( $this->returnValue( $dbMock ) ); + + return array( $dbMock, $backendMock, $wrapperMock ); + } +} diff --git a/tests/phpunit/includes/filerepo/MigrateFileRepoLayoutTest.php b/tests/phpunit/includes/filerepo/MigrateFileRepoLayoutTest.php new file mode 100644 index 00000000..551d3a76 --- /dev/null +++ b/tests/phpunit/includes/filerepo/MigrateFileRepoLayoutTest.php @@ -0,0 +1,114 @@ +<?php + +class MigrateFileRepoLayoutTest extends MediaWikiTestCase { + protected $tmpPrefix; + protected $migratorMock; + protected $tmpFilepath; + protected $text = 'testing'; + + protected function setUp() { + parent::setUp(); + + $filename = 'Foo.png'; + + $this->tmpPrefix = wfTempDir() . '/migratefilelayout-test-' . time() . '-' . mt_rand(); + + $backend = new FSFileBackend( array( + 'name' => 'local-migratefilerepolayouttest', + 'wikiId' => wfWikiID(), + 'containerPaths' => array( + 'migratefilerepolayouttest-original' => "{$this->tmpPrefix}-original", + 'migratefilerepolayouttest-public' => "{$this->tmpPrefix}-public", + 'migratefilerepolayouttest-thumb' => "{$this->tmpPrefix}-thumb", + 'migratefilerepolayouttest-temp' => "{$this->tmpPrefix}-temp", + 'migratefilerepolayouttest-deleted' => "{$this->tmpPrefix}-deleted", + ) + ) ); + + $dbMock = $this->getMockBuilder( 'DatabaseMysql' ) + ->disableOriginalConstructor() + ->getMock(); + + $imageRow = new stdClass; + $imageRow->img_name = $filename; + $imageRow->img_sha1 = sha1( $this->text ); + + $dbMock->expects( $this->any() ) + ->method( 'select' ) + ->will( $this->onConsecutiveCalls( + new FakeResultWrapper( array( $imageRow ) ), // image + new FakeResultWrapper( array() ), // image + new FakeResultWrapper( array() ) // filearchive + ) ); + + $repoMock = $this->getMock( 'LocalRepo', + array( 'getMasterDB' ), + array( array( + 'name' => 'migratefilerepolayouttest', + 'backend' => $backend + ) ) ); + + $repoMock->expects( $this->any() )->method( 'getMasterDB' )->will( $this->returnValue( $dbMock ) ); + + $this->migratorMock = $this->getMock( 'MigrateFileRepoLayout', array( 'getRepo' ) ); + $this->migratorMock->expects( $this->any() )->method( 'getRepo' )->will( $this->returnValue( $repoMock ) ); + + $this->tmpFilepath = TempFSFile::factory( 'migratefilelayout-test-', 'png' )->getPath(); + + file_put_contents( $this->tmpFilepath, $this->text ); + + $hashPath = $repoMock->getHashPath( $filename ); + + $status = $repoMock->store( $this->tmpFilepath, 'public', $hashPath . $filename, FileRepo::OVERWRITE ); + } + + protected function deleteFilesRecursively( $directory ) { + foreach ( glob( $directory . '/*' ) as $file ) { + if ( is_dir( $file ) ) { + $this->deleteFilesRecursively( $file ); + } else { + unlink( $file ); + } + } + + rmdir( $directory ); + } + + protected function tearDown() { + foreach ( glob( $this->tmpPrefix . '*' ) as $directory ) { + $this->deleteFilesRecursively( $directory ); + } + + unlink( $this->tmpFilepath ); + + parent::tearDown(); + } + + public function testMigration() { + $this->migratorMock->loadParamsAndArgs( null, array( 'oldlayout' => 'name', 'newlayout' => 'sha1' ) ); + + ob_start(); + + $this->migratorMock->execute(); + + ob_end_clean(); + + $sha1 = sha1( $this->text ); + + $expectedOriginalFilepath = $this->tmpPrefix + . '-original/' + . substr( $sha1, 0, 1 ) + . '/' + . substr( $sha1, 1, 1 ) + . '/' + . substr( $sha1, 2, 1 ) + . '/' + . $sha1 ; + + $this->assertEquals( file_get_contents( $expectedOriginalFilepath ), $this->text, 'New sha1 file should be exist and have the right contents' ); + + $expectedPublicFilepath = $this->tmpPrefix . '-public/f/f8/Foo.png'; + + $this->assertEquals( file_get_contents( $expectedPublicFilepath ), $this->text, 'Existing name file should still and have the right contents' ); + } +} diff --git a/tests/phpunit/includes/htmlform/HTMLAutoCompleteSelectFieldTest.php b/tests/phpunit/includes/htmlform/HTMLAutoCompleteSelectFieldTest.php index 2c7f50c9..3b5347cd 100644 --- a/tests/phpunit/includes/htmlform/HTMLAutoCompleteSelectFieldTest.php +++ b/tests/phpunit/includes/htmlform/HTMLAutoCompleteSelectFieldTest.php @@ -6,7 +6,7 @@ */ class HtmlAutoCompleteSelectFieldTest extends MediaWikiTestCase { - var $options = array( + public $options = array( 'Bulgaria' => 'BGR', 'Burkina Faso' => 'BFA', 'Burundi' => 'BDI', diff --git a/tests/phpunit/includes/json/FormatJsonTest.php b/tests/phpunit/includes/json/FormatJsonTest.php index f0ac6acc..8bca3331 100644 --- a/tests/phpunit/includes/json/FormatJsonTest.php +++ b/tests/phpunit/includes/json/FormatJsonTest.php @@ -159,12 +159,12 @@ class FormatJsonTest extends MediaWikiTestCase { $this->assertJson( $json ); $st = FormatJson::parse( $json ); - $this->assertType( 'Status', $st ); + $this->assertInstanceOf( 'Status', $st ); $this->assertTrue( $st->isGood() ); $this->assertEquals( $expected, $st->getValue() ); $st = FormatJson::parse( $json, FormatJson::FORCE_ASSOC ); - $this->assertType( 'Status', $st ); + $this->assertInstanceOf( 'Status', $st ); $this->assertTrue( $st->isGood() ); $this->assertEquals( $value, $st->getValue() ); } @@ -230,7 +230,7 @@ class FormatJsonTest extends MediaWikiTestCase { } $st = FormatJson::parse( $value, FormatJson::TRY_FIXING ); - $this->assertType( 'Status', $st ); + $this->assertInstanceOf( 'Status', $st ); if ( $expected === false ) { $this->assertFalse( $st->isOK(), 'Expected isOK() == false' ); } else { @@ -256,7 +256,7 @@ class FormatJsonTest extends MediaWikiTestCase { */ public function testParseErrors( $value ) { $st = FormatJson::parse( $value ); - $this->assertType( 'Status', $st ); + $this->assertInstanceOf( 'Status', $st ); $this->assertFalse( $st->isOK() ); } @@ -313,7 +313,7 @@ class FormatJsonTest extends MediaWikiTestCase { */ public function testParseStripComments( $json, $expect ) { $st = FormatJson::parse( $json, FormatJson::STRIP_COMMENTS ); - $this->assertType( 'Status', $st ); + $this->assertInstanceOf( 'Status', $st ); $this->assertTrue( $st->isGood() ); $this->assertEquals( $expect, $st->getValue() ); } diff --git a/tests/phpunit/includes/libs/ArrayUtilsTest.php b/tests/phpunit/includes/libs/ArrayUtilsTest.php index b5ea7b72..32b150c7 100644 --- a/tests/phpunit/includes/libs/ArrayUtilsTest.php +++ b/tests/phpunit/includes/libs/ArrayUtilsTest.php @@ -23,11 +23,11 @@ class ArrayUtilsTest extends PHPUnit_Framework_TestCase { } function provideFindLowerBound() { - $self = $this; - $indexValueCallback = function ( $size ) use ( $self ) { - return function ( $val ) use ( $self, $size ) { - $self->assertTrue( $val >= 0 ); - $self->assertTrue( $val < $size ); + $that = $this; + $indexValueCallback = function ( $size ) use ( $that ) { + return function ( $val ) use ( $that, $size ) { + $that->assertTrue( $val >= 0 ); + $that->assertTrue( $val < $size ); return $val; }; }; @@ -212,7 +212,7 @@ class ArrayUtilsTest extends PHPUnit_Framework_TestCase { array(), array( 1 => 1 ), array( 1 ), - array( 1 => 1), + array( 1 => 1 ), ), array( array(), diff --git a/tests/phpunit/includes/libs/CSSMinTest.php b/tests/phpunit/includes/libs/CSSMinTest.php index 6142f967..7841f30f 100644 --- a/tests/phpunit/includes/libs/CSSMinTest.php +++ b/tests/phpunit/includes/libs/CSSMinTest.php @@ -102,12 +102,12 @@ class CSSMinTest extends MediaWikiTestCase { array( 'Without trailing slash', array( 'foo { prop: url(../bar.png); }', false, 'http://example.org/quux', false ), - 'foo { prop: url(http://example.org/quux/../bar.png); }', + 'foo { prop: url(http://example.org/bar.png); }', ), array( 'With trailing slash on remote (bug 27052)', array( 'foo { prop: url(../bar.png); }', false, 'http://example.org/quux/', false ), - 'foo { prop: url(http://example.org/quux/../bar.png); }', + 'foo { prop: url(http://example.org/bar.png); }', ), array( 'Guard against stripping double slashes from query', @@ -133,12 +133,7 @@ class CSSMinTest extends MediaWikiTestCase { $remotePath = 'http://localhost/w/'; $realOutput = CSSMin::remap( $input, $localPath, $remotePath ); - - $this->assertEquals( - $expectedOutput, - preg_replace( '/\d+-\d+-\d+T\d+:\d+:\d+Z/', 'timestamp', $realOutput ), - "CSSMin::remap: $message" - ); + $this->assertEquals( $expectedOutput, $realOutput, "CSSMin::remap: $message" ); } public static function provideIsRemoteUrl() { @@ -197,7 +192,7 @@ class CSSMinTest extends MediaWikiTestCase { array( 'Regular file', 'foo { background: url(red.gif); }', - 'foo { background: url(http://localhost/w/red.gif?timestamp); }', + 'foo { background: url(http://localhost/w/red.gif?34ac6); }', ), array( 'Regular file (missing)', @@ -242,12 +237,12 @@ class CSSMinTest extends MediaWikiTestCase { array( 'Embedded file', 'foo { /* @embed */ background: url(red.gif); }', - "foo { background: url($red); background: url(http://localhost/w/red.gif?timestamp)!ie; }", + "foo { background: url($red); background: url(http://localhost/w/red.gif?34ac6)!ie; }", ), array( 'Embedded file, other comments before the rule', "foo { /* Bar. */ /* @embed */ background: url(red.gif); }", - "foo { /* Bar. */ background: url($red); /* Bar. */ background: url(http://localhost/w/red.gif?timestamp)!ie; }", + "foo { /* Bar. */ background: url($red); /* Bar. */ background: url(http://localhost/w/red.gif?34ac6)!ie; }", ), array( 'Can not re-embed data: URIs', @@ -268,12 +263,12 @@ class CSSMinTest extends MediaWikiTestCase { 'Embedded file (inline @embed)', 'foo { background: /* @embed */ url(red.gif); }', "foo { background: url($red); " - . "background: url(http://localhost/w/red.gif?timestamp)!ie; }", + . "background: url(http://localhost/w/red.gif?34ac6)!ie; }", ), array( 'Can not embed large files', 'foo { /* @embed */ background: url(large.png); }', - "foo { background: url(http://localhost/w/large.png?timestamp); }", + "foo { background: url(http://localhost/w/large.png?e3d1f); }", ), array( 'SVG files are embedded without base64 encoding and unnecessary IE 6 and 7 fallback', @@ -283,55 +278,55 @@ class CSSMinTest extends MediaWikiTestCase { array( 'Two regular files in one rule', 'foo { background: url(red.gif), url(green.gif); }', - 'foo { background: url(http://localhost/w/red.gif?timestamp), ' - . 'url(http://localhost/w/green.gif?timestamp); }', + 'foo { background: url(http://localhost/w/red.gif?34ac6), ' + . 'url(http://localhost/w/green.gif?13651); }', ), array( 'Two embedded files in one rule', 'foo { /* @embed */ background: url(red.gif), url(green.gif); }', "foo { background: url($red), url($green); " - . "background: url(http://localhost/w/red.gif?timestamp), " - . "url(http://localhost/w/green.gif?timestamp)!ie; }", + . "background: url(http://localhost/w/red.gif?34ac6), " + . "url(http://localhost/w/green.gif?13651)!ie; }", ), array( 'Two embedded files in one rule (inline @embed)', 'foo { background: /* @embed */ url(red.gif), /* @embed */ url(green.gif); }', "foo { background: url($red), url($green); " - . "background: url(http://localhost/w/red.gif?timestamp), " - . "url(http://localhost/w/green.gif?timestamp)!ie; }", + . "background: url(http://localhost/w/red.gif?34ac6), " + . "url(http://localhost/w/green.gif?13651)!ie; }", ), array( 'Two embedded files in one rule (inline @embed), one too large', 'foo { background: /* @embed */ url(red.gif), /* @embed */ url(large.png); }', - "foo { background: url($red), url(http://localhost/w/large.png?timestamp); " - . "background: url(http://localhost/w/red.gif?timestamp), " - . "url(http://localhost/w/large.png?timestamp)!ie; }", + "foo { background: url($red), url(http://localhost/w/large.png?e3d1f); " + . "background: url(http://localhost/w/red.gif?34ac6), " + . "url(http://localhost/w/large.png?e3d1f)!ie; }", ), array( 'Practical example with some noise', 'foo { /* @embed */ background: #f9f9f9 url(red.gif) 0 0 no-repeat; }', "foo { background: #f9f9f9 url($red) 0 0 no-repeat; " - . "background: #f9f9f9 url(http://localhost/w/red.gif?timestamp) 0 0 no-repeat!ie; }", + . "background: #f9f9f9 url(http://localhost/w/red.gif?34ac6) 0 0 no-repeat!ie; }", ), array( 'Does not mess with other properties', 'foo { color: red; background: url(red.gif); font-size: small; }', - 'foo { color: red; background: url(http://localhost/w/red.gif?timestamp); font-size: small; }', + 'foo { color: red; background: url(http://localhost/w/red.gif?34ac6); font-size: small; }', ), array( 'Spacing and miscellanea not changed (1)', 'foo { background: url(red.gif); }', - 'foo { background: url(http://localhost/w/red.gif?timestamp); }', + 'foo { background: url(http://localhost/w/red.gif?34ac6); }', ), array( 'Spacing and miscellanea not changed (2)', 'foo {background:url(red.gif)}', - 'foo {background:url(http://localhost/w/red.gif?timestamp)}', + 'foo {background:url(http://localhost/w/red.gif?34ac6)}', ), array( 'Spaces within url() parentheses are ignored', 'foo { background: url( red.gif ); }', - 'foo { background: url(http://localhost/w/red.gif?timestamp); }', + 'foo { background: url(http://localhost/w/red.gif?34ac6); }', ), array( '@import rule to local file (should we remap this?)', @@ -351,22 +346,22 @@ class CSSMinTest extends MediaWikiTestCase { array( 'Simple case with comments after url', 'foo { prop: url(red.gif)/* some {funny;} comment */ ; }', - 'foo { prop: url(http://localhost/w/red.gif?timestamp)/* some {funny;} comment */ ; }', + 'foo { prop: url(http://localhost/w/red.gif?34ac6)/* some {funny;} comment */ ; }', ), array( 'Embedded file with comment before url', 'foo { /* @embed */ background: /* some {funny;} comment */ url(red.gif); }', - "foo { background: /* some {funny;} comment */ url($red); background: /* some {funny;} comment */ url(http://localhost/w/red.gif?timestamp)!ie; }", + "foo { background: /* some {funny;} comment */ url($red); background: /* some {funny;} comment */ url(http://localhost/w/red.gif?34ac6)!ie; }", ), array( 'Embedded file with comments inside and outside the rule', 'foo { /* @embed */ background: url(red.gif) /* some {foo;} comment */; /* some {bar;} comment */ }', - "foo { background: url($red) /* some {foo;} comment */; background: url(http://localhost/w/red.gif?timestamp) /* some {foo;} comment */!ie; /* some {bar;} comment */ }", + "foo { background: url($red) /* some {foo;} comment */; background: url(http://localhost/w/red.gif?34ac6) /* some {foo;} comment */!ie; /* some {bar;} comment */ }", ), array( 'Embedded file with comment outside the rule', 'foo { /* @embed */ background: url(red.gif); /* some {funny;} comment */ }', - "foo { background: url($red); background: url(http://localhost/w/red.gif?timestamp)!ie; /* some {funny;} comment */ }", + "foo { background: url($red); background: url(http://localhost/w/red.gif?34ac6)!ie; /* some {funny;} comment */ }", ), array( 'Rule with two urls, each with comments', diff --git a/tests/phpunit/includes/libs/IEUrlExtensionTest.php b/tests/phpunit/includes/libs/IEUrlExtensionTest.php index e96953ee..57668e50 100644 --- a/tests/phpunit/includes/libs/IEUrlExtensionTest.php +++ b/tests/phpunit/includes/libs/IEUrlExtensionTest.php @@ -170,4 +170,37 @@ class IEUrlExtensionTest extends PHPUnit_Framework_TestCase { 'Two dots' ); } + + /** + * @covers IEUrlExtension::findIE6Extension + */ + public function testScriptQuery() { + $this->assertEquals( + 'php', + IEUrlExtension::findIE6Extension( 'example.php?foo=a&bar=b' ), + 'Script with query' + ); + } + + /** + * @covers IEUrlExtension::findIE6Extension + */ + public function testEscapedScriptQuery() { + $this->assertEquals( + '', + IEUrlExtension::findIE6Extension( 'example%2Ephp?foo=a&bar=b' ), + 'Script with urlencoded dot and query' + ); + } + + /** + * @covers IEUrlExtension::findIE6Extension + */ + public function testEscapedScriptQueryDot() { + $this->assertEquals( + 'y', + IEUrlExtension::findIE6Extension( 'example%2Ephp?foo=a.x&bar=b.y' ), + 'Script with urlencoded dot and query with dot' + ); + } } diff --git a/tests/phpunit/includes/libs/IPSetTest.php b/tests/phpunit/includes/libs/IPSetTest.php deleted file mode 100644 index 5bbacef4..00000000 --- a/tests/phpunit/includes/libs/IPSetTest.php +++ /dev/null @@ -1,252 +0,0 @@ -<?php - -/** - * @group IPSet - */ -class IPSetTest extends PHPUnit_Framework_TestCase { - /** - * Provides test cases for IPSetTest::testIPSet - * - * Returns an array of test cases. Each case is an array of (description, - * config, tests). Description is just text output for failure messages, - * config is an array constructor argument for IPSet, and the tests are - * an array of IP => expected (boolean) result against the config dataset. - */ - public static function provideIPSets() { - return array( - array( - 'old_list_subset', - array( - '208.80.152.162', - '10.64.0.123', - '10.64.0.124', - '10.64.0.125', - '10.64.0.126', - '10.64.0.127', - '10.64.0.128', - '10.64.0.129', - '10.64.32.104', - '10.64.32.105', - '10.64.32.106', - '10.64.32.107', - '91.198.174.45', - '91.198.174.46', - '91.198.174.47', - '91.198.174.57', - '2620:0:862:1:A6BA:DBFF:FE30:CFB3', - '91.198.174.58', - '2620:0:862:1:A6BA:DBFF:FE38:FFDA', - '208.80.152.16', - '208.80.152.17', - '208.80.152.18', - '208.80.152.19', - '91.198.174.102', - '91.198.174.103', - '91.198.174.104', - '91.198.174.105', - '91.198.174.106', - '91.198.174.107', - '91.198.174.81', - '2620:0:862:1:26B6:FDFF:FEF5:B2D4', - '91.198.174.82', - '2620:0:862:1:26B6:FDFF:FEF5:ABB4', - '10.20.0.113', - '2620:0:862:102:26B6:FDFF:FEF5:AD9C', - '10.20.0.114', - '2620:0:862:102:26B6:FDFF:FEF5:7C38', - ), - array( - '0.0.0.0' => false, - '255.255.255.255' => false, - '10.64.0.122' => false, - '10.64.0.123' => true, - '10.64.0.124' => true, - '10.64.0.129' => true, - '10.64.0.130' => false, - '91.198.174.81' => true, - '91.198.174.80' => false, - '0::0' => false, - 'ffff:ffff:ffff:ffff:FFFF:FFFF:FFFF:FFFF' => false, - '2001:db8::1234' => false, - '2620:0:862:1:26b6:fdff:fef5:abb3' => false, - '2620:0:862:1:26b6:fdff:fef5:abb4' => true, - '2620:0:862:1:26b6:fdff:fef5:abb5' => false, - ), - ), - array( - 'new_cidr_set', - array( - '208.80.154.0/26', - '2620:0:861:1::/64', - '208.80.154.128/26', - '2620:0:861:2::/64', - '208.80.154.64/26', - '2620:0:861:3::/64', - '208.80.155.96/27', - '2620:0:861:4::/64', - '10.64.0.0/22', - '2620:0:861:101::/64', - '10.64.16.0/22', - '2620:0:861:102::/64', - '10.64.32.0/22', - '2620:0:861:103::/64', - '10.64.48.0/22', - '2620:0:861:107::/64', - '91.198.174.0/25', - '2620:0:862:1::/64', - '10.20.0.0/24', - '2620:0:862:102::/64', - '10.128.0.0/24', - '2620:0:863:101::/64', - '10.2.4.26', - ), - array( - '0.0.0.0' => false, - '255.255.255.255' => false, - '10.2.4.25' => false, - '10.2.4.26' => true, - '10.2.4.27' => false, - '10.20.0.255' => true, - '10.128.0.0' => true, - '10.64.17.55' => true, - '10.64.20.0' => false, - '10.64.27.207' => false, - '10.64.31.255' => false, - '0::0' => false, - 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' => false, - '2001:DB8::1' => false, - '2620:0:861:106::45' => false, - '2620:0:862:103::' => false, - '2620:0:862:102:10:20:0:113' => true, - ), - ), - array( - 'empty_set', - array(), - array( - '0.0.0.0' => false, - '255.255.255.255' => false, - '10.2.4.25' => false, - '10.2.4.26' => false, - '10.2.4.27' => false, - '10.20.0.255' => false, - '10.128.0.0' => false, - '10.64.17.55' => false, - '10.64.20.0' => false, - '10.64.27.207' => false, - '10.64.31.255' => false, - '0::0' => false, - 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' => false, - '2001:DB8::1' => false, - '2620:0:861:106::45' => false, - '2620:0:862:103::' => false, - '2620:0:862:102:10:20:0:113' => false, - ), - ), - array( - 'edge_cases', - array( - '0.0.0.0', - '255.255.255.255', - '::', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', - '10.10.10.10/25', // host bits intentional - ), - array( - '0.0.0.0' => true, - '255.255.255.255' => true, - '10.2.4.25' => false, - '10.2.4.26' => false, - '10.2.4.27' => false, - '10.20.0.255' => false, - '10.128.0.0' => false, - '10.64.17.55' => false, - '10.64.20.0' => false, - '10.64.27.207' => false, - '10.64.31.255' => false, - '0::0' => true, - 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' => true, - '2001:DB8::1' => false, - '2620:0:861:106::45' => false, - '2620:0:862:103::' => false, - '2620:0:862:102:10:20:0:113' => false, - '10.10.9.255' => false, - '10.10.10.0' => true, - '10.10.10.1' => true, - '10.10.10.10' => true, - '10.10.10.126' => true, - '10.10.10.127' => true, - '10.10.10.128' => false, - '10.10.10.177' => false, - '10.10.10.255' => false, - '10.10.11.0' => false, - ), - ), - array( - 'exercise_optimizer', - array( - 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fffe:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fffd:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fffc:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fffb:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fffa:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff9:8000/113', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff9:0/113', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff8:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff7:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff6:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff5:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff4:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff3:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff2:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff1:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:fff0:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffef:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffee:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffec:0/111', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffeb:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffea:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe9:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe8:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe7:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe6:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe5:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe4:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe3:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe2:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe1:0/112', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffe0:0/110', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffc0:0/107', - 'ffff:ffff:ffff:ffff:ffff:ffff:ffa0:0/107', - ), - array( - '0.0.0.0' => false, - '255.255.255.255' => false, - '::' => false, - 'ffff:ffff:ffff:ffff:ffff:ffff:ff9f:ffff' => false, - 'ffff:ffff:ffff:ffff:ffff:ffff:ffa0:0' => true, - 'ffff:ffff:ffff:ffff:ffff:ffff:ffc0:1234' => true, - 'ffff:ffff:ffff:ffff:ffff:ffff:ffed:ffff' => true, - 'ffff:ffff:ffff:ffff:ffff:ffff:fff4:4444' => true, - 'ffff:ffff:ffff:ffff:ffff:ffff:fff9:8080' => true, - 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' => true, - ), - ), - ); - } - - /** - * Validates IPSet loading and matching code - * - * @covers IPSet - * @dataProvider provideIPSets - */ - public function testIPSet( $desc, array $cfg, array $tests ) { - $ipset = new IPSet( $cfg ); - foreach ( $tests as $ip => $expected ) { - $result = $ipset->match( $ip ); - $this->assertEquals( $expected, $result, "Incorrect match() result for $ip in dataset $desc" ); - } - } -} diff --git a/tests/phpunit/includes/libs/JavaScriptMinifierTest.php b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php index 149a28c1..d23534ed 100644 --- a/tests/phpunit/includes/libs/JavaScriptMinifierTest.php +++ b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php @@ -140,6 +140,13 @@ class JavaScriptMinifierTest extends PHPUnit_Framework_TestCase { array( "5..toString();", "5..toString();" ), array( "5...toString();", false ), array( "5.\n.toString();", '5..toString();' ), + + // Boolean minification (!0 / !1) + array( "var a = { b: true };", "var a={b:!0};" ), + array( "var a = { true: 12 };", "var a={true:12};", false ), + array( "a.true = 12;", "a.true=12;", false ), + array( "a.foo = true;", "a.foo=!0;" ), + array( "a.foo = false;", "a.foo=!1;" ), ); } @@ -147,15 +154,17 @@ class JavaScriptMinifierTest extends PHPUnit_Framework_TestCase { * @dataProvider provideCases * @covers JavaScriptMinifier::minify */ - public function testJavaScriptMinifierOutput( $code, $expectedOutput ) { + public function testJavaScriptMinifierOutput( $code, $expectedOutput, $expectedValid = true ) { $minified = JavaScriptMinifier::minify( $code ); // JSMin+'s parser will throw an exception if output is not valid JS. // suppression of warnings needed for stupid crap - wfSuppressWarnings(); - $parser = new JSParser(); - wfRestoreWarnings(); - $parser->parse( $minified, 'minify-test.js', 1 ); + if ( $expectedValid ) { + MediaWiki\suppressWarnings(); + $parser = new JSParser(); + MediaWiki\restoreWarnings(); + $parser->parse( $minified, 'minify-test.js', 1 ); + } $this->assertEquals( $expectedOutput, diff --git a/tests/phpunit/includes/libs/ObjectFactoryTest.php b/tests/phpunit/includes/libs/ObjectFactoryTest.php index 92207325..aea037e0 100644 --- a/tests/phpunit/includes/libs/ObjectFactoryTest.php +++ b/tests/phpunit/includes/libs/ObjectFactoryTest.php @@ -26,11 +26,20 @@ class ObjectFactoryTest extends PHPUnit_Framework_TestCase { public function testClosureExpansionDisabled() { $obj = ObjectFactory::getObjectFromSpec( array( 'class' => 'ObjectFactoryTest_Fixture', - 'args' => array( function (){ return 'unwrapped'; }, ), + 'args' => array( function() { + return 'unwrapped'; + }, ), + 'calls' => array( + 'setter' => array( function() { + return 'unwrapped'; + }, ), + ), 'closure_expansion' => false, ) ); $this->assertInstanceOf( 'Closure', $obj->args[0] ); $this->assertSame( 'unwrapped', $obj->args[0]() ); + $this->assertInstanceOf( 'Closure', $obj->setterArgs[0] ); + $this->assertSame( 'unwrapped', $obj->setterArgs[0]() ); } /** @@ -39,22 +48,46 @@ class ObjectFactoryTest extends PHPUnit_Framework_TestCase { public function testClosureExpansionEnabled() { $obj = ObjectFactory::getObjectFromSpec( array( 'class' => 'ObjectFactoryTest_Fixture', - 'args' => array( function (){ return 'unwrapped'; }, ), + 'args' => array( function() { + return 'unwrapped'; + }, ), + 'calls' => array( + 'setter' => array( function() { + return 'unwrapped'; + }, ), + ), 'closure_expansion' => true, ) ); $this->assertInternalType( 'string', $obj->args[0] ); $this->assertSame( 'unwrapped', $obj->args[0] ); + $this->assertInternalType( 'string', $obj->setterArgs[0] ); + $this->assertSame( 'unwrapped', $obj->setterArgs[0] ); $obj = ObjectFactory::getObjectFromSpec( array( 'class' => 'ObjectFactoryTest_Fixture', - 'args' => array( function (){ return 'unwrapped'; }, ), + 'args' => array( function() { + return 'unwrapped'; + }, ), + 'calls' => array( + 'setter' => array( function() { + return 'unwrapped'; + }, ), + ), ) ); $this->assertInternalType( 'string', $obj->args[0] ); $this->assertSame( 'unwrapped', $obj->args[0] ); + $this->assertInternalType( 'string', $obj->setterArgs[0] ); + $this->assertSame( 'unwrapped', $obj->setterArgs[0] ); } } class ObjectFactoryTest_Fixture { public $args; - public function __construct( /*...*/ ) { $this->args = func_get_args(); } + public $setterArgs; + public function __construct( /*...*/ ) { + $this->args = func_get_args(); + } + public function setter( /*...*/ ) { + $this->setterArgs = func_get_args(); + } } diff --git a/tests/phpunit/includes/libs/ProcessCacheLRUTest.php b/tests/phpunit/includes/libs/ProcessCacheLRUTest.php index 43001979..cec662a9 100644 --- a/tests/phpunit/includes/libs/ProcessCacheLRUTest.php +++ b/tests/phpunit/includes/libs/ProcessCacheLRUTest.php @@ -70,7 +70,7 @@ class ProcessCacheLRUTest extends PHPUnit_Framework_TestCase { /** * @dataProvider provideInvalidConstructorArg - * @expectedException UnexpectedValueException + * @expectedException Wikimedia\Assert\ParameterAssertionException * @covers ProcessCacheLRU::__construct */ public function testConstructorGivenInvalidValue( $maxSize ) { diff --git a/tests/phpunit/includes/libs/SamplingStatsdClientTest.php b/tests/phpunit/includes/libs/SamplingStatsdClientTest.php new file mode 100644 index 00000000..be6732d5 --- /dev/null +++ b/tests/phpunit/includes/libs/SamplingStatsdClientTest.php @@ -0,0 +1,43 @@ +<?php + +use Liuggio\StatsdClient\Entity\StatsdData; + +class SamplingStatsdClientTest extends PHPUnit_Framework_TestCase { + /** + * @dataProvider samplingDataProvider + */ + public function testSampling( $data, $sampleRate, $seed, $expectWrite ) { + $sender = $this->getMock( 'Liuggio\StatsdClient\Sender\SenderInterface' ); + $sender->expects( $this->any() )->method( 'open' )->will( $this->returnValue( true ) ); + if ( $expectWrite ) { + $sender->expects( $this->once() )->method( 'write' ) + ->with( $this->anything(), $this->equalTo( $data ) ); + } else { + $sender->expects( $this->never() )->method( 'write' ); + } + mt_srand( $seed ); + $client = new SamplingStatsdClient( $sender ); + $client->send( $data, $sampleRate ); + } + + public function samplingDataProvider() { + $unsampled = new StatsdData(); + $unsampled->setKey( 'foo' ); + $unsampled->setValue( 1 ); + + $sampled = new StatsdData(); + $sampled->setKey( 'foo' ); + $sampled->setValue( 1 ); + $sampled->setSampleRate( '0.1' ); + + return array( + // $data, $sampleRate, $seed, $expectWrite + array( $unsampled, 1, 0 /*0.44*/, $unsampled ), + array( $sampled, 1, 0 /*0.44*/, null ), + array( $sampled, 1, 4 /*0.03*/, $sampled ), + array( $unsampled, 0.1, 4 /*0.03*/, $sampled ), + array( $sampled, 0.5, 0 /*0.44*/, null ), + array( $sampled, 0.5, 4 /*0.03*/, $sampled ), + ); + } +} diff --git a/tests/phpunit/includes/libs/XhprofTest.php b/tests/phpunit/includes/libs/XhprofTest.php index 2440fc08..77b188cf 100644 --- a/tests/phpunit/includes/libs/XhprofTest.php +++ b/tests/phpunit/includes/libs/XhprofTest.php @@ -255,43 +255,43 @@ class XhprofTest extends PHPUnit_Framework_TestCase { */ protected function getXhprofFixture( array $opts = array() ) { $xhprof = new Xhprof( $opts ); - $xhprof->loadRawData( array ( - 'foo==>bar' => array ( + $xhprof->loadRawData( array( + 'foo==>bar' => array( 'ct' => 2, 'wt' => 57, 'cpu' => 92, 'mu' => 1896, 'pmu' => 0, ), - 'foo==>strlen' => array ( + 'foo==>strlen' => array( 'ct' => 2, 'wt' => 21, 'cpu' => 141, 'mu' => 752, 'pmu' => 0, ), - 'bar==>bar@1' => array ( + 'bar==>bar@1' => array( 'ct' => 1, 'wt' => 18, 'cpu' => 19, 'mu' => 752, 'pmu' => 0, ), - 'main()==>foo' => array ( + 'main()==>foo' => array( 'ct' => 1, 'wt' => 304, 'cpu' => 307, 'mu' => 4008, 'pmu' => 0, ), - 'main()==>xhprof_disable' => array ( + 'main()==>xhprof_disable' => array( 'ct' => 1, 'wt' => 8, 'cpu' => 10, 'mu' => 768, 'pmu' => 392, ), - 'main()' => array ( + 'main()' => array( 'ct' => 1, 'wt' => 353, 'cpu' => 351, @@ -311,7 +311,7 @@ class XhprofTest extends PHPUnit_Framework_TestCase { */ protected function assertArrayStructure( $struct, $actual, $label = null ) { $this->assertInternalType( 'array', $actual, $label ); - $this->assertCount( count($struct), $actual, $label ); + $this->assertCount( count( $struct ), $actual, $label ); foreach ( $struct as $key => $type ) { $this->assertArrayHasKey( $key, $actual ); $this->assertInternalType( $type, $actual[$key] ); diff --git a/tests/phpunit/includes/libs/composer/ComposerLockTest.php b/tests/phpunit/includes/libs/composer/ComposerLockTest.php index b5fd5f6e..cac3b101 100644 --- a/tests/phpunit/includes/libs/composer/ComposerLockTest.php +++ b/tests/phpunit/includes/libs/composer/ComposerLockTest.php @@ -27,34 +27,95 @@ class ComposerLockTest extends MediaWikiTestCase { 'wikimedia/cdb' => array( 'version' => '1.0.1', 'type' => 'library', + 'licenses' => array( 'GPL-2.0' ), + 'authors' => array( + array( + 'name' => 'Tim Starling', + 'email' => 'tstarling@wikimedia.org', + ), + array( + 'name' => 'Chad Horohoe', + 'email' => 'chad@wikimedia.org', + ), + ), + 'description' => 'Constant Database (CDB) wrapper library for PHP. Provides pure-PHP fallback when dba_* functions are absent.', ), 'cssjanus/cssjanus' => array( 'version' => '1.1.1', 'type' => 'library', + 'licenses' => array( 'Apache-2.0' ), + 'authors' => array(), + 'description' => 'Convert CSS stylesheets between left-to-right and right-to-left.', ), 'leafo/lessphp' => array( 'version' => '0.5.0', 'type' => 'library', + 'licenses' => array( 'MIT', 'GPL-3.0' ), + 'authors' => array( + array( + 'name' => 'Leaf Corcoran', + 'email' => 'leafot@gmail.com', + 'homepage' => 'http://leafo.net', + ), + ), + 'description' => 'lessphp is a compiler for LESS written in PHP.', ), 'psr/log' => array( 'version' => '1.0.0', 'type' => 'library', + 'licenses' => array( 'MIT' ), + 'authors' => array( + array( + 'name' => 'PHP-FIG', + 'homepage' => 'http://www.php-fig.org/', + ), + ), + 'description' => 'Common interface for logging libraries', ), 'oojs/oojs-ui' => array( 'version' => '0.6.0', 'type' => 'library', + 'licenses' => array( 'MIT' ), + 'authors' => array(), + 'description' => '', ), 'composer/installers' => array( 'version' => '1.0.19', 'type' => 'composer-installer', + 'licenses' => array( 'MIT' ), + 'authors' => array( + array( + 'name' => 'Kyle Robinson Young', + 'email' => 'kyle@dontkry.com', + 'homepage' => 'https://github.com/shama', + ), + ), + 'description' => 'A multi-framework Composer library installer', ), 'mediawiki/translate' => array( 'version' => '2014.12', 'type' => 'mediawiki-extension', + 'licenses' => array( 'GPL-2.0+' ), + 'authors' => array( + array( + 'name' => 'Niklas Laxström', + 'email' => 'niklas.laxstrom@gmail.com', + 'role' => 'Lead nitpicker', + ), + array( + 'name' => 'Siebrand Mazeland', + 'email' => 's.mazeland@xs4all.nl', + 'role' => 'Developer', + ), + ), + 'description' => 'The only standard solution to translate any kind of text with an avant-garde web interface within MediaWiki, including your documentation and software', ), 'mediawiki/universal-language-selector' => array( 'version' => '2014.12', 'type' => 'mediawiki-extension', + 'licenses' => array( 'GPL-2.0+', 'MIT' ), + 'authors' => array(), + 'description' => 'The primary aim is to allow users to select a language and configure its support in an easy way. Main features are language selection, input methods and web fonts.', ), ), $lock->getInstalledDependencies(), false, true ); } diff --git a/tests/phpunit/includes/logging/BlockLogFormatterTest.php b/tests/phpunit/includes/logging/BlockLogFormatterTest.php new file mode 100644 index 00000000..c7dc641d --- /dev/null +++ b/tests/phpunit/includes/logging/BlockLogFormatterTest.php @@ -0,0 +1,372 @@ +<?php + +class BlockLogFormatterTest extends LogFormatterTestCase { + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideBlockLogDatabaseRows() { + return array( + // Current log format + array( + array( + 'type' => 'block', + 'action' => 'block', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + '5::duration' => 'infinite', + '6::flags' => 'anononly', + ), + ), + array( + 'text' => 'Sysop blocked Logtestuser with an expiry time of indefinite (anonymous users only)', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array( 'anononly' ), + ), + ), + ), + + // Old legacy log + array( + array( + 'type' => 'block', + 'action' => 'block', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + 'infinite', + 'anononly', + ), + ), + array( + 'legacy' => true, + 'text' => 'Sysop blocked Logtestuser with an expiry time of indefinite (anonymous users only)', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array( 'anononly' ), + ), + ), + ), + + // Old legacy log without flag + array( + array( + 'type' => 'block', + 'action' => 'block', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + 'infinite', + ), + ), + array( + 'legacy' => true, + 'text' => 'Sysop blocked Logtestuser with an expiry time of indefinite', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array(), + ), + ), + ), + + // Very old legacy log without duration + array( + array( + 'type' => 'block', + 'action' => 'block', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array(), + ), + array( + 'legacy' => true, + 'text' => 'Sysop blocked Logtestuser with an expiry time of indefinite', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array(), + ), + ), + ), + ); + } + + /** + * @dataProvider provideBlockLogDatabaseRows + */ + public function testBlockLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideReblockLogDatabaseRows() { + return array( + // Current log format + array( + array( + 'type' => 'block', + 'action' => 'reblock', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + '5::duration' => 'infinite', + '6::flags' => 'anononly', + ), + ), + array( + 'text' => 'Sysop changed block settings for Logtestuser with an expiry time of' + . ' indefinite (anonymous users only)', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array( 'anononly' ), + ), + ), + ), + + // Old log + array( + array( + 'type' => 'block', + 'action' => 'reblock', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + 'infinite', + 'anononly', + ), + ), + array( + 'legacy' => true, + 'text' => 'Sysop changed block settings for Logtestuser with an expiry time of' + . ' indefinite (anonymous users only)', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array( 'anononly' ), + ), + ), + ), + + // Older log without flag + array( + array( + 'type' => 'block', + 'action' => 'reblock', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + 'infinite', + ) + ), + array( + 'legacy' => true, + 'text' => 'Sysop changed block settings for Logtestuser with an expiry time of indefinite', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array(), + ), + ), + ), + ); + } + + /** + * @dataProvider provideReblockLogDatabaseRows + */ + public function testReblockLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideUnblockLogDatabaseRows() { + return array( + // Current log format + array( + array( + 'type' => 'block', + 'action' => 'unblock', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array(), + ), + array( + 'text' => 'Sysop unblocked Logtestuser', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideUnblockLogDatabaseRows + */ + public function testUnblockLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideSuppressBlockLogDatabaseRows() { + return array( + // Current log format + array( + array( + 'type' => 'suppress', + 'action' => 'block', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + '5::duration' => 'infinite', + '6::flags' => 'anononly', + ), + ), + array( + 'text' => 'Sysop blocked Logtestuser with an expiry time of indefinite (anonymous users only)', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array( 'anononly' ), + ), + ), + ), + + // legacy log + array( + array( + 'type' => 'suppress', + 'action' => 'block', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + 'infinite', + 'anononly', + ), + ), + array( + 'legacy' => true, + 'text' => 'Sysop blocked Logtestuser with an expiry time of indefinite (anonymous users only)', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array( 'anononly' ), + ), + ), + ), + ); + } + + /** + * @dataProvider provideSuppressBlockLogDatabaseRows + */ + public function testSuppressBlockLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideSuppressReblockLogDatabaseRows() { + return array( + // Current log format + array( + array( + 'type' => 'suppress', + 'action' => 'reblock', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + '5::duration' => 'infinite', + '6::flags' => 'anononly', + ), + ), + array( + 'text' => 'Sysop changed block settings for Logtestuser with an expiry time of' + . ' indefinite (anonymous users only)', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array( 'anononly' ), + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'suppress', + 'action' => 'reblock', + 'comment' => 'Block comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Logtestuser', + 'params' => array( + 'infinite', + 'anononly', + ), + ), + array( + 'legacy' => true, + 'text' => 'Sysop changed block settings for Logtestuser with an expiry time of' + . ' indefinite (anonymous users only)', + 'api' => array( + 'duration' => 'infinite', + 'flags' => array( 'anononly' ), + ), + ), + ), + ); + } + + /** + * @dataProvider provideSuppressReblockLogDatabaseRows + */ + public function testSuppressReblockLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/DeleteLogFormatterTest.php b/tests/phpunit/includes/logging/DeleteLogFormatterTest.php new file mode 100644 index 00000000..28e7efaf --- /dev/null +++ b/tests/phpunit/includes/logging/DeleteLogFormatterTest.php @@ -0,0 +1,527 @@ +<?php + +class DeleteLogFormatterTest extends LogFormatterTestCase { + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideDeleteLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'delete', + 'action' => 'delete', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array(), + ), + array( + 'text' => 'User deleted page Page', + 'api' => array(), + ), + ), + + // Legacy format + array( + array( + 'type' => 'delete', + 'action' => 'delete', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array(), + ), + array( + 'legacy' => true, + 'text' => 'User deleted page Page', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideDeleteLogDatabaseRows + */ + public function testDeleteLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideRestoreLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'delete', + 'action' => 'restore', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array(), + ), + array( + 'text' => 'User restored page Page', + 'api' => array(), + ), + ), + + // Legacy format + array( + array( + 'type' => 'delete', + 'action' => 'restore', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array(), + ), + array( + 'legacy' => true, + 'text' => 'User restored page Page', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideRestoreLogDatabaseRows + */ + public function testRestoreLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideRevisionLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'delete', + 'action' => 'revision', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '4::type' => 'archive', + '5::ids' => array( '1', '3', '4' ), + '6::ofield' => '1', + '7::nfield' => '2', + ), + ), + array( + 'text' => 'User changed visibility of 3 revisions on page Page: edit summary ' + . 'hidden and content unhidden', + 'api' => array( + 'type' => 'archive', + 'ids' => array( '1', '3', '4' ), + 'old' => array( + 'bitmask' => 1, + 'content' => true, + 'comment' => false, + 'user' => false, + 'restricted' => false, + ), + 'new' => array( + 'bitmask' => 2, + 'content' => false, + 'comment' => true, + 'user' => false, + 'restricted' => false, + ), + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'delete', + 'action' => 'revision', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + 'archive', + '1,3,4', + 'ofield=1', + 'nfield=2', + ), + ), + array( + 'legacy' => true, + 'text' => 'User changed visibility of 3 revisions on page Page: edit summary ' + . 'hidden and content unhidden', + 'api' => array( + 'type' => 'archive', + 'ids' => array( '1', '3', '4' ), + 'old' => array( + 'bitmask' => 1, + 'content' => true, + 'comment' => false, + 'user' => false, + 'restricted' => false, + ), + 'new' => array( + 'bitmask' => 2, + 'content' => false, + 'comment' => true, + 'user' => false, + 'restricted' => false, + ), + ), + ), + ), + ); + } + + /** + * @dataProvider provideRevisionLogDatabaseRows + */ + public function testRevisionLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideEventLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'delete', + 'action' => 'event', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '4::ids' => array( '1', '3', '4' ), + '5::ofield' => '1', + '6::nfield' => '2', + ), + ), + array( + 'text' => 'User changed visibility of 3 log events on Page: edit summary hidden ' + . 'and content unhidden', + 'api' => array( + 'type' => 'logging', + 'ids' => array( '1', '3', '4' ), + 'old' => array( + 'bitmask' => 1, + 'content' => true, + 'comment' => false, + 'user' => false, + 'restricted' => false, + ), + 'new' => array( + 'bitmask' => 2, + 'content' => false, + 'comment' => true, + 'user' => false, + 'restricted' => false, + ), + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'delete', + 'action' => 'event', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '1,3,4', + 'ofield=1', + 'nfield=2', + ), + ), + array( + 'legacy' => true, + 'text' => 'User changed visibility of 3 log events on Page: edit summary hidden ' + . 'and content unhidden', + 'api' => array( + 'type' => 'logging', + 'ids' => array( '1', '3', '4' ), + 'old' => array( + 'bitmask' => 1, + 'content' => true, + 'comment' => false, + 'user' => false, + 'restricted' => false, + ), + 'new' => array( + 'bitmask' => 2, + 'content' => false, + 'comment' => true, + 'user' => false, + 'restricted' => false, + ), + ), + ), + ), + ); + } + + /** + * @dataProvider provideEventLogDatabaseRows + */ + public function testEventLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideSuppressRevisionLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'suppress', + 'action' => 'revision', + 'comment' => 'Suppress comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '4::type' => 'archive', + '5::ids' => array( '1', '3', '4' ), + '6::ofield' => '1', + '7::nfield' => '10', + ), + ), + array( + 'text' => 'User secretly changed visibility of 3 revisions on page Page: edit ' + . 'summary hidden, content unhidden and applied restrictions to administrators', + 'api' => array( + 'type' => 'archive', + 'ids' => array( '1', '3', '4' ), + 'old' => array( + 'bitmask' => 1, + 'content' => true, + 'comment' => false, + 'user' => false, + 'restricted' => false, + ), + 'new' => array( + 'bitmask' => 10, + 'content' => false, + 'comment' => true, + 'user' => false, + 'restricted' => true, + ), + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'suppress', + 'action' => 'revision', + 'comment' => 'Suppress comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + 'archive', + '1,3,4', + 'ofield=1', + 'nfield=10', + ), + ), + array( + 'legacy' => true, + 'text' => 'User secretly changed visibility of 3 revisions on page Page: edit ' + . 'summary hidden, content unhidden and applied restrictions to administrators', + 'api' => array( + 'type' => 'archive', + 'ids' => array( '1', '3', '4' ), + 'old' => array( + 'bitmask' => 1, + 'content' => true, + 'comment' => false, + 'user' => false, + 'restricted' => false, + ), + 'new' => array( + 'bitmask' => 10, + 'content' => false, + 'comment' => true, + 'user' => false, + 'restricted' => true, + ), + ), + ), + ), + ); + } + + /** + * @dataProvider provideSuppressRevisionLogDatabaseRows + */ + public function testSuppressRevisionLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideSuppressEventLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'suppress', + 'action' => 'event', + 'comment' => 'Suppress comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '4::ids' => array( '1', '3', '4' ), + '5::ofield' => '1', + '6::nfield' => '10', + ), + ), + array( + 'text' => 'User secretly changed visibility of 3 log events on Page: edit ' + . 'summary hidden, content unhidden and applied restrictions to administrators', + 'api' => array( + 'type' => 'logging', + 'ids' => array( '1', '3', '4' ), + 'old' => array( + 'bitmask' => 1, + 'content' => true, + 'comment' => false, + 'user' => false, + 'restricted' => false, + ), + 'new' => array( + 'bitmask' => 10, + 'content' => false, + 'comment' => true, + 'user' => false, + 'restricted' => true, + ), + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'suppress', + 'action' => 'event', + 'comment' => 'Suppress comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '1,3,4', + 'ofield=1', + 'nfield=10', + ), + ), + array( + 'legacy' => true, + 'text' => 'User secretly changed visibility of 3 log events on Page: edit ' + . 'summary hidden, content unhidden and applied restrictions to administrators', + 'api' => array( + 'type' => 'logging', + 'ids' => array( '1', '3', '4' ), + 'old' => array( + 'bitmask' => 1, + 'content' => true, + 'comment' => false, + 'user' => false, + 'restricted' => false, + ), + 'new' => array( + 'bitmask' => 10, + 'content' => false, + 'comment' => true, + 'user' => false, + 'restricted' => true, + ), + ), + ), + ), + ); + } + + /** + * @dataProvider provideSuppressEventLogDatabaseRows + */ + public function testSuppressEventLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideSuppressDeleteLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'suppress', + 'action' => 'delete', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array(), + ), + array( + 'text' => 'User suppressed page Page', + 'api' => array(), + ), + ), + + // Legacy format + array( + array( + 'type' => 'suppress', + 'action' => 'delete', + 'comment' => 'delete comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array(), + ), + array( + 'legacy' => true, + 'text' => 'User suppressed page Page', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideSuppressDeleteLogDatabaseRows + */ + public function testSuppressDeleteLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/LogFormatterTest.php b/tests/phpunit/includes/logging/LogFormatterTest.php index 515990e6..844c9afb 100644 --- a/tests/phpunit/includes/logging/LogFormatterTest.php +++ b/tests/phpunit/includes/logging/LogFormatterTest.php @@ -1,4 +1,5 @@ <?php + /** * @group Database */ @@ -19,6 +20,16 @@ class LogFormatterTest extends MediaWikiLangTestCase { */ protected $context; + /** + * @var Title + */ + protected $target; + + /** + * @var string + */ + protected $user_comment; + protected function setUp() { parent::setUp(); @@ -35,12 +46,15 @@ class LogFormatterTest extends MediaWikiLangTestCase { Language::getLocalisationCache()->recache( $wgLang->getCode() ); $this->user = User::newFromName( 'Testuser' ); - $this->title = Title::newMainPage(); + $this->title = Title::newFromText( 'SomeTitle' ); + $this->target = Title::newFromText( 'TestTarget' ); $this->context = new RequestContext(); $this->context->setUser( $this->user ); $this->context->setTitle( $this->title ); $this->context->setLanguage( $wgLang ); + + $this->user_comment = '<User comment about action>'; } protected function tearDown() { @@ -292,4 +306,309 @@ class LogFormatterTest extends MediaWikiLangTestCase { array( '4:user-link:key', 'foo', array( 'key' => 'Foo' ) ), ); } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeBlock() { + $sep = $this->context->msg( 'colon-separator' )->text(); + + # block/block + $this->assertIRCComment( + $this->context->msg( 'blocklogentry', 'SomeTitle', 'duration', '(flags)' )->plain() + . $sep . $this->user_comment, + 'block', 'block', + array( + '5::duration' => 'duration', + '6::flags' => 'flags', + ), + $this->user_comment + ); + # block/block - legacy + $this->assertIRCComment( + $this->context->msg( 'blocklogentry', 'SomeTitle', 'duration', '(flags)' )->plain() + . $sep . $this->user_comment, + 'block', 'block', + array( + 'duration', + 'flags', + ), + $this->user_comment, + '', + true + ); + # block/unblock + $this->assertIRCComment( + $this->context->msg( 'unblocklogentry', 'SomeTitle' )->plain() . $sep . $this->user_comment, + 'block', 'unblock', + array(), + $this->user_comment + ); + # block/reblock + $this->assertIRCComment( + $this->context->msg( 'reblock-logentry', 'SomeTitle', 'duration', '(flags)' )->plain() + . $sep . $this->user_comment, + 'block', 'reblock', + array( + '5::duration' => 'duration', + '6::flags' => 'flags', + ), + $this->user_comment + ); + } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeDelete() { + $sep = $this->context->msg( 'colon-separator' )->text(); + + # delete/delete + $this->assertIRCComment( + $this->context->msg( 'deletedarticle', 'SomeTitle' )->plain() . $sep . $this->user_comment, + 'delete', 'delete', + array(), + $this->user_comment + ); + + # delete/restore + $this->assertIRCComment( + $this->context->msg( 'undeletedarticle', 'SomeTitle' )->plain() . $sep . $this->user_comment, + 'delete', 'restore', + array(), + $this->user_comment + ); + } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeNewusers() { + $this->assertIRCComment( + 'New user account', + 'newusers', 'newusers', + array() + ); + $this->assertIRCComment( + 'New user account', + 'newusers', 'create', + array() + ); + $this->assertIRCComment( + 'created new account SomeTitle', + 'newusers', 'create2', + array() + ); + $this->assertIRCComment( + 'Account created automatically', + 'newusers', 'autocreate', + array() + ); + } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeMove() { + $move_params = array( + '4::target' => $this->target->getPrefixedText(), + '5::noredir' => 0, + ); + $sep = $this->context->msg( 'colon-separator' )->text(); + + # move/move + $this->assertIRCComment( + $this->context->msg( '1movedto2', 'SomeTitle', 'TestTarget' ) + ->plain() . $sep . $this->user_comment, + 'move', 'move', + $move_params, + $this->user_comment + ); + + # move/move_redir + $this->assertIRCComment( + $this->context->msg( '1movedto2_redir', 'SomeTitle', 'TestTarget' ) + ->plain() . $sep . $this->user_comment, + 'move', 'move_redir', + $move_params, + $this->user_comment + ); + } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypePatrol() { + # patrol/patrol + $this->assertIRCComment( + $this->context->msg( 'patrol-log-line', 'revision 777', '[[SomeTitle]]', '' )->plain(), + 'patrol', 'patrol', + array( + '4::curid' => '777', + '5::previd' => '666', + '6::auto' => 0, + ) + ); + } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeProtect() { + $protectParams = array( + '[edit=sysop] (indefinite) [move=sysop] (indefinite)' + ); + $sep = $this->context->msg( 'colon-separator' )->text(); + + # protect/protect + $this->assertIRCComment( + $this->context->msg( 'protectedarticle', 'SomeTitle ' . $protectParams[0] ) + ->plain() . $sep . $this->user_comment, + 'protect', 'protect', + $protectParams, + $this->user_comment + ); + + # protect/unprotect + $this->assertIRCComment( + $this->context->msg( 'unprotectedarticle', 'SomeTitle' )->plain() . $sep . $this->user_comment, + 'protect', 'unprotect', + array(), + $this->user_comment + ); + + # protect/modify + $this->assertIRCComment( + $this->context->msg( 'modifiedarticleprotection', 'SomeTitle ' . $protectParams[0] ) + ->plain() . $sep . $this->user_comment, + 'protect', 'modify', + $protectParams, + $this->user_comment + ); + + # protect/move_prot + $this->assertIRCComment( + $this->context->msg( 'movedarticleprotection', 'SomeTitle', 'OldTitle' ) + ->plain() . $sep . $this->user_comment, + 'protect', 'move_prot', + array( + '4::oldtitle' => 'OldTitle' + ), + $this->user_comment + ); + } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeUpload() { + $sep = $this->context->msg( 'colon-separator' )->text(); + + # upload/upload + $this->assertIRCComment( + $this->context->msg( 'uploadedimage', 'SomeTitle' )->plain() . $sep . $this->user_comment, + 'upload', 'upload', + array(), + $this->user_comment + ); + + # upload/overwrite + $this->assertIRCComment( + $this->context->msg( 'overwroteimage', 'SomeTitle' )->plain() . $sep . $this->user_comment, + 'upload', 'overwrite', + array(), + $this->user_comment + ); + } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeMerge() { + $sep = $this->context->msg( 'colon-separator' )->text(); + + # merge/merge + $this->assertIRCComment( + $this->context->msg( 'pagemerge-logentry', 'SomeTitle', 'Dest', 'timestamp' )->plain() + . $sep . $this->user_comment, + 'merge', 'merge', + array( + '4::dest' => 'Dest', + '5::mergepoint' => 'timestamp', + ), + $this->user_comment + ); + } + + /** + * @covers LogFormatter::getIRCActionComment + * @covers LogFormatter::getIRCActionText + */ + public function testIrcMsgForLogTypeImport() { + $sep = $this->context->msg( 'colon-separator' )->text(); + + # import/upload + $msg = $this->context->msg( 'import-logentry-upload', 'SomeTitle' )->plain() . + $sep . + $this->user_comment; + $this->assertIRCComment( + $msg, + 'import', 'upload', + array(), + $this->user_comment + ); + + # import/interwiki + $msg = $this->context->msg( 'import-logentry-interwiki', 'SomeTitle' )->plain() . + $sep . + $this->user_comment; + $this->assertIRCComment( + $msg, + 'import', 'interwiki', + array(), + $this->user_comment + ); + } + + /** + * @param string $expected Expected IRC text without colors codes + * @param string $type Log type (move, delete, suppress, patrol ...) + * @param string $action A log type action + * @param array $params + * @param string $comment (optional) A comment for the log action + * @param string $msg (optional) A message for PHPUnit :-) + */ + protected function assertIRCComment( $expected, $type, $action, $params, + $comment = null, $msg = '', $legacy = false + ) { + $logEntry = new ManualLogEntry( $type, $action ); + $logEntry->setPerformer( $this->user ); + $logEntry->setTarget( $this->title ); + if ( $comment !== null ) { + $logEntry->setComment( $comment ); + } + $logEntry->setParameters( $params ); + $logEntry->setLegacy( $legacy ); + + $formatter = LogFormatter::newFromEntry( $logEntry ); + $formatter->setContext( $this->context ); + + // Apply the same transformation as done in IRCColourfulRCFeedFormatter::getLine for rc_comment + $ircRcComment = IRCColourfulRCFeedFormatter::cleanupForIRC( $formatter->getIRCActionComment() ); + + $this->assertEquals( + $expected, + $ircRcComment, + $msg + ); + } + } diff --git a/tests/phpunit/includes/logging/LogFormatterTestCase.php b/tests/phpunit/includes/logging/LogFormatterTestCase.php new file mode 100644 index 00000000..e88452b7 --- /dev/null +++ b/tests/phpunit/includes/logging/LogFormatterTestCase.php @@ -0,0 +1,65 @@ +<?php + +/** + * @since 1.26 + */ +abstract class LogFormatterTestCase extends MediaWikiLangTestCase { + + public function doTestLogFormatter( $row, $extra ) { + RequestContext::resetMain(); + $row = $this->expandDatabaseRow( $row, $this->isLegacy( $extra ) ); + + $formatter = LogFormatter::newFromRow( $row ); + + $this->assertEquals( + $extra['text'], + self::removeSomeHtml( $formatter->getActionText() ), + 'Action text is equal to expected text' + ); + + $this->assertSame( // ensure types and array key order + $extra['api'], + self::removeApiMetaData( $formatter->formatParametersForApi() ), + 'Api log params is equal to expected array' + ); + } + + protected function isLegacy( $extra ) { + return isset( $extra['legacy'] ) && $extra['legacy']; + } + + protected function expandDatabaseRow( $data, $legacy ) { + return array( + // no log_id because no insert in database + 'log_type' => $data['type'], + 'log_action' => $data['action'], + 'log_timestamp' => isset( $data['timestamp'] ) ? $data['timestamp'] : wfTimestampNow(), + 'log_user' => isset( $data['user'] ) ? $data['user'] : 0, + 'log_user_text' => isset( $data['user_text'] ) ? $data['user_text'] : 'User', + 'log_namespace' => isset( $data['namespace'] ) ? $data['namespace'] : NS_MAIN, + 'log_title' => isset( $data['title'] ) ? $data['title'] : 'Main_Page', + 'log_page' => isset( $data['page'] ) ? $data['page'] : 0, + 'log_comment' => isset( $data['comment'] ) ? $data['comment'] : '', + 'log_params' => $legacy + ? LogPage::makeParamBlob( $data['params'] ) + : LogEntryBase::makeParamBlob( $data['params'] ), + 'log_deleted' => isset( $data['deleted'] ) ? $data['deleted'] : 0, + ); + } + + private static function removeSomeHtml( $html ) { + $html = str_replace( '"', '"', $html ); + return trim( preg_replace( '/<(a|span)[^>]*>([^<]*)<\/\1>/', '$2', $html ) ); + } + + private static function removeApiMetaData( $val ) { + if ( is_array( $val ) ) { + unset( $val['_element'] ); + unset( $val['_type'] ); + foreach ( $val as $key => $value ) { + $val[$key] = self::removeApiMetaData( $value ); + } + } + return $val; + } +} diff --git a/tests/phpunit/includes/logging/MergeLogFormatterTest.php b/tests/phpunit/includes/logging/MergeLogFormatterTest.php new file mode 100644 index 00000000..2ff0ddf5 --- /dev/null +++ b/tests/phpunit/includes/logging/MergeLogFormatterTest.php @@ -0,0 +1,67 @@ +<?php + +class MergeLogFormatterTest extends LogFormatterTestCase { + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideMergeLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'merge', + 'action' => 'merge', + 'comment' => 'Merge comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + '4::dest' => 'NewPage', + '5::mergepoint' => '20140804160710', + ), + ), + array( + 'text' => 'User merged OldPage into NewPage (revisions up to 16:07, 4 August 2014)', + 'api' => array( + 'dest_ns' => 0, + 'dest_title' => 'NewPage', + 'mergepoint' => '2014-08-04T16:07:10Z', + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'merge', + 'action' => 'merge', + 'comment' => 'merge comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + 'NewPage', + '20140804160710', + ), + ), + array( + 'legacy' => true, + 'text' => 'User merged OldPage into NewPage (revisions up to 16:07, 4 August 2014)', + 'api' => array( + 'dest_ns' => 0, + 'dest_title' => 'NewPage', + 'mergepoint' => '2014-08-04T16:07:10Z', + ), + ), + ), + ); + } + + /** + * @dataProvider provideMergeLogDatabaseRows + */ + public function testMergeLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/MoveLogFormatterTest.php b/tests/phpunit/includes/logging/MoveLogFormatterTest.php new file mode 100644 index 00000000..fdc4b7e1 --- /dev/null +++ b/tests/phpunit/includes/logging/MoveLogFormatterTest.php @@ -0,0 +1,270 @@ +<?php + +class MoveLogFormatterTest extends LogFormatterTestCase { + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideMoveLogDatabaseRows() { + return array( + // Current format - with redirect + array( + array( + 'type' => 'move', + 'action' => 'move', + 'comment' => 'move comment with redirect', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + '4::target' => 'NewPage', + '5::noredir' => '0', + ), + ), + array( + 'text' => 'User moved page OldPage to NewPage', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => false, + ), + ), + ), + + // Current format - without redirect + array( + array( + 'type' => 'move', + 'action' => 'move', + 'comment' => 'move comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + '4::target' => 'NewPage', + '5::noredir' => '1', + ), + ), + array( + 'text' => 'User moved page OldPage to NewPage without leaving a redirect', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => true, + ), + ), + ), + + // legacy format - with redirect + array( + array( + 'type' => 'move', + 'action' => 'move', + 'comment' => 'move comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + 'NewPage', + '', + ), + ), + array( + 'legacy' => true, + 'text' => 'User moved page OldPage to NewPage', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => false, + ), + ), + ), + + // legacy format - without redirect + array( + array( + 'type' => 'move', + 'action' => 'move', + 'comment' => 'move comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + 'NewPage', + '1', + ), + ), + array( + 'legacy' => true, + 'text' => 'User moved page OldPage to NewPage without leaving a redirect', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => true, + ), + ), + ), + + // old format without flag for redirect suppression + array( + array( + 'type' => 'move', + 'action' => 'move', + 'comment' => 'move comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + 'NewPage', + ), + ), + array( + 'legacy' => true, + 'text' => 'User moved page OldPage to NewPage', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => false, + ), + ), + ), + ); + } + + /** + * @dataProvider provideMoveLogDatabaseRows + */ + public function testMoveLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideMoveRedirLogDatabaseRows() { + return array( + // Current format - with redirect + array( + array( + 'type' => 'move', + 'action' => 'move_redir', + 'comment' => 'move comment with redirect', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + '4::target' => 'NewPage', + '5::noredir' => '0', + ), + ), + array( + 'text' => 'User moved page OldPage to NewPage over redirect', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => false, + ), + ), + ), + + // Current format - without redirect + array( + array( + 'type' => 'move', + 'action' => 'move_redir', + 'comment' => 'move comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + '4::target' => 'NewPage', + '5::noredir' => '1', + ), + ), + array( + 'text' => 'User moved page OldPage to NewPage over a redirect without leaving a redirect', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => true, + ), + ), + ), + + // legacy format - with redirect + array( + array( + 'type' => 'move', + 'action' => 'move_redir', + 'comment' => 'move comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + 'NewPage', + '', + ), + ), + array( + 'legacy' => true, + 'text' => 'User moved page OldPage to NewPage over redirect', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => false, + ), + ), + ), + + // legacy format - without redirect + array( + array( + 'type' => 'move', + 'action' => 'move_redir', + 'comment' => 'move comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + 'NewPage', + '1', + ), + ), + array( + 'legacy' => true, + 'text' => 'User moved page OldPage to NewPage over a redirect without leaving a redirect', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => true, + ), + ), + ), + + // old format without flag for redirect suppression + array( + array( + 'type' => 'move', + 'action' => 'move_redir', + 'comment' => 'move comment', + 'namespace' => NS_MAIN, + 'title' => 'OldPage', + 'params' => array( + 'NewPage', + ), + ), + array( + 'legacy' => true, + 'text' => 'User moved page OldPage to NewPage over redirect', + 'api' => array( + 'target_ns' => 0, + 'target_title' => 'NewPage', + 'suppressredirect' => false, + ), + ), + ), + ); + } + + /** + * @dataProvider provideMoveRedirLogDatabaseRows + */ + public function testMoveRedirLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/NewUsersLogFormatterTest.php b/tests/phpunit/includes/logging/NewUsersLogFormatterTest.php new file mode 100644 index 00000000..5b03370d --- /dev/null +++ b/tests/phpunit/includes/logging/NewUsersLogFormatterTest.php @@ -0,0 +1,207 @@ +<?php + +/** + * @group Database + */ +class NewUsersLogFormatterTest extends LogFormatterTestCase { + + protected function setUp() { + parent::setUp(); + + // Register LogHandler, see $wgNewUserLog in Setup.php + $this->mergeMwGlobalArrayValue( 'wgLogActionsHandlers', array( + 'newusers/newusers' => 'NewUsersLogFormatter', + 'newusers/create' => 'NewUsersLogFormatter', + 'newusers/create2' => 'NewUsersLogFormatter', + 'newusers/byemail' => 'NewUsersLogFormatter', + 'newusers/autocreate' => 'NewUsersLogFormatter', + ) ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideNewUsersLogDatabaseRows() { + return array( + // Only old logs + array( + array( + 'type' => 'newusers', + 'action' => 'newusers', + 'comment' => 'newusers comment', + 'user' => 0, + 'user_text' => 'New user', + 'namespace' => NS_USER, + 'title' => 'New user', + 'params' => array(), + ), + array( + 'legacy' => true, + 'text' => 'User account New user was created', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideNewUsersLogDatabaseRows + */ + public function testNewUsersLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideCreateLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'newusers', + 'action' => 'create', + 'comment' => 'newusers comment', + 'user' => 0, + 'user_text' => 'New user', + 'namespace' => NS_USER, + 'title' => 'New user', + 'params' => array( + '4::userid' => 1, + ), + ), + array( + 'text' => 'User account New user was created', + 'api' => array( + 'userid' => 1, + ), + ), + ), + ); + } + + /** + * @dataProvider provideCreateLogDatabaseRows + */ + public function testCreateLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideCreate2LogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'newusers', + 'action' => 'create2', + 'comment' => 'newusers comment', + 'user' => 0, + 'user_text' => 'User', + 'namespace' => NS_USER, + 'title' => 'UTSysop', + 'params' => array( + '4::userid' => 1, + ), + ), + array( + 'text' => 'User account UTSysop was created by User', + 'api' => array( + 'userid' => 1, + ), + ), + ), + ); + } + + /** + * @dataProvider provideCreate2LogDatabaseRows + */ + public function testCreate2LogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideByemailLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'newusers', + 'action' => 'byemail', + 'comment' => 'newusers comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'UTSysop', + 'params' => array( + '4::userid' => 1, + ), + ), + array( + 'text' => 'User account UTSysop was created by Sysop and password was sent by email', + 'api' => array( + 'userid' => 1, + ), + ), + ), + ); + } + + /** + * @dataProvider provideByemailLogDatabaseRows + */ + public function testByemailLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideAutocreateLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'newusers', + 'action' => 'autocreate', + 'comment' => 'newusers comment', + 'user' => 0, + 'user_text' => 'New user', + 'namespace' => NS_USER, + 'title' => 'New user', + 'params' => array( + '4::userid' => 1, + ), + ), + array( + 'text' => 'User account New user was created automatically', + 'api' => array( + 'userid' => 1, + ), + ), + ), + ); + } + + /** + * @dataProvider provideAutocreateLogDatabaseRows + */ + public function testAutocreateLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/PageLangLogFormatterTest.php b/tests/phpunit/includes/logging/PageLangLogFormatterTest.php new file mode 100644 index 00000000..226e492b --- /dev/null +++ b/tests/phpunit/includes/logging/PageLangLogFormatterTest.php @@ -0,0 +1,53 @@ +<?php + +class PageLangLogFormatterTest extends LogFormatterTestCase { + + protected function setUp() { + parent::setUp(); + + // Disable cldr extension + $this->setMwGlobals( 'wgHooks', array() ); + // Register LogHandler, see $wgPageLanguageUseDB in Setup.php + $this->mergeMwGlobalArrayValue( 'wgLogActionsHandlers', array( + 'pagelang/pagelang' => 'PageLangLogFormatter', + ) ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function providePageLangLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'pagelang', + 'action' => 'pagelang', + 'comment' => 'page lang comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '4::oldlanguage' => 'en', + '5::newlanguage' => 'de[def]', + ), + ), + array( + 'text' => 'User changed page language for Page from English (en) to Deutsch (de) [default].', + 'api' => array( + 'oldlanguage' => 'en', + 'newlanguage' => 'de[def]' + ), + ), + ), + ); + } + + /** + * @dataProvider providePageLangLogDatabaseRows + */ + public function testPageLangLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/PatrolLogFormatterTest.php b/tests/phpunit/includes/logging/PatrolLogFormatterTest.php new file mode 100644 index 00000000..6e1c5efc --- /dev/null +++ b/tests/phpunit/includes/logging/PatrolLogFormatterTest.php @@ -0,0 +1,118 @@ +<?php + +class PatrolLogFormatterTest extends LogFormatterTestCase { + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function providePatrolLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'patrol', + 'action' => 'patrol', + 'comment' => 'patrol comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '4::curid' => 2, + '5::previd' => 1, + '6::auto' => 0, + ), + ), + array( + 'text' => 'User marked revision 2 of page Page patrolled', + 'api' => array( + 'curid' => 2, + 'previd' => 1, + 'auto' => false, + ), + ), + ), + + // Current format - autopatrol + array( + array( + 'type' => 'patrol', + 'action' => 'patrol', + 'comment' => 'patrol comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '4::curid' => 2, + '5::previd' => 1, + '6::auto' => 1, + ), + ), + array( + 'text' => 'User automatically marked revision 2 of page Page patrolled', + 'api' => array( + 'curid' => 2, + 'previd' => 1, + 'auto' => true, + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'patrol', + 'action' => 'patrol', + 'comment' => 'patrol comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '2', + '1', + '0', + ), + ), + array( + 'legacy' => true, + 'text' => 'User marked revision 2 of page Page patrolled', + 'api' => array( + 'curid' => 2, + 'previd' => 1, + 'auto' => false, + ), + ), + ), + + // Legacy format - autopatrol + array( + array( + 'type' => 'patrol', + 'action' => 'patrol', + 'comment' => 'patrol comment', + 'namespace' => NS_MAIN, + 'title' => 'Page', + 'params' => array( + '2', + '1', + '1', + ), + ), + array( + 'legacy' => true, + 'text' => 'User automatically marked revision 2 of page Page patrolled', + 'api' => array( + 'curid' => 2, + 'previd' => 1, + 'auto' => true, + ), + ), + ), + ); + } + + /** + * @dataProvider providePatrolLogDatabaseRows + */ + public function testPatrolLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/ProtectLogFormatterTest.php b/tests/phpunit/includes/logging/ProtectLogFormatterTest.php new file mode 100644 index 00000000..611b2dfc --- /dev/null +++ b/tests/phpunit/includes/logging/ProtectLogFormatterTest.php @@ -0,0 +1,63 @@ +<?php + +class ProtectLogFormatterTest extends LogFormatterTestCase { + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideMoveProtLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'protect', + 'action' => 'move_prot', + 'comment' => 'Move comment', + 'namespace' => NS_MAIN, + 'title' => 'NewPage', + 'params' => array( + '4::oldtitle' => 'OldPage', + ), + ), + array( + 'text' => 'User moved protection settings from OldPage to NewPage', + 'api' => array( + 'oldtitle_ns' => 0, + 'oldtitle_title' => 'OldPage', + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'protect', + 'action' => 'move_prot', + 'comment' => 'Move comment', + 'namespace' => NS_MAIN, + 'title' => 'NewPage', + 'params' => array( + 'OldPage', + ), + ), + array( + 'legacy' => true, + 'text' => 'User moved protection settings from OldPage to NewPage', + 'api' => array( + 'oldtitle_ns' => 0, + 'oldtitle_title' => 'OldPage', + ), + ), + ), + ); + } + + /** + * @dataProvider provideMoveProtLogDatabaseRows + */ + public function testMoveProtLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/RightsLogFormatterTest.php b/tests/phpunit/includes/logging/RightsLogFormatterTest.php new file mode 100644 index 00000000..e9577f11 --- /dev/null +++ b/tests/phpunit/includes/logging/RightsLogFormatterTest.php @@ -0,0 +1,157 @@ +<?php + +class RightsLogFormatterTest extends LogFormatterTestCase { + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideRightsLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'rights', + 'action' => 'rights', + 'comment' => 'rights comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'User', + 'params' => array( + '4::oldgroups' => array(), + '5::newgroups' => array( 'sysop', 'bureaucrat' ), + ), + ), + array( + 'text' => 'Sysop changed group membership for User:User from (none) to ' + . 'administrator and bureaucrat', + 'api' => array( + 'oldgroups' => array(), + 'newgroups' => array( 'sysop', 'bureaucrat' ), + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'rights', + 'action' => 'rights', + 'comment' => 'rights comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'User', + 'params' => array( + '', + 'sysop, bureaucrat', + ), + ), + array( + 'legacy' => true, + 'text' => 'Sysop changed group membership for User:User from (none) to ' + . 'administrator and bureaucrat', + 'api' => array( + 'oldgroups' => array(), + 'newgroups' => array( 'sysop', 'bureaucrat' ), + ), + ), + ), + + // Really old entry + array( + array( + 'type' => 'rights', + 'action' => 'rights', + 'comment' => 'rights comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'User', + 'params' => array(), + ), + array( + 'legacy' => true, + 'text' => 'Sysop changed group membership for User:User', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideRightsLogDatabaseRows + */ + public function testRightsLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideAutopromoteLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'rights', + 'action' => 'autopromote', + 'comment' => 'rights comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Sysop', + 'params' => array( + '4::oldgroups' => array( 'sysop' ), + '5::newgroups' => array( 'sysop', 'bureaucrat' ), + ), + ), + array( + 'text' => 'Sysop was automatically promoted from administrator to ' + . 'administrator and bureaucrat', + 'api' => array( + 'oldgroups' => array( 'sysop' ), + 'newgroups' => array( 'sysop', 'bureaucrat' ), + ), + ), + ), + + // Legacy format + array( + array( + 'type' => 'rights', + 'action' => 'autopromote', + 'comment' => 'rights comment', + 'user' => 0, + 'user_text' => 'Sysop', + 'namespace' => NS_USER, + 'title' => 'Sysop', + 'params' => array( + 'sysop', + 'sysop, bureaucrat', + ), + ), + array( + 'legacy' => true, + 'text' => 'Sysop was automatically promoted from administrator to ' + . 'administrator and bureaucrat', + 'api' => array( + 'oldgroups' => array( 'sysop' ), + 'newgroups' => array( 'sysop', 'bureaucrat' ), + ), + ), + ), + ); + } + + /** + * @dataProvider provideAutopromoteLogDatabaseRows + */ + public function testAutopromoteLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/logging/UploadLogFormatterTest.php b/tests/phpunit/includes/logging/UploadLogFormatterTest.php new file mode 100644 index 00000000..12f51613 --- /dev/null +++ b/tests/phpunit/includes/logging/UploadLogFormatterTest.php @@ -0,0 +1,166 @@ +<?php + +class UploadLogFormatterTest extends LogFormatterTestCase { + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideUploadLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'upload', + 'action' => 'upload', + 'comment' => 'upload comment', + 'namespace' => NS_FILE, + 'title' => 'File.png', + 'params' => array( + 'img_sha1' => 'hash', + 'img_timestamp' => '20150101000000', + ), + ), + array( + 'text' => 'User uploaded File:File.png', + 'api' => array( + 'img_sha1' => 'hash', + 'img_timestamp' => '2015-01-01T00:00:00Z', + ), + ), + ), + + // Old format without params + array( + array( + 'type' => 'upload', + 'action' => 'upload', + 'comment' => 'upload comment', + 'namespace' => NS_FILE, + 'title' => 'File.png', + 'params' => array(), + ), + array( + 'text' => 'User uploaded File:File.png', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideUploadLogDatabaseRows + */ + public function testUploadLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideOverwriteLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'upload', + 'action' => 'overwrite', + 'comment' => 'upload comment', + 'namespace' => NS_FILE, + 'title' => 'File.png', + 'params' => array( + 'img_sha1' => 'hash', + 'img_timestamp' => '20150101000000', + ), + ), + array( + 'text' => 'User uploaded a new version of File:File.png', + 'api' => array( + 'img_sha1' => 'hash', + 'img_timestamp' => '2015-01-01T00:00:00Z', + ), + ), + ), + + // Old format without params + array( + array( + 'type' => 'upload', + 'action' => 'overwrite', + 'comment' => 'upload comment', + 'namespace' => NS_FILE, + 'title' => 'File.png', + 'params' => array(), + ), + array( + 'text' => 'User uploaded a new version of File:File.png', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideOverwriteLogDatabaseRows + */ + public function testOverwriteLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } + + /** + * Provide different rows from the logging table to test + * for backward compatibility. + * Do not change the existing data, just add a new database row + */ + public static function provideRevertLogDatabaseRows() { + return array( + // Current format + array( + array( + 'type' => 'upload', + 'action' => 'revert', + 'comment' => 'upload comment', + 'namespace' => NS_FILE, + 'title' => 'File.png', + 'params' => array( + 'img_sha1' => 'hash', + 'img_timestamp' => '20150101000000', + ), + ), + array( + 'text' => 'User uploaded File:File.png', + 'api' => array( + 'img_sha1' => 'hash', + 'img_timestamp' => '2015-01-01T00:00:00Z', + ), + ), + ), + + // Old format without params + array( + array( + 'type' => 'upload', + 'action' => 'revert', + 'comment' => 'upload comment', + 'namespace' => NS_FILE, + 'title' => 'File.png', + 'params' => array(), + ), + array( + 'text' => 'User uploaded File:File.png', + 'api' => array(), + ), + ), + ); + } + + /** + * @dataProvider provideRevertLogDatabaseRows + */ + public function testRevertLogDatabaseRows( $row, $extra ) { + $this->doTestLogFormatter( $row, $extra ); + } +} diff --git a/tests/phpunit/includes/media/ExifBitmapTest.php b/tests/phpunit/includes/media/ExifBitmapTest.php index 41330f41..adbc9775 100644 --- a/tests/phpunit/includes/media/ExifBitmapTest.php +++ b/tests/phpunit/includes/media/ExifBitmapTest.php @@ -3,7 +3,7 @@ /** * @group Media */ -class ExifBitmapTest extends MediaWikiTestCase { +class ExifBitmapTest extends MediaWikiMediaTestCase { /** * @var ExifBitmapHandler @@ -143,4 +143,41 @@ class ExifBitmapTest extends MediaWikiTestCase { $res = $this->handler->convertMetadataVersion( $metadata, 1 ); $this->assertEquals( $expected, $res ); } + + /** + * @dataProvider provideSwappingICCProfile + * @covers BitmapHandler::swapICCProfile + */ + public function testSwappingICCProfile( $sourceFilename, $controlFilename, $newProfileFilename, $oldProfileName ) { + global $wgExiftool; + + if ( !$wgExiftool || !is_file( $wgExiftool ) ) { + $this->markTestSkipped( "Exiftool not installed, cannot test ICC profile swapping" ); + } + + $this->setMwGlobals( 'wgUseTinyRGBForJPGThumbnails', true ); + + $sourceFilepath = $this->filePath . $sourceFilename; + $controlFilepath = $this->filePath . $controlFilename; + $profileFilepath = $this->filePath . $newProfileFilename; + $filepath = $this->getNewTempFile(); + + copy( $sourceFilepath, $filepath ); + + $file = $this->dataFile( $sourceFilename, 'image/jpeg' ); + $this->handler->swapICCProfile( $filepath, $oldProfileName, $profileFilepath ); + + $this->assertEquals( sha1( file_get_contents( $filepath ) ), sha1( file_get_contents( $controlFilepath ) ) ); + } + + public function provideSwappingICCProfile() { + return array( + // File with sRGB should end up with TinyRGB + array( 'srgb.jpg', 'tinyrgb.jpg', 'tinyrgb.icc', 'IEC 61966-2.1 Default RGB colour space - sRGB' ), + // File with TinyRGB should be left unchanged + array( 'tinyrgb.jpg', 'tinyrgb.jpg', 'tinyrgb.icc', 'IEC 61966-2.1 Default RGB colour space - sRGB' ), + // File with no profile should be left unchanged + array( 'test.jpg', 'test.jpg', 'tinyrgb.icc', 'IEC 61966-2.1 Default RGB colour space - sRGB' ) + ); + } } diff --git a/tests/phpunit/includes/media/FormatMetadataTest.php b/tests/phpunit/includes/media/FormatMetadataTest.php index 54758f94..b666c83c 100644 --- a/tests/phpunit/includes/media/FormatMetadataTest.php +++ b/tests/phpunit/includes/media/FormatMetadataTest.php @@ -37,39 +37,6 @@ class FormatMetadataTest extends MediaWikiMediaTestCase { } /** - * @param string $filename - * @param int $expected Total image area - * @dataProvider provideFlattenArray - * @covers FormatMetadata::flattenArray - */ - public function testFlattenArray( $vals, $type, $noHtml, $ctx, $expected ) { - $actual = FormatMetadata::flattenArray( $vals, $type, $noHtml, $ctx ); - $this->assertEquals( $expected, $actual ); - } - - public static function provideFlattenArray() { - return array( - array( - array( 1, 2, 3 ), 'ul', false, false, - "<ul><li>1</li>\n<li>2</li>\n<li>3</li></ul>", - ), - array( - array( 1, 2, 3 ), 'ol', false, false, - "<ol><li>1</li>\n<li>2</li>\n<li>3</li></ol>", - ), - array( - array( 1, 2, 3 ), 'ul', true, false, - "\n*1\n*2\n*3", - ), - array( - array( 1, 2, 3 ), 'ol', true, false, - "\n#1\n#2\n#3", - ), - // TODO: more test cases - ); - } - - /** * @param mixed $input * @param mixed $output * @dataProvider provideResolveMultivalueValue diff --git a/tests/phpunit/includes/media/WebPTest.php b/tests/phpunit/includes/media/WebPTest.php new file mode 100644 index 00000000..d36710a3 --- /dev/null +++ b/tests/phpunit/includes/media/WebPTest.php @@ -0,0 +1,127 @@ +<?php +class WebPHandlerTest extends MediaWikiTestCase { + public function setUp() { + parent::setUp(); + // Allocated file for testing + $this->tempFileName = tempnam( wfTempDir(), 'WEBP' ); + } + public function tearDown() { + parent::tearDown(); + unlink( $this->tempFileName ); + } + /** + * @dataProvider provideTestExtractMetaData + */ + public function testExtractMetaData( $header, $expectedResult ) { + // Put header into file + file_put_contents( $this->tempFileName, $header ); + + $this->assertEquals( $expectedResult, WebPHandler::extractMetadata( $this->tempFileName ) ); + } + public function provideTestExtractMetaData() { + return array( + // Files from https://developers.google.com/speed/webp/gallery2 + array( "\x52\x49\x46\x46\x90\x68\x01\x00\x57\x45\x42\x50\x56\x50\x38\x4C\x83\x68\x01\x00\x2F\x8F\x01\x4B\x10\x8D\x38\x6C\xDB\x46\x92\xE0\xE0\x82\x7B\x6C", + array( 'compression' => 'lossless', 'width' => 400, 'height' => 301 ) ), + array( "\x52\x49\x46\x46\x64\x5B\x00\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\x8F\x01\x00\x2C\x01\x00\x41\x4C\x50\x48\xE5\x0E", + array( 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 400, 'height' => 301) ), + array( "\x52\x49\x46\x46\xA8\x72\x00\x00\x57\x45\x42\x50\x56\x50\x38\x4C\x9B\x72\x00\x00\x2F\x81\x81\x62\x10\x8D\x40\x8C\x24\x39\x6E\x73\x73\x38\x01\x96", + array( 'compression' => 'lossless', 'width' => 386, 'height' => 395 ) ), + array( "\x52\x49\x46\x46\xE0\x42\x00\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\x81\x01\x00\x8A\x01\x00\x41\x4C\x50\x48\x56\x10", + array( 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 386, 'height' => 395 ) ), + array( "\x52\x49\x46\x46\x70\x61\x02\x00\x57\x45\x42\x50\x56\x50\x38\x4C\x63\x61\x02\x00\x2F\x1F\xC3\x95\x10\x8D\xC8\x72\xDB\xC8\x92\x24\xD8\x91\xD9\x91", + array( 'compression' => 'lossless', 'width' => 800, 'height' => 600 ) ), + array( "\x52\x49\x46\x46\x1C\x1D\x01\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\x1F\x03\x00\x57\x02\x00\x41\x4C\x50\x48\x25\x8B", + array( 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 800, 'height' => 600 ) ), + array( "\x52\x49\x46\x46\xFA\xC5\x00\x00\x57\x45\x42\x50\x56\x50\x38\x4C\xEE\xC5\x00\x00\x2F\xA4\x81\x28\x10\x8D\x40\x68\x24\xC9\x91\xA4\xAE\xF3\x97\x75", + array( 'compression' => 'lossless', 'width' => 421, 'height' => 163 ) ), + array( "\x52\x49\x46\x46\xF6\x5D\x00\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\xA4\x01\x00\xA2\x00\x00\x41\x4C\x50\x48\x38\x1A", + array( 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 421, 'height' => 163 ) ), + array( "\x52\x49\x46\x46\xC4\x96\x01\x00\x57\x45\x42\x50\x56\x50\x38\x4C\xB8\x96\x01\x00\x2F\x2B\xC1\x4A\x10\x11\x87\x6D\xDB\x48\x12\xFC\x60\xB0\x83\x24", + array( 'compression' => 'lossless', 'width' => 300, 'height' => 300 ) ), + array( "\x52\x49\x46\x46\x0A\x11\x01\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\x2B\x01\x00\x2B\x01\x00\x41\x4C\x50\x48\x67\x6E", + array( 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 300, 'height' => 300 ) ), + + // Lossy files from https://developers.google.com/speed/webp/gallery1 + array( "\x52\x49\x46\x46\x68\x76\x00\x00\x57\x45\x42\x50\x56\x50\x38\x20\x5C\x76\x00\x00\xD2\xBE\x01\x9D\x01\x2A\x26\x02\x70\x01\x3E\xD5\x4E\x97\x43\xA2", + array( 'compression' => 'lossy', 'width' => 550, 'height' => 368 ) ), + array( "\x52\x49\x46\x46\xB0\xEC\x00\x00\x57\x45\x42\x50\x56\x50\x38\x20\xA4\xEC\x00\x00\xB2\x4B\x02\x9D\x01\x2A\x26\x02\x94\x01\x3E\xD1\x50\x96\x46\x26", + array( 'compression' => 'lossy', 'width' => 550, 'height' => 404 ) ), + array( "\x52\x49\x46\x46\x7A\x19\x03\x00\x57\x45\x42\x50\x56\x50\x38\x20\x6E\x19\x03\x00\xB2\xF8\x09\x9D\x01\x2A\x00\x05\xD0\x02\x3E\xAD\x46\x99\x4A\xA5", + array( 'compression' => 'lossy', 'width' => 1280, 'height' => 720 ) ), + array( "\x52\x49\x46\x46\x44\xB3\x02\x00\x57\x45\x42\x50\x56\x50\x38\x20\x38\xB3\x02\x00\x52\x57\x06\x9D\x01\x2A\x00\x04\x04\x03\x3E\xA5\x44\x96\x49\x26", + array( 'compression' => 'lossy', 'width' => 1024, 'height' => 772) ), + array( "\x52\x49\x46\x46\x02\x43\x01\x00\x57\x45\x42\x50\x56\x50\x38\x20\xF6\x42\x01\x00\x12\xC0\x05\x9D\x01\x2A\x00\x04\xF0\x02\x3E\x79\x34\x93\x47\xA4", + array( 'compression' => 'lossy', 'width' => 1024, 'height' => 752) ), + + // Animated file from https://groups.google.com/a/chromium.org/d/topic/blink-dev/Y8tRC4mdQz8/discussion + array( "\x52\x49\x46\x46\xD0\x0B\x02\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x12\x00\x00\x00\x3F\x01\x00\x3F\x01\x00\x41\x4E", + array( 'compression' => 'unknown', 'animated' => true, 'transparency' => true, 'width' => 320, 'height' => 320 ) ), + + // Error cases + array( '', false ), + array( ' ', false ), + array( 'RIFF ', false ), + array( 'RIFF1234WEBP ', false ), + array( 'RIFF1234WEBPVP8 ', false ), + array( 'RIFF1234WEBPVP8L ', false ), + ); + } + + /** + * @dataProvider provideTestWithFileExtractMetaData + */ + public function testWithFileExtractMetaData( $filename, $expectedResult ) { + $this->assertEquals( $expectedResult, WebPHandler::extractMetadata( $filename ) ); + } + public function provideTestWithFileExtractMetaData() { + return array( + array( __DIR__ . '/../../data/media/2_webp_ll.webp', + array( 'compression' => 'lossless', 'width' => 386, 'height' => 395 ) ), + array( __DIR__ . '/../../data/media/2_webp_a.webp', + array( 'compression' => 'lossy', 'animated' => false, 'transparency' => true, 'width' => 386, 'height' => 395 ) ), + ); + } + + /** + * @dataProvider provideTestGetImageSize + */ + public function testGetImageSize( $path, $expectedResult ) { + $handler = new WebPHandler(); + $this->assertEquals( $expectedResult, $handler->getImageSize( null, $path ) ); + } + public function provideTestGetImageSize() { + return array( + // Public domain files from https://developers.google.com/speed/webp/gallery2 + array( __DIR__ . '/../../data/media/2_webp_a.webp', array( 386, 395 ) ), + array( __DIR__ . '/../../data/media/2_webp_ll.webp', array( 386, 395 ) ), + array( __DIR__ . '/../../data/media/webp_animated.webp', array( 300, 225 ) ), + + // Error cases + array( __FILE__, false ), + ); + } + + /** + * Tests the WebP MIME detection. This should really be a separate test, but sticking it + * here for now. + * + * @dataProvider provideTestGetMimeType + */ + public function testGuessMimeType( $path ) { + $mime = MimeMagic::singleton(); + $this->assertEquals( 'image/webp', $mime->guessMimeType( $path, false ) ); + } + public function provideTestGetMimeType() { + return array( + // Public domain files from https://developers.google.com/speed/webp/gallery2 + array( __DIR__ . '/../../data/media/2_webp_a.webp' ), + array( __DIR__ . '/../../data/media/2_webp_ll.webp' ), + array( __DIR__ . '/../../data/media/webp_animated.webp' ), + ); + } +} + +/* Python code to extract a header and convert to PHP format: + * print '"%s"' % ''.join( '\\x%02X' % ord(c) for c in urllib.urlopen(url).read(36) ) + */ diff --git a/tests/phpunit/includes/media/XMPValidateTest.php b/tests/phpunit/includes/media/XMPValidateTest.php index ebec8f6c..53671d42 100644 --- a/tests/phpunit/includes/media/XMPValidateTest.php +++ b/tests/phpunit/includes/media/XMPValidateTest.php @@ -1,5 +1,7 @@ <?php +use Psr\Log\NullLogger; + /** * @group Media */ @@ -11,7 +13,8 @@ class XMPValidateTest extends MediaWikiTestCase { */ public function testValidateDate( $value, $expected ) { // The method should modify $value. - XMPValidate::validateDate( array(), $value, true ); + $validate = new XMPValidate( new NullLogger() ); + $validate->validateDate( array(), $value, true ); $this->assertEquals( $expected, $value ); } diff --git a/tests/phpunit/includes/objectcache/BagOStuffTest.php b/tests/phpunit/includes/objectcache/BagOStuffTest.php index 4516bb4e..b6840062 100644 --- a/tests/phpunit/includes/objectcache/BagOStuffTest.php +++ b/tests/phpunit/includes/objectcache/BagOStuffTest.php @@ -1,8 +1,10 @@ <?php /** * @author Matthias Mullie <mmullie@wikimedia.org> + * @group BagOStuff */ class BagOStuffTest extends MediaWikiTestCase { + /** @var BagOStuff */ private $cache; protected function setUp() { @@ -136,20 +138,48 @@ class BagOStuffTest extends MediaWikiTestCase { public function testGetMulti() { $value1 = array( 'this' => 'is', 'a' => 'test' ); $value2 = array( 'this' => 'is', 'another' => 'test' ); + $value3 = array( 'testing a key that may be encoded when sent to cache backend' ); $key1 = wfMemcKey( 'test1' ); $key2 = wfMemcKey( 'test2' ); + $key3 = wfMemcKey( 'will-%-encode' ); // internally, MemcachedBagOStuffs will encode to will-%25-encode $this->cache->add( $key1, $value1 ); $this->cache->add( $key2, $value2 ); + $this->cache->add( $key3, $value3 ); $this->assertEquals( - $this->cache->getMulti( array( $key1, $key2 ) ), - array( $key1 => $value1, $key2 => $value2 ) + array( $key1 => $value1, $key2 => $value2, $key3 => $value3 ), + $this->cache->getMulti( array( $key1, $key2, $key3 ) ) ); // cleanup $this->cache->delete( $key1 ); $this->cache->delete( $key2 ); + $this->cache->delete( $key3 ); + } + + /** + * @covers BagOStuff::getScopedLock + */ + public function testGetScopedLock() { + $key = wfMemcKey( 'test' ); + $value1 = $this->cache->getScopedLock( $key, 0 ); + $value2 = $this->cache->getScopedLock( $key, 0 ); + + $this->assertType( 'ScopedCallback', $value1, 'First call returned lock' ); + $this->assertNull( $value2, 'Duplicate call returned no lock' ); + + unset( $value1 ); + + $value3 = $this->cache->getScopedLock( $key, 0 ); + $this->assertType( 'ScopedCallback', $value3, 'Lock returned callback after release' ); + unset( $value3 ); + + $value1 = $this->cache->getScopedLock( $key, 0, 5, 'reentry' ); + $value2 = $this->cache->getScopedLock( $key, 0, 5, 'reentry' ); + + $this->assertType( 'ScopedCallback', $value1, 'First reentrant call returned lock' ); + $this->assertType( 'ScopedCallback', $value1, 'Second reentrant call returned lock' ); } } diff --git a/tests/phpunit/includes/objectcache/MultiWriteBagOStuffTest.php b/tests/phpunit/includes/objectcache/MultiWriteBagOStuffTest.php new file mode 100644 index 00000000..2b66181c --- /dev/null +++ b/tests/phpunit/includes/objectcache/MultiWriteBagOStuffTest.php @@ -0,0 +1,55 @@ +<?php + +/** + * @group Database + */ +class MultiWriteBagOStuffTest extends MediaWikiTestCase { + /** @var HashBagOStuff */ + private $cache1; + /** @var HashBagOStuff */ + private $cache2; + /** @var MultiWriteBagOStuff */ + private $cache; + + protected function setUp() { + parent::setUp(); + + $this->cache1 = new HashBagOStuff(); + $this->cache2 = new HashBagOStuff(); + $this->cache = new MultiWriteBagOStuff( array( + 'caches' => array( $this->cache1, $this->cache2 ), + 'replication' => 'async' + ) ); + } + + public function testSetImmediate() { + $key = wfRandomString(); + $value = wfRandomString(); + $this->cache->set( $key, $value ); + + // Set in tier 1 + $this->assertEquals( $value, $this->cache1->get( $key ), 'Written to tier 1' ); + // Set in tier 2 + $this->assertEquals( $value, $this->cache2->get( $key ), 'Written to tier 2' ); + } + + public function testSetDelayed() { + $key = wfRandomString(); + $value = wfRandomString(); + + // XXX: DeferredUpdates bound to transactions in CLI mode + $dbw = wfGetDB( DB_MASTER ); + $dbw->begin(); + $this->cache->set( $key, $value ); + + // Set in tier 1 + $this->assertEquals( $value, $this->cache1->get( $key ), 'Written to tier 1' ); + // Not yet set in tier 2 + $this->assertEquals( false, $this->cache2->get( $key ), 'Not written to tier 2' ); + + $dbw->commit(); + + // Set in tier 2 + $this->assertEquals( $value, $this->cache2->get( $key ), 'Written to tier 2' ); + } +} diff --git a/tests/phpunit/includes/objectcache/ReplicatedBagOStuffTest.php b/tests/phpunit/includes/objectcache/ReplicatedBagOStuffTest.php new file mode 100644 index 00000000..a419f5b6 --- /dev/null +++ b/tests/phpunit/includes/objectcache/ReplicatedBagOStuffTest.php @@ -0,0 +1,62 @@ +<?php + +class ReplicatedBagOStuffTest extends MediaWikiTestCase { + /** @var HashBagOStuff */ + private $writeCache; + /** @var HashBagOStuff */ + private $readCache; + /** @var ReplicatedBagOStuff */ + private $cache; + + protected function setUp() { + parent::setUp(); + + $this->writeCache = new HashBagOStuff(); + $this->readCache = new HashBagOStuff(); + $this->cache = new ReplicatedBagOStuff( array( + 'writeFactory' => $this->writeCache, + 'readFactory' => $this->readCache, + ) ); + } + + /** + * @covers ReplicatedBagOStuff::set + */ + public function testSet() { + $key = wfRandomString(); + $value = wfRandomString(); + $this->cache->set( $key, $value ); + + // Write to master. + $this->assertEquals( $this->writeCache->get( $key ), $value ); + // Don't write to slave. Replication is deferred to backend. + $this->assertEquals( $this->readCache->get( $key ), false ); + } + + /** + * @covers ReplicatedBagOStuff::get + */ + public function testGet() { + $key = wfRandomString(); + + $write = wfRandomString(); + $this->writeCache->set( $key, $write ); + $read = wfRandomString(); + $this->readCache->set( $key, $read ); + + // Read from slave. + $this->assertEquals( $this->cache->get( $key ), $read ); + } + + /** + * @covers ReplicatedBagOStuff::get + */ + public function testGetAbsent() { + $key = wfRandomString(); + $value = wfRandomString(); + $this->writeCache->set( $key, $value ); + + // Don't read from master. No failover if value is absent. + $this->assertEquals( $this->cache->get( $key ), false ); + } +} diff --git a/tests/phpunit/includes/objectcache/WANObjectCacheTest.php b/tests/phpunit/includes/objectcache/WANObjectCacheTest.php new file mode 100644 index 00000000..40ae4613 --- /dev/null +++ b/tests/phpunit/includes/objectcache/WANObjectCacheTest.php @@ -0,0 +1,292 @@ +<?php + +class WANObjectCacheTest extends MediaWikiTestCase { + /** @var WANObjectCache */ + private $cache; + + protected function setUp() { + parent::setUp(); + + if ( $this->getCliArg( 'use-wanobjectcache' ) ) { + $name = $this->getCliArg( 'use-wanobjectcache' ); + + $this->cache = ObjectCache::getWANInstance( $name ); + } else { + $this->cache = new WANObjectCache( array( + 'cache' => new HashBagOStuff(), + 'pool' => 'testcache-hash', + 'relayer' => new EventRelayerNull( array() ) + ) ); + } + + $wanCache = TestingAccessWrapper::newFromObject( $this->cache ); + $this->internalCache = $wanCache->cache; + } + + /** + * @dataProvider provider_testSetAndGet + * @covers WANObjectCache::set() + * @covers WANObjectCache::get() + * @param mixed $value + * @param integer $ttl + */ + public function testSetAndGet( $value, $ttl ) { + $key = wfRandomString(); + $this->cache->set( $key, $value, $ttl ); + + $curTTL = null; + $this->assertEquals( $value, $this->cache->get( $key, $curTTL ) ); + if ( is_infinite( $ttl ) || $ttl == 0 ) { + $this->assertTrue( is_infinite( $curTTL ), "Current TTL is infinite" ); + } else { + $this->assertGreaterThan( 0, $curTTL, "Current TTL > 0" ); + $this->assertLessThanOrEqual( $ttl, $curTTL, "Current TTL < nominal TTL" ); + } + } + + public static function provider_testSetAndGet() { + return array( + array( 14141, 3 ), + array( 3535.666, 3 ), + array( array(), 3 ), + array( null, 3 ), + array( '0', 3 ), + array( (object)array( 'meow' ), 3 ), + array( INF, 3 ), + array( '', 3 ), + array( 'pizzacat', INF ), + ); + } + + public function testGetNotExists() { + $key = wfRandomString(); + $curTTL = null; + $value = $this->cache->get( $key, $curTTL ); + + $this->assertFalse( $value, "Non-existing key has false value" ); + $this->assertNull( $curTTL, "Non-existing key has null current TTL" ); + } + + public function testSetOver() { + $key = wfRandomString(); + for ( $i = 0; $i < 3; ++$i ) { + $value = wfRandomString(); + $this->cache->set( $key, $value, 3 ); + + $this->assertEquals( $this->cache->get( $key ), $value ); + } + } + + /** + * @covers WANObjectCache::getWithSetCallback() + */ + public function testGetWithSetCallback() { + $cache = $this->cache; + + $key = wfRandomString(); + $value = wfRandomString(); + $cKey1 = wfRandomString(); + $cKey2 = wfRandomString(); + + $wasSet = 0; + $func = function( $old, &$ttl ) use ( &$wasSet, $value ) { + ++$wasSet; + $ttl = 20; // override with another value + return $value; + }; + + $wasSet = 0; + $v = $cache->getWithSetCallback( $key, $func, 30, array(), array( 'lockTSE' => 5 ) ); + $this->assertEquals( $v, $value ); + $this->assertEquals( 1, $wasSet, "Value regenerated" ); + + $curTTL = null; + $v = $cache->get( $key, $curTTL ); + $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' ); + $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' ); + + $wasSet = 0; + $v = $cache->getWithSetCallback( $key, $func, 30, array(), array( 'lockTSE' => 5 ) ); + $this->assertEquals( $v, $value ); + $this->assertEquals( 0, $wasSet, "Value not regenerated" ); + + $priorTime = microtime( true ); + usleep( 1 ); + $wasSet = 0; + $v = $cache->getWithSetCallback( $key, $func, 30, array( $cKey1, $cKey2 ) ); + $this->assertEquals( $v, $value ); + $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" ); + $t1 = $cache->getCheckKeyTime( $cKey1 ); + $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' ); + $t2 = $cache->getCheckKeyTime( $cKey2 ); + $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' ); + + $priorTime = microtime( true ); + $wasSet = 0; + $v = $cache->getWithSetCallback( $key, $func, 30, array( $cKey1, $cKey2 ) ); + $this->assertEquals( $v, $value ); + $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" ); + $t1 = $cache->getCheckKeyTime( $cKey1 ); + $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' ); + $t2 = $cache->getCheckKeyTime( $cKey2 ); + $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' ); + + $curTTL = null; + $v = $cache->get( $key, $curTTL, array( $cKey1, $cKey2 ) ); + $this->assertEquals( $v, $value ); + $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" ); + } + + /** + * @covers WANObjectCache::getWithSetCallback() + */ + public function testLockTSE() { + $cache = $this->cache; + $key = wfRandomString(); + $value = wfRandomString(); + + $calls = 0; + $func = function() use ( &$calls, $value ) { + ++$calls; + return $value; + }; + + $cache->delete( $key ); + $ret = $cache->getWithSetCallback( $key, 30, $func, array(), array( 'lockTSE' => 5 ) ); + $this->assertEquals( $value, $ret ); + $this->assertEquals( 1, $calls, 'Value was populated' ); + + // Acquire a lock to verify that getWithSetCallback uses lockTSE properly + $this->internalCache->lock( $key, 0 ); + $ret = $cache->getWithSetCallback( $key, 30, $func, array(), array( 'lockTSE' => 5 ) ); + $this->assertEquals( $value, $ret ); + $this->assertEquals( 1, $calls, 'Callback was not used' ); + } + + /** + * @covers WANObjectCache::getMulti() + */ + public function testGetMulti() { + $cache = $this->cache; + + $value1 = array( 'this' => 'is', 'a' => 'test' ); + $value2 = array( 'this' => 'is', 'another' => 'test' ); + + $key1 = wfRandomString(); + $key2 = wfRandomString(); + $key3 = wfRandomString(); + + $cache->set( $key1, $value1, 5 ); + $cache->set( $key2, $value2, 10 ); + + $curTTLs = array(); + $this->assertEquals( + array( $key1 => $value1, $key2 => $value2 ), + $cache->getMulti( array( $key1, $key2, $key3 ), $curTTLs ) + ); + + $this->assertEquals( 2, count( $curTTLs ), "Two current TTLs in array" ); + $this->assertGreaterThan( 0, $curTTLs[$key1], "Key 1 has current TTL > 0" ); + $this->assertGreaterThan( 0, $curTTLs[$key2], "Key 2 has current TTL > 0" ); + + $cKey1 = wfRandomString(); + $cKey2 = wfRandomString(); + $curTTLs = array(); + $this->assertEquals( + array( $key1 => $value1, $key2 => $value2 ), + $cache->getMulti( array( $key1, $key2, $key3 ), $curTTLs ), + 'Result array populated' + ); + + $priorTime = microtime( true ); + usleep( 1 ); + $curTTLs = array(); + $this->assertEquals( + array( $key1 => $value1, $key2 => $value2 ), + $cache->getMulti( array( $key1, $key2, $key3 ), $curTTLs, array( $cKey1, $cKey2 ) ), + "Result array populated even with new check keys" + ); + $t1 = $cache->getCheckKeyTime( $cKey1 ); + $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key 1 generated on miss' ); + $t2 = $cache->getCheckKeyTime( $cKey2 ); + $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check key 2 generated on miss' ); + $this->assertEquals( 2, count( $curTTLs ), "Current TTLs array set" ); + $this->assertLessThanOrEqual( 0, $curTTLs[$key1], 'Key 1 has current TTL <= 0' ); + $this->assertLessThanOrEqual( 0, $curTTLs[$key2], 'Key 2 has current TTL <= 0' ); + + usleep( 1 ); + $curTTLs = array(); + $this->assertEquals( + array( $key1 => $value1, $key2 => $value2 ), + $cache->getMulti( array( $key1, $key2, $key3 ), $curTTLs, array( $cKey1, $cKey2 ) ), + "Result array still populated even with new check keys" + ); + $this->assertEquals( 2, count( $curTTLs ), "Current TTLs still array set" ); + $this->assertLessThan( 0, $curTTLs[$key1], 'Key 1 has negative current TTL' ); + $this->assertLessThan( 0, $curTTLs[$key2], 'Key 2 has negative current TTL' ); + } + + /** + * @covers WANObjectCache::delete() + */ + public function testDelete() { + $key = wfRandomString(); + $value = wfRandomString(); + $this->cache->set( $key, $value ); + + $curTTL = null; + $v = $this->cache->get( $key, $curTTL ); + $this->assertEquals( $value, $v, "Key was created with value" ); + $this->assertGreaterThan( 0, $curTTL, "Existing key has current TTL > 0" ); + + $this->cache->delete( $key ); + + $curTTL = null; + $v = $this->cache->get( $key, $curTTL ); + $this->assertFalse( $v, "Deleted key has false value" ); + $this->assertLessThan( 0, $curTTL, "Deleted key has current TTL < 0" ); + + $this->cache->set( $key, $value . 'more' ); + $this->assertFalse( $v, "Deleted key is tombstoned and has false value" ); + $this->assertLessThan( 0, $curTTL, "Deleted key is tombstoned and has current TTL < 0" ); + } + + /** + * @covers WANObjectCache::touchCheckKey() + * @covers WANObjectCache::resetCheckKey() + * @covers WANObjectCache::getCheckKeyTime() + */ + public function testTouchKeys() { + $key = wfRandomString(); + + $priorTime = microtime( true ); + usleep( 1 ); + $t0 = $this->cache->getCheckKeyTime( $key ); + $this->assertGreaterThanOrEqual( $priorTime, $t0, 'Check key auto-created' ); + + $priorTime = microtime( true ); + usleep( 1 ); + $this->cache->touchCheckKey( $key ); + $t1 = $this->cache->getCheckKeyTime( $key ); + $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key created' ); + + $t2 = $this->cache->getCheckKeyTime( $key ); + $this->assertEquals( $t1, $t2, 'Check key time did not change' ); + + usleep( 1 ); + $this->cache->touchCheckKey( $key ); + $t3 = $this->cache->getCheckKeyTime( $key ); + $this->assertGreaterThan( $t2, $t3, 'Check key time increased' ); + + $t4 = $this->cache->getCheckKeyTime( $key ); + $this->assertEquals( $t3, $t4, 'Check key time did not change' ); + + usleep( 1 ); + $this->cache->resetCheckKey( $key ); + $t5 = $this->cache->getCheckKeyTime( $key ); + $this->assertGreaterThan( $t4, $t5, 'Check key time increased' ); + + $t6 = $this->cache->getCheckKeyTime( $key ); + $this->assertEquals( $t5, $t6, 'Check key time did not change' ); + } +} diff --git a/tests/phpunit/includes/parser/MagicVariableTest.php b/tests/phpunit/includes/parser/MagicVariableTest.php index 17226113..cd54a9e3 100644 --- a/tests/phpunit/includes/parser/MagicVariableTest.php +++ b/tests/phpunit/includes/parser/MagicVariableTest.php @@ -10,6 +10,8 @@ * @copyright Copyright © 2011, Antoine Musso * @file * @todo covers tags + * + * @group Database */ class MagicVariableTest extends MediaWikiTestCase { diff --git a/tests/phpunit/includes/parser/MediaWikiParserTest.php b/tests/phpunit/includes/parser/MediaWikiParserTest.php index e6dbfc30..210c17c5 100644 --- a/tests/phpunit/includes/parser/MediaWikiParserTest.php +++ b/tests/phpunit/includes/parser/MediaWikiParserTest.php @@ -92,7 +92,7 @@ class MediaWikiParserTest { // enough to cause there to be separate names for different // things, which is good enough for our purposes. $extensionName = basename( dirname( $fileName ) ); - $testsName = $extensionName . '⁄' . basename( $fileName, '.txt' ); + $testsName = $extensionName . '__' . basename( $fileName, '.txt' ); $escapedFileName = strtr( $fileName, array( "'" => "\\'", '\\' => '\\\\' ) ); $parserTestClassName = ucfirst( $testsName ); // Official spec for class names: http://php.net/manual/en/language.oop5.basic.php diff --git a/tests/phpunit/includes/parser/NewParserTest.php b/tests/phpunit/includes/parser/NewParserTest.php index 7d21c4f7..d95e9225 100644 --- a/tests/phpunit/includes/parser/NewParserTest.php +++ b/tests/phpunit/includes/parser/NewParserTest.php @@ -91,7 +91,7 @@ class NewParserTest extends MediaWikiTestCase { ); $tmpGlobals['wgForeignFileRepos'] = array(); $tmpGlobals['wgDefaultExternalStore'] = array(); - $tmpGlobals['wgEnableParserCache'] = false; + $tmpGlobals['wgParserCacheType'] = CACHE_NONE; $tmpGlobals['wgCapitalLinks'] = true; $tmpGlobals['wgNoFollowLinks'] = true; $tmpGlobals['wgNoFollowDomainExceptions'] = array(); @@ -106,7 +106,6 @@ class NewParserTest extends MediaWikiTestCase { $tmpGlobals['wgAdaptiveMessageCache'] = true; $tmpGlobals['wgUseDatabaseMessages'] = true; $tmpGlobals['wgLocaltimezone'] = 'UTC'; - $tmpGlobals['wgDeferredUpdateList'] = array(); $tmpGlobals['wgGroupPermissions'] = array( '*' => array( 'createaccount' => true, @@ -160,10 +159,10 @@ class NewParserTest extends MediaWikiTestCase { $this->djVuSupport = new DjVuSupport(); // Tidy support $this->tidySupport = new TidySupport(); + $tmpGlobals['wgTidyConfig'] = null; $tmpGlobals['wgUseTidy'] = false; - $tmpGlobals['wgAlwaysUseTidy'] = false; $tmpGlobals['wgDebugTidy'] = false; - $tmpGlobals['wgTidyConf'] = $IP . '/includes/tidy.conf'; + $tmpGlobals['wgTidyConf'] = $IP . '/includes/tidy/tidy.conf'; $tmpGlobals['wgTidyOpts'] = ''; $tmpGlobals['wgTidyInternal'] = $this->tidySupport->isInternal(); @@ -185,6 +184,8 @@ class NewParserTest extends MediaWikiTestCase { $wgNamespaceAliases['Image'] = $this->savedWeirdGlobals['image_alias']; $wgNamespaceAliases['Image_talk'] = $this->savedWeirdGlobals['image_talk_alias']; + MWTidy::destroySingleton(); + // Restore backends RepoGroup::destroySingleton(); FileBackendGroup::destroySingleton(); @@ -454,6 +455,7 @@ class NewParserTest extends MediaWikiTestCase { $GLOBALS[$var] = $val; } + MWTidy::destroySingleton(); MagicWord::clearCache(); # The entries saved into RepoGroup cache with previous globals will be wrong. diff --git a/tests/phpunit/includes/parser/ParserMethodsTest.php b/tests/phpunit/includes/parser/ParserMethodsTest.php index 1790086a..af143caa 100644 --- a/tests/phpunit/includes/parser/ParserMethodsTest.php +++ b/tests/phpunit/includes/parser/ParserMethodsTest.php @@ -1,5 +1,9 @@ <?php +/** + * @group Database + */ + class ParserMethodsTest extends MediaWikiLangTestCase { public static function providePreSaveTransform() { diff --git a/tests/phpunit/includes/parser/TagHooksTest.php b/tests/phpunit/includes/parser/TagHooksTest.php index 251da471..4af38985 100644 --- a/tests/phpunit/includes/parser/TagHooksTest.php +++ b/tests/phpunit/includes/parser/TagHooksTest.php @@ -1,6 +1,7 @@ <?php /** + * @group Database * @group Parser */ class TagHookTest extends MediaWikiTestCase { @@ -18,12 +19,6 @@ class TagHookTest extends MediaWikiTestCase { return array( array( "foo<bar" ), array( "foo>bar" ), array( "foo\nbar" ), array( "foo\rbar" ) ); } - protected function setUp() { - parent::setUp(); - - $this->setMwGlobals( 'wgAlwaysUseTidy', false ); - } - /** * @dataProvider provideValidNames * @covers Parser::setHook diff --git a/tests/phpunit/includes/parser/TidyTest.php b/tests/phpunit/includes/parser/TidyTest.php index f656a74d..5db29080 100644 --- a/tests/phpunit/includes/parser/TidyTest.php +++ b/tests/phpunit/includes/parser/TidyTest.php @@ -7,8 +7,7 @@ class TidyTest extends MediaWikiTestCase { protected function setUp() { parent::setUp(); - $check = MWTidy::tidy( '' ); - if ( strpos( $check, '<!--' ) !== false ) { + if ( !MWTidy::isEnabled() ) { $this->markTestSkipped( 'Tidy not found' ); } } diff --git a/tests/phpunit/includes/password/PasswordPolicyChecksTest.php b/tests/phpunit/includes/password/PasswordPolicyChecksTest.php new file mode 100644 index 00000000..af34282f --- /dev/null +++ b/tests/phpunit/includes/password/PasswordPolicyChecksTest.php @@ -0,0 +1,136 @@ +<?php +/** + * Testing password-policy check functions + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +class PasswordPolicyChecksTest extends MediaWikiTestCase { + + /** + * @covers PasswordPolicyChecks::checkMinimalPasswordLength + */ + public function testCheckMinimalPasswordLength() { + $statusOK = PasswordPolicyChecks::checkMinimalPasswordLength( + 3, // policy value + User::newFromName( 'user' ), // User + 'password' // password + ); + $this->assertTrue( $statusOK->isGood(), 'Password is longer than minimal policy' ); + $statusShort = PasswordPolicyChecks::checkMinimalPasswordLength( + 10, // policy value + User::newFromName( 'user' ), // User + 'password' // password + ); + $this->assertFalse( + $statusShort->isGood(), + 'Password is shorter than minimal policy' + ); + $this->assertTrue( + $statusShort->isOk(), + 'Password is shorter than minimal policy, not fatal' + ); + } + + /** + * @covers PasswordPolicyChecks::checkMinimumPasswordLengthToLogin + */ + public function testCheckMinimumPasswordLengthToLogin() { + $statusOK = PasswordPolicyChecks::checkMinimumPasswordLengthToLogin( + 3, // policy value + User::newFromName( 'user' ), // User + 'password' // password + ); + $this->assertTrue( $statusOK->isGood(), 'Password is longer than minimal policy' ); + $statusShort = PasswordPolicyChecks::checkMinimumPasswordLengthToLogin( + 10, // policy value + User::newFromName( 'user' ), // User + 'password' // password + ); + $this->assertFalse( + $statusShort->isGood(), + 'Password is shorter than minimum login policy' + ); + $this->assertFalse( + $statusShort->isOk(), + 'Password is shorter than minimum login policy, fatal' + ); + } + + /** + * @covers PasswordPolicyChecks::checkMaximalPasswordLength + */ + public function testCheckMaximalPasswordLength() { + $statusOK = PasswordPolicyChecks::checkMaximalPasswordLength( + 100, // policy value + User::newFromName( 'user' ), // User + 'password' // password + ); + $this->assertTrue( $statusOK->isGood(), 'Password is shorter than maximal policy' ); + $statusLong = PasswordPolicyChecks::checkMaximalPasswordLength( + 4, // policy value + User::newFromName( 'user' ), // User + 'password' // password + ); + $this->assertFalse( $statusLong->isGood(), + 'Password is longer than maximal policy' + ); + $this->assertFalse( $statusLong->isOk(), + 'Password is longer than maximal policy, fatal' + ); + } + + /** + * @covers PasswordPolicyChecks::checkPasswordCannotMatchUsername + */ + public function testCheckPasswordCannotMatchUsername() { + $statusOK = PasswordPolicyChecks::checkPasswordCannotMatchUsername( + 1, // policy value + User::newFromName( 'user' ), // User + 'password' // password + ); + $this->assertTrue( $statusOK->isGood(), 'Password does not match username' ); + $statusLong = PasswordPolicyChecks::checkPasswordCannotMatchUsername( + 1, // policy value + User::newFromName( 'user' ), // User + 'user' // password + ); + $this->assertFalse( $statusLong->isGood(), 'Password matches username' ); + $this->assertTrue( $statusLong->isOk(), 'Password matches username, not fatal' ); + } + + /** + * @covers PasswordPolicyChecks::checkPasswordCannotMatchBlacklist + */ + public function testCheckPasswordCannotMatchBlacklist() { + $statusOK = PasswordPolicyChecks::checkPasswordCannotMatchBlacklist( + true, // policy value + User::newFromName( 'Username' ), // User + 'AUniquePassword' // password + ); + $this->assertTrue( $statusOK->isGood(), 'Password is not on blacklist' ); + $statusLong = PasswordPolicyChecks::checkPasswordCannotMatchBlacklist( + true, // policy value + User::newFromName( 'Useruser1' ), // User + 'Passpass1' // password + ); + $this->assertFalse( $statusLong->isGood(), 'Password matches blacklist' ); + $this->assertTrue( $statusLong->isOk(), 'Password matches blacklist, not fatal' ); + } + +} diff --git a/tests/phpunit/includes/password/UserPasswordPolicyTest.php b/tests/phpunit/includes/password/UserPasswordPolicyTest.php new file mode 100644 index 00000000..ce4e30ab --- /dev/null +++ b/tests/phpunit/includes/password/UserPasswordPolicyTest.php @@ -0,0 +1,234 @@ +<?php +/** + * Testing for password-policy enforcement, based on a user's groups. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +class UserPasswordPolicyTest extends MediaWikiTestCase { + + protected $policies = array( + 'checkuser' => array( + 'MinimalPasswordLength' => 10, + 'MinimumPasswordLengthToLogin' => 6, + 'PasswordCannotMatchUsername' => true, + ), + 'sysop' => array( + 'MinimalPasswordLength' => 8, + 'MinimumPasswordLengthToLogin' => 1, + 'PasswordCannotMatchUsername' => true, + ), + 'default' => array( + 'MinimalPasswordLength' => 4, + 'MinimumPasswordLengthToLogin' => 1, + 'PasswordCannotMatchBlacklist' => true, + 'MaximalPasswordLength' => 4096, + ), + ); + + protected $checks = array( + 'MinimalPasswordLength' => 'PasswordPolicyChecks::checkMinimalPasswordLength', + 'MinimumPasswordLengthToLogin' => 'PasswordPolicyChecks::checkMinimumPasswordLengthToLogin', + 'PasswordCannotMatchUsername' => 'PasswordPolicyChecks::checkPasswordCannotMatchUsername', + 'PasswordCannotMatchBlacklist' => 'PasswordPolicyChecks::checkPasswordCannotMatchBlacklist', + 'MaximalPasswordLength' => 'PasswordPolicyChecks::checkMaximalPasswordLength', + ); + + private function getUserPasswordPolicy() { + return new UserPasswordPolicy( $this->policies, $this->checks ); + } + + /** + * @covers UserPasswordPolicy::getPoliciesForUser + */ + public function testGetPoliciesForUser() { + + $upp = $this->getUserPasswordPolicy(); + + $user = User::newFromName( 'TestUserPolicy' ); + $user->addGroup( 'sysop' ); + + $this->assertArrayEquals( + array( + 'MinimalPasswordLength' => 8, + 'MinimumPasswordLengthToLogin' => 1, + 'PasswordCannotMatchUsername' => 1, + 'PasswordCannotMatchBlacklist' => true, + 'MaximalPasswordLength' => 4096, + ), + $upp->getPoliciesForUser( $user ) + ); + } + + /** + * @covers UserPasswordPolicy::getPoliciesForGroups + */ + public function testGetPoliciesForGroups() { + $effective = UserPasswordPolicy::getPoliciesForGroups( + $this->policies, + array( 'user', 'checkuser' ), + $this->policies['default'] + ); + + $this->assertArrayEquals( + array( + 'MinimalPasswordLength' => 10, + 'MinimumPasswordLengthToLogin' => 6, + 'PasswordCannotMatchUsername' => true, + 'PasswordCannotMatchBlacklist' => true, + 'MaximalPasswordLength' => 4096, + ), + $effective + ); + } + + /** + * @dataProvider provideCheckUserPassword + * @covers UserPasswordPolicy::checkUserPassword + */ + public function testCheckUserPassword( $username, $groups, $password, $valid, $ok, $msg ) { + + $upp = $this->getUserPasswordPolicy(); + + $user = User::newFromName( $username ); + foreach ( $groups as $group ) { + $user->addGroup( $group ); + } + + $status = $upp->checkUserPassword( $user, $password ); + $this->assertSame( $valid, $status->isGood(), $msg . ' - password valid' ); + $this->assertSame( $ok, $status->isOk(), $msg . ' - can login' ); + } + + public function provideCheckUserPassword() { + return array( + array( + 'PassPolicyUser', + array(), + '', + false, + false, + 'No groups, default policy, password too short to login' + ), + array( + 'PassPolicyUser', + array( 'user' ), + 'aaa', + false, + true, + 'Default policy, short password' + ), + array( + 'PassPolicyUser', + array( 'sysop' ), + 'abcdabcdabcd', + true, + true, + 'Sysop with good password' + ), + array( + 'PassPolicyUser', + array( 'sysop' ), + 'abcd', + false, + true, + 'Sysop with short password' + ), + array( + 'PassPolicyUser', + array( 'sysop', 'checkuser' ), + 'abcdabcd', + false, + true, + 'Checkuser with short password' + ), + array( + 'PassPolicyUser', + array( 'sysop', 'checkuser' ), + 'abcd', + false, + false, + 'Checkuser with too short password to login' + ), + array( + 'Useruser', + array( 'user' ), + 'Passpass', + false, + true, + 'Username & password on blacklist' + ), + ); + } + + /** + * @dataProvider provideMaxOfPolicies + * @covers UserPasswordPolicy::maxOfPolicies + */ + public function testMaxOfPolicies( $p1, $p2, $max, $msg ) { + $this->assertArrayEquals( + $max, + UserPasswordPolicy::maxOfPolicies( $p1, $p2 ), + $msg + ); + } + + public function provideMaxOfPolicies() { + return array( + array( + array( 'MinimalPasswordLength' => 8 ), //p1 + array( 'MinimalPasswordLength' => 2 ), //p2 + array( 'MinimalPasswordLength' => 8 ), //max + 'Basic max in p1' + ), + array( + array( 'MinimalPasswordLength' => 2 ), //p1 + array( 'MinimalPasswordLength' => 8 ), //p2 + array( 'MinimalPasswordLength' => 8 ), //max + 'Basic max in p2' + ), + array( + array( 'MinimalPasswordLength' => 8 ), //p1 + array( + 'MinimalPasswordLength' => 2, + 'PasswordCannotMatchUsername' => 1, + ), //p2 + array( + 'MinimalPasswordLength' => 8, + 'PasswordCannotMatchUsername' => 1, + ), //max + 'Missing items in p1' + ), + array( + array( + 'MinimalPasswordLength' => 8, + 'PasswordCannotMatchUsername' => 1, + ), //p1 + array( + 'MinimalPasswordLength' => 2, + ), //p2 + array( + 'MinimalPasswordLength' => 8, + 'PasswordCannotMatchUsername' => 1, + ), //max + 'Missing items in p2' + ), + ); + } + +} diff --git a/tests/phpunit/includes/registration/CoreVersionCheckerTest.php b/tests/phpunit/includes/registration/CoreVersionCheckerTest.php new file mode 100644 index 00000000..bc154b37 --- /dev/null +++ b/tests/phpunit/includes/registration/CoreVersionCheckerTest.php @@ -0,0 +1,38 @@ +<?php + +/** + * @covers CoreVersionChecker + */ +class CoreVersionCheckerTest extends PHPUnit_Framework_TestCase { + /** + * @dataProvider provideCheck + */ + public function testCheck( $coreVersion, $constraint, $expected ) { + $checker = new CoreVersionChecker( $coreVersion ); + $this->assertEquals( $expected, $checker->check( $constraint ) ); + } + + public static function provideCheck() { + return array( + // array( $wgVersion, constraint, expected ) + array( '1.25alpha', '>= 1.26', false ), + array( '1.25.0', '>= 1.26', false ), + array( '1.26alpha', '>= 1.26', true ), + array( '1.26alpha', '>= 1.26.0', true ), + array( '1.26alpha', '>= 1.26.0-stable', false ), + array( '1.26.0', '>= 1.26.0-stable', true ), + array( '1.26.1', '>= 1.26.0-stable', true ), + array( '1.27.1', '>= 1.26.0-stable', true ), + array( '1.26alpha', '>= 1.26.1', false ), + array( '1.26alpha', '>= 1.26alpha', true ), + array( '1.26alpha', '>= 1.25', true ), + array( '1.26.0-alpha.14', '>= 1.26.0-alpha.15', false ), + array( '1.26.0-alpha.14', '>= 1.26.0-alpha.10', true ), + array( '1.26.1', '>= 1.26.2, <=1.26.0', false ), + array( '1.26.1', '^1.26.2', false ), + // Accept anything for un-parsable version strings + array( '1.26mwf14', '== 1.25alpha', true ), + array( 'totallyinvalid', '== 1.0', true ), + ); + } +} diff --git a/tests/phpunit/includes/registration/ExtensionProcessorTest.php b/tests/phpunit/includes/registration/ExtensionProcessorTest.php index ff6be6c2..1cb8a5d9 100644 --- a/tests/phpunit/includes/registration/ExtensionProcessorTest.php +++ b/tests/phpunit/includes/registration/ExtensionProcessorTest.php @@ -14,7 +14,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase { * * @var array */ - static $default = array( + public static $default = array( 'name' => 'FooBar', ); @@ -28,7 +28,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase { '@metadata' => array( 'foobarbaz' ), 'AnAttribute' => array( 'omg' ), 'AutoloadClasses' => array( 'FooBar' => 'includes/FooBar.php' ), - ) ); + ), 1 ); $extracted = $processor->getExtractedInfo(); $attributes = $extracted['attributes']; @@ -96,7 +96,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase { */ public function testRegisterHooks( $pre, $info, $expected ) { $processor = new MockExtensionProcessor( array( 'wgHooks' => $pre ) ); - $processor->extractInfo( $this->dir, $info ); + $processor->extractInfo( $this->dir, $info, 1 ); $extracted = $processor->getExtractedInfo(); $this->assertEquals( $expected, $extracted['globals']['wgHooks'] ); } @@ -119,8 +119,8 @@ class ExtensionProcessorTest extends MediaWikiTestCase { 'Bar' => 'somevalue' ), ) + self::$default; - $processor->extractInfo( $this->dir, $info ); - $processor->extractInfo( $this->dir, $info2 ); + $processor->extractInfo( $this->dir, $info, 1 ); + $processor->extractInfo( $this->dir, $info2, 1 ); $extracted = $processor->getExtractedInfo(); $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] ); $this->assertEquals( 10, $extracted['globals']['wgFoo'] ); @@ -159,7 +159,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase { */ public function testExtracttExtensionMessagesFiles( $input, $expected ) { $processor = new ExtensionProcessor(); - $processor->extractInfo( $this->dir, $input + self::$default ); + $processor->extractInfo( $this->dir, $input + self::$default, 1 ); $out = $processor->getExtractedInfo(); foreach ( $expected as $key => $value ) { $this->assertEquals( $value, $out['globals'][$key] ); @@ -187,7 +187,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase { */ public function testExtractMessagesDirs( $input, $expected ) { $processor = new ExtensionProcessor(); - $processor->extractInfo( $this->dir, $input + self::$default ); + $processor->extractInfo( $this->dir, $input + self::$default, 1 ); $out = $processor->getExtractedInfo(); foreach ( $expected as $key => $value ) { $this->assertEquals( $value, $out['globals'][$key] ); @@ -200,7 +200,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase { */ public function testExtractResourceLoaderModules( $input, $expected ) { $processor = new ExtensionProcessor(); - $processor->extractInfo( $this->dir, $input + self::$default ); + $processor->extractInfo( $this->dir, $input + self::$default, 1 ); $out = $processor->getExtractedInfo(); foreach ( $expected as $key => $value ) { $this->assertEquals( $value, $out['globals'][$key] ); diff --git a/tests/phpunit/includes/registration/ExtensionRegistryTest.php b/tests/phpunit/includes/registration/ExtensionRegistryTest.php index c3a0c8d4..201cbfcd 100644 --- a/tests/phpunit/includes/registration/ExtensionRegistryTest.php +++ b/tests/phpunit/includes/registration/ExtensionRegistryTest.php @@ -218,6 +218,7 @@ class ExtensionRegistryTest extends MediaWikiTestCase { 'user' => array( 'right' => true, 'somethingtwo' => false, + 'nonduplicated' => true, ), ExtensionRegistry::MERGE_STRATEGY => 'array_plus_2d', ), @@ -233,6 +234,7 @@ class ExtensionRegistryTest extends MediaWikiTestCase { 'user' => array( 'somethingtwo' => true, 'right' => true, + 'nonduplicated' => true, ) ), ), diff --git a/tests/phpunit/includes/resourceloader/DerivativeResourceLoaderContextTest.php b/tests/phpunit/includes/resourceloader/DerivativeResourceLoaderContextTest.php new file mode 100644 index 00000000..0d11f621 --- /dev/null +++ b/tests/phpunit/includes/resourceloader/DerivativeResourceLoaderContextTest.php @@ -0,0 +1,78 @@ +<?php + +/** + * @group ResourceLoader + */ +class DerivativeResourceLoaderContextTest extends PHPUnit_Framework_TestCase { + + protected static function getResourceLoaderContext() { + $resourceLoader = new ResourceLoader(); + $request = new FauxRequest( array( + 'lang' => 'zh', + 'modules' => 'test.context', + 'only' => 'scripts', + 'skin' => 'fallback', + 'target' => 'test', + ) ); + return new ResourceLoaderContext( $resourceLoader, $request ); + } + + public function testGet() { + $context = self::getResourceLoaderContext(); + $derived = new DerivativeResourceLoaderContext( $context ); + + $this->assertEquals( $derived->getLanguage(), 'zh' ); + $this->assertEquals( $derived->getModules(), array( 'test.context' ) ); + $this->assertEquals( $derived->getOnly(), 'scripts' ); + $this->assertEquals( $derived->getSkin(), 'fallback' ); + $this->assertEquals( $derived->getHash(), 'zh|ltr|fallback||||||scripts|' ); + } + + public function testSetLanguage() { + $context = self::getResourceLoaderContext(); + $derived = new DerivativeResourceLoaderContext( $context ); + + $derived->setLanguage( 'nl' ); + $this->assertEquals( $derived->getLanguage(), 'nl' ); + + $derived->setLanguage( 'he' ); + $this->assertEquals( $derived->getDirection(), 'rtl' ); + } + + public function testSetModules() { + $context = self::getResourceLoaderContext(); + $derived = new DerivativeResourceLoaderContext( $context ); + + $derived->setModules( array( 'test.override' ) ); + $this->assertEquals( $derived->getModules(), array( 'test.override' ) ); + } + + public function testSetOnly() { + $context = self::getResourceLoaderContext(); + $derived = new DerivativeResourceLoaderContext( $context ); + + $derived->setOnly( 'styles' ); + $this->assertEquals( $derived->getOnly(), 'styles' ); + + $derived->setOnly( null ); + $this->assertEquals( $derived->getOnly(), null ); + } + + public function testSetSkin() { + $context = self::getResourceLoaderContext(); + $derived = new DerivativeResourceLoaderContext( $context ); + + $derived->setSkin( 'override' ); + $this->assertEquals( $derived->getSkin(), 'override' ); + } + + public function testGetHash() { + $context = self::getResourceLoaderContext(); + $derived = new DerivativeResourceLoaderContext( $context ); + + $derived->setLanguage( 'nl' ); + // Assert that subclass is able to clear parent class "hash" member + $this->assertEquals( $derived->getHash(), 'nl|ltr|fallback||||||scripts|' ); + } + +} diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php index 122995a5..9d97b282 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php @@ -1,6 +1,7 @@ <?php /** + * @group Database * @group ResourceLoader */ class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase { @@ -157,7 +158,7 @@ class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase { * @covers ResourceLoaderFileModule::getStyles * @covers ResourceLoaderFileModule::getStyleFiles */ - public function testMixedCssAnnotations( ) { + public function testMixedCssAnnotations() { $basePath = __DIR__ . '/../../data/css'; $testModule = new ResourceLoaderFileModule( array( 'localBasePath' => $basePath, @@ -225,23 +226,4 @@ class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase { $this->assertEquals( $rl->getTemplates(), $expected ); } - - public static function providerGetModifiedTime() { - $modules = self::getModules(); - - return array( - // Check the default value when no templates present in module is 1 - array( $modules['noTemplateModule'], 1 ), - ); - } - - /** - * @dataProvider providerGetModifiedTime - * @covers ResourceLoaderFileModule::getModifiedTime - */ - public function testGetModifiedTime( $module, $expected ) { - $rl = new ResourceLoaderFileModule( $module ); - $ts = $rl->getModifiedTime( $this->getResourceLoaderContext() ); - $this->assertEquals( $ts, $expected ); - } } diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php index 758cfe19..cc121ba3 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php @@ -109,9 +109,6 @@ class ResourceLoaderImageTest extends ResourceLoaderTestCase { class ResourceLoaderImageTestable extends ResourceLoaderImage { // Make some protected methods public - public function getPath( ResourceLoaderContext $context ) { - return parent::getPath( $context ); - } public function massageSvgPathdata( $svg ) { return parent::massageSvgPathdata( $svg ); } diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderModuleTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderModuleTest.php index 6d1ed4e0..41653fb0 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderModuleTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderModuleTest.php @@ -3,10 +3,9 @@ class ResourceLoaderModuleTest extends ResourceLoaderTestCase { /** - * @covers ResourceLoaderModule::getDefinitionSummary - * @covers ResourceLoaderFileModule::getDefinitionSummary + * @covers ResourceLoaderModule::getVersionHash */ - public function testDefinitionSummary() { + public function testGetVersionHash() { $context = $this->getResourceLoaderContext(); $baseParams = array( @@ -16,15 +15,13 @@ class ResourceLoaderModuleTest extends ResourceLoaderTestCase { ); $module = new ResourceLoaderFileModule( $baseParams ); - - $jsonSummary = json_encode( $module->getDefinitionSummary( $context ) ); + $version = json_encode( $module->getVersionHash( $context ) ); // Exactly the same $module = new ResourceLoaderFileModule( $baseParams ); - $this->assertEquals( - $jsonSummary, - json_encode( $module->getDefinitionSummary( $context ) ), + $version, + json_encode( $module->getVersionHash( $context ) ), 'Instance is insignificant' ); @@ -32,10 +29,9 @@ class ResourceLoaderModuleTest extends ResourceLoaderTestCase { $module = new ResourceLoaderFileModule( array( 'dependencies' => array( 'mediawiki', 'jquery' ), ) + $baseParams ); - $this->assertEquals( - $jsonSummary, - json_encode( $module->getDefinitionSummary( $context ) ), + $version, + json_encode( $module->getVersionHash( $context ) ), 'Order of dependencies is insignificant' ); @@ -43,10 +39,9 @@ class ResourceLoaderModuleTest extends ResourceLoaderTestCase { $module = new ResourceLoaderFileModule( array( 'messages' => array( 'world', 'hello' ), ) + $baseParams ); - $this->assertEquals( - $jsonSummary, - json_encode( $module->getDefinitionSummary( $context ) ), + $version, + json_encode( $module->getVersionHash( $context ) ), 'Order of messages is insignificant' ); @@ -54,20 +49,43 @@ class ResourceLoaderModuleTest extends ResourceLoaderTestCase { $module = new ResourceLoaderFileModule( array( 'scripts' => array( 'bar.js', 'foo.js' ), ) + $baseParams ); - $this->assertNotEquals( - $jsonSummary, - json_encode( $module->getDefinitionSummary( $context ) ), + $version, + json_encode( $module->getVersionHash( $context ) ), 'Order of scripts is significant' ); // Subclass $module = new ResourceLoaderFileModuleTestModule( $baseParams ); - $this->assertNotEquals( - $jsonSummary, - json_encode( $module->getDefinitionSummary( $context ) ), + $version, + json_encode( $module->getVersionHash( $context ) ), 'Class is significant' ); } + + /** + * @covers ResourceLoaderModule::validateScriptFile + */ + public function testValidateScriptFile() { + $context = $this->getResourceLoaderContext(); + + $module = new ResourceLoaderTestModule( array( + 'script' => "var a = 'this is';\n {\ninvalid" + ) ); + $this->assertEquals( + $module->getScript( $context ), + 'mw.log.error("JavaScript parse error: Parse error: Unexpected token; token } expected in file \'input\' on line 3");', + 'Replace invalid syntax with error logging' + ); + + $module = new ResourceLoaderTestModule( array( + 'script' => "\n'valid';" + ) ); + $this->assertEquals( + $module->getScript( $context ), + "\n'valid';", + 'Leave valid scripts as-is' + ); + } } diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php index 7f3506cc..cb916142 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php @@ -10,7 +10,8 @@ class ResourceLoaderStartUpModuleTest extends ResourceLoaderTestCase { 'out' => ' mw.loader.addSource( { "local": "/w/load.php" -} );mw.loader.register( [] );' +} ); +mw.loader.register( [] );' ) ), array( array( 'msg' => 'Basic registry', @@ -20,10 +21,11 @@ mw.loader.addSource( { 'out' => ' mw.loader.addSource( { "local": "/w/load.php" -} );mw.loader.register( [ +} ); +mw.loader.register( [ [ "test.blank", - 1388534400 + "wvTifjse" ] ] );', ) ), @@ -37,20 +39,21 @@ mw.loader.addSource( { 'out' => ' mw.loader.addSource( { "local": "/w/load.php" -} );mw.loader.register( [ +} ); +mw.loader.register( [ [ "test.blank", - 1388534400 + "wvTifjse" ], [ "test.group.foo", - 1388534400, + "wvTifjse", [], "x-foo" ], [ "test.group.bar", - 1388534400, + "wvTifjse", [], "x-bar" ] @@ -65,10 +68,11 @@ mw.loader.addSource( { 'out' => ' mw.loader.addSource( { "local": "/w/load.php" -} );mw.loader.register( [ +} ); +mw.loader.register( [ [ "test.blank", - 1388534400 + "wvTifjse" ] ] );' ) ), @@ -87,10 +91,11 @@ mw.loader.addSource( { mw.loader.addSource( { "local": "/w/load.php", "example": "http://example.org/w/load.php" -} );mw.loader.register( [ +} ); +mw.loader.register( [ [ "test.blank", - 1388534400, + "wvTifjse", [], null, "example" @@ -123,14 +128,15 @@ mw.loader.addSource( { 'out' => ' mw.loader.addSource( { "local": "/w/load.php" -} );mw.loader.register( [ +} ); +mw.loader.register( [ [ "test.x.core", - 1388534400 + "wvTifjse" ], [ "test.x.polyfill", - 1388534400, + "wvTifjse", [], null, null, @@ -138,7 +144,7 @@ mw.loader.addSource( { ], [ "test.y.polyfill", - 1388534400, + "wvTifjse", [], null, null, @@ -146,7 +152,7 @@ mw.loader.addSource( { ], [ "test.z.foo", - 1388534400, + "wvTifjse", [ 0, 1, @@ -219,39 +225,40 @@ mw.loader.addSource( { mw.loader.addSource( { "local": "/w/load.php", "example": "http://example.org/w/load.php" -} );mw.loader.register( [ +} ); +mw.loader.register( [ [ "test.blank", - 1388534400 + "wvTifjse" ], [ "test.x.core", - 1388534400 + "wvTifjse" ], [ "test.x.util", - 1388534400, + "wvTifjse", [ 1 ] ], [ "test.x.foo", - 1388534400, + "wvTifjse", [ 1 ] ], [ "test.x.bar", - 1388534400, + "wvTifjse", [ 2 ] ], [ "test.x.quux", - 1388534400, + "wvTifjse", [ 3, 4, @@ -260,25 +267,25 @@ mw.loader.addSource( { ], [ "test.group.foo.1", - 1388534400, + "wvTifjse", [], "x-foo" ], [ "test.group.foo.2", - 1388534400, + "wvTifjse", [], "x-foo" ], [ "test.group.bar.1", - 1388534400, + "wvTifjse", [], "x-bar" ], [ "test.group.bar.2", - 1388534400, + "wvTifjse", [], "x-bar", "example" @@ -342,10 +349,10 @@ mw.loader.addSource( { $rl->register( $modules ); $module = new ResourceLoaderStartUpModule(); $this->assertEquals( -'mw.loader.addSource({"local":"/w/load.php"});' +'mw.loader.addSource({"local":"/w/load.php"});' . "\n" . 'mw.loader.register([' -. '["test.blank",1388534400],' -. '["test.min",1388534400,[0],null,null,' +. '["test.blank","wvTifjse"],' +. '["test.min","wvTifjse",[0],null,null,' . '"return!!(window.JSON\u0026\u0026JSON.parse\u0026\u0026JSON.stringify);"' . ']]);', $module->getModuleRegistrations( $context ), @@ -364,14 +371,15 @@ mw.loader.addSource( { $this->assertEquals( 'mw.loader.addSource( { "local": "/w/load.php" -} );mw.loader.register( [ +} ); +mw.loader.register( [ [ "test.blank", - 1388534400 + "wvTifjse" ], [ "test.min", - 1388534400, + "wvTifjse", [ 0 ], diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php index ca7307ec..b6838859 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php @@ -6,17 +6,8 @@ class ResourceLoaderTest extends ResourceLoaderTestCase { parent::setUp(); $this->setMwGlobals( array( - 'wgResourceLoaderLESSFunctions' => array( - 'test-sum' => function ( $frame, $less ) { - $sum = 0; - foreach ( $frame[2] as $arg ) { - $sum += (int)$arg[1]; - } - return $sum; - }, - ), 'wgResourceLoaderLESSImportPaths' => array( - dirname( dirname( __DIR__ ) ) . '/data/less/common', + dirname( dirname( __DIR__ ) ) . '/data/less/common', ), 'wgResourceLoaderLESSVars' => array( 'foo' => '2px', diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderWikiModuleTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderWikiModuleTest.php index 93a3ebba..8cefec75 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderWikiModuleTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderWikiModuleTest.php @@ -31,16 +31,15 @@ class ResourceLoaderWikiModuleTest extends ResourceLoaderTestCase { $module = new ResourceLoaderWikiModule( $params ); $module->setConfig( $config ); - // Use getDefinitionSummary because getPages is protected - $summary = $module->getDefinitionSummary( ResourceLoaderContext::newDummyContext() ); - $this->assertEquals( - $expected, - $summary['pages'] - ); + // Because getPages is protected.. + $getPages = new ReflectionMethod( $module, 'getPages' ); + $getPages->setAccessible( true ); + $out = $getPages->invoke( $module, ResourceLoaderContext::newDummyContext() ); + $this->assertEquals( $expected, $out ); } public static function provideGetPages() { - $settings = array( + $settings = self::getSettings() + array( 'UseSiteJs' => true, 'UseSiteCss' => true, ); @@ -110,39 +109,27 @@ class ResourceLoaderWikiModuleTest extends ResourceLoaderTestCase { array( array(), 'test1', true ), // 'site' module with a non-empty page array( - array( - 'MediaWiki:Common.js' => array( - 'timestamp' => 123456789, - 'length' => 1234 - ) - ), 'site', false, + array( 'MediaWiki:Common.js' => array( 'rev_sha1' => 'dmh6qn', 'rev_len' => 1234 ) ), + 'site', + false, ), // 'site' module with an empty page array( - array( - 'MediaWiki:Monobook.js' => array( - 'timestamp' => 987654321, - 'length' => 0, - ), - ), 'site', false, + array( 'MediaWiki:Foo.js' => array( 'rev_sha1' => 'phoi', 'rev_len' => 0 ) ), + 'site', + false, ), // 'user' module with a non-empty page array( - array( - 'User:FooBar/common.js' => array( - 'timestamp' => 246813579, - 'length' => 25, - ), - ), 'user', false, + array( 'User:Example/common.js' => array( 'rev_sha1' => 'j7ssba', 'rev_len' => 25 ) ), + 'user', + false, ), // 'user' module with an empty page array( - array( - 'User:FooBar/monobook.js' => array( - 'timestamp' => 1357924680, - 'length' => 0, - ), - ), 'user', true, + array( 'User:Example/foo.js' => array( 'rev_sha1' => 'phoi', 'rev_len' => 0 ) ), + 'user', + true, ), ); } diff --git a/tests/phpunit/includes/site/CachingSiteStoreTest.php b/tests/phpunit/includes/site/CachingSiteStoreTest.php index d0a79803..4305ceb9 100644 --- a/tests/phpunit/includes/site/CachingSiteStoreTest.php +++ b/tests/phpunit/includes/site/CachingSiteStoreTest.php @@ -96,17 +96,17 @@ class CachingSiteStoreTest extends MediaWikiTestCase { ->getMock(); // php 5.3 compatibility! - $self = $this; + $that = $this; $dbSiteStore->expects( $this->any() ) ->method( 'getSite' ) - ->will( $this->returnValue( $self->getTestSite() ) ); + ->will( $this->returnValue( $that->getTestSite() ) ); $dbSiteStore->expects( $this->any() ) ->method( 'getSites' ) - ->will( $this->returnCallback( function() use( $self ) { + ->will( $this->returnCallback( function() use ( $that ) { $siteList = new SiteList(); - $siteList->setSite( $self->getTestSite() ); + $siteList->setSite( $that->getTestSite() ); return $siteList; } ) ); diff --git a/tests/phpunit/includes/site/DBSiteStoreTest.php b/tests/phpunit/includes/site/DBSiteStoreTest.php index 673ba54d..48ef5243 100644 --- a/tests/phpunit/includes/site/DBSiteStoreTest.php +++ b/tests/phpunit/includes/site/DBSiteStoreTest.php @@ -130,4 +130,28 @@ class DBSiteStoreTest extends MediaWikiTestCase { $sites = $store->getSites(); $this->assertEquals( 0, $sites->count() ); } + + /** + * @covers DBSiteStore::getSites + */ + public function testGetSitesDefaultOrder() { + $store = new DBSiteStore(); + $siteB = new Site(); + $siteB->setGlobalId( 'B' ); + $siteA = new Site(); + $siteA->setGlobalId( 'A' ); + $store->saveSites( array( $siteB, $siteA ) ); + + $sites = $store->getSites(); + $siteIdentifiers = array(); + /** @var Site $site */ + foreach ( $sites as $site ) { + $siteIdentifiers[] = $site->getGlobalId(); + } + $this->assertSame( array( 'A', 'B' ), $siteIdentifiers ); + + // Note: SiteList::getGlobalIdentifiers uses an other internal state. Iteration must be + // tested separately. + $this->assertSame( array( 'A', 'B' ), $sites->getGlobalIdentifiers() ); + } } diff --git a/tests/phpunit/includes/site/HashSiteStoreTest.php b/tests/phpunit/includes/site/HashSiteStoreTest.php index 49a96338..bebc0936 100644 --- a/tests/phpunit/includes/site/HashSiteStoreTest.php +++ b/tests/phpunit/includes/site/HashSiteStoreTest.php @@ -32,7 +32,7 @@ class HashSiteStoreTest extends MediaWikiTestCase { public function testGetSites() { $expectedSites = array(); - foreach( TestSites::getSites() as $testSite ) { + foreach ( TestSites::getSites() as $testSite ) { $siteId = $testSite->getGlobalId(); $expectedSites[$siteId] = $testSite; } diff --git a/tests/phpunit/includes/site/SiteExporterTest.php b/tests/phpunit/includes/site/SiteExporterTest.php index 19dd0aa1..7be19ef9 100644 --- a/tests/phpunit/includes/site/SiteExporterTest.php +++ b/tests/phpunit/includes/site/SiteExporterTest.php @@ -53,7 +53,7 @@ class SiteExporterTest extends PHPUnit_Framework_TestCase { $exporter->exportSites( array( $foo, $acme ) ); fseek( $tmp, 0 ); - $xml = fread( $tmp, 16*1024 ); + $xml = fread( $tmp, 16 * 1024 ); $this->assertContains( '<sites ', $xml ); $this->assertContains( '<site>', $xml ); @@ -133,7 +133,7 @@ class SiteExporterTest extends PHPUnit_Framework_TestCase { $exporter->exportSites( $sites ); fseek( $tmp, 0 ); - $xml = fread( $tmp, 16*1024 ); + $xml = fread( $tmp, 16 * 1024 ); $actualSites = new SiteList(); $store = $this->newSiteStore( $actualSites ); diff --git a/tests/phpunit/includes/site/SiteImporterTest.php b/tests/phpunit/includes/site/SiteImporterTest.php index cb0316ab..b11b1a9f 100644 --- a/tests/phpunit/includes/site/SiteImporterTest.php +++ b/tests/phpunit/includes/site/SiteImporterTest.php @@ -34,11 +34,11 @@ class SiteImporterTest extends PHPUnit_Framework_TestCase { private function newSiteImporter( array $expectedSites, $errorCount ) { $store = $this->getMock( 'SiteStore' ); - $self = $this; + $that = $this; $store->expects( $this->once() ) ->method( 'saveSites' ) - ->will( $this->returnCallback( function ( $sites ) use ( $expectedSites, $self ) { - $self->assertSitesEqual( $expectedSites, $sites ); + ->will( $this->returnCallback( function ( $sites ) use ( $expectedSites, $that ) { + $that->assertSitesEqual( $expectedSites, $sites ); } ) ); $store->expects( $this->any() ) @@ -141,12 +141,12 @@ class SiteImporterTest extends PHPUnit_Framework_TestCase { /** * @dataProvider provideImportFromXML */ - public function testImportFromXML( $xml, array $expectedSites, $errorCount = 0 ) { + public function testImportFromXML( $xml, array $expectedSites, $errorCount = 0 ) { $importer = $this->newSiteImporter( $expectedSites, $errorCount ); $importer->importFromXML( $xml ); } - public function testImportFromXML_malformed() { + public function testImportFromXML_malformed() { $this->setExpectedException( 'Exception' ); $store = $this->getMock( 'SiteStore' ); @@ -154,7 +154,7 @@ class SiteImporterTest extends PHPUnit_Framework_TestCase { $importer->importFromXML( 'THIS IS NOT XML' ); } - public function testImportFromFile() { + public function testImportFromFile() { $foo = Site::newForType( Site::TYPE_UNKNOWN ); $foo->setGlobalId( 'Foo' ); diff --git a/tests/phpunit/includes/specials/SpecialBlankPageTest.php b/tests/phpunit/includes/specials/SpecialBlankPageTest.php new file mode 100644 index 00000000..1d4f5e51 --- /dev/null +++ b/tests/phpunit/includes/specials/SpecialBlankPageTest.php @@ -0,0 +1,25 @@ +<?php + +/** + * @licence GNU GPL v2+ + * @author Adam Shorland + * + * @covers SpecialBlankpage + */ +class SpecialBlankPageTest extends SpecialPageTestBase { + + /** + * Returns a new instance of the special page under test. + * + * @return SpecialPage + */ + protected function newSpecialPage() { + return new SpecialBlankpage(); + } + + public function testHasWikiMsg() { + list( $html, ) = $this->executeSpecialPage(); + $this->assertContains( wfMessage( 'intentionallyblankpage' )->text(), $html ); + } + +} diff --git a/tests/phpunit/includes/specials/SpecialPageTestBase.php b/tests/phpunit/includes/specials/SpecialPageTestBase.php new file mode 100644 index 00000000..9c7b0f00 --- /dev/null +++ b/tests/phpunit/includes/specials/SpecialPageTestBase.php @@ -0,0 +1,165 @@ +<?php + +/** + * Base class for testing special pages. + * + * @since 1.26 + * + * @licence GNU GPL v2+ + * @author Jeroen De Dauw < jeroendedauw@gmail.com > + * @author Daniel Kinzler + * @author Adam Shorland + * @author Thiemo Mättig + */ +abstract class SpecialPageTestBase extends MediaWikiTestCase { + + private $obLevel; + + protected function setUp() { + parent::setUp(); + + $this->obLevel = ob_get_level(); + } + + protected function tearDown() { + $obLevel = ob_get_level(); + + while ( ob_get_level() > $this->obLevel ) { + ob_end_clean(); + } + + if ( $obLevel !== $this->obLevel ) { + $this->fail( + "Test changed output buffer level: was {$this->obLevel} before test, but $obLevel after test." + ); + } + + parent::tearDown(); + } + + /** + * Returns a new instance of the special page under test. + * + * @return SpecialPage + */ + abstract protected function newSpecialPage(); + + /** + * @param string $subPage The subpage parameter to call the page with + * @param WebRequest|null $request Web request that may contain URL parameters, etc + * @param Language|string|null $language The language which should be used in the context + * @param User|null $user The user which should be used in the context of this special page + * + * @throws Exception + * @return array( string, WebResponse ) A two-elements array containing the HTML output + * generated by the special page as well as the response object. + */ + protected function executeSpecialPage( + $subPage = '', + WebRequest $request = null, + $language = null, + User $user = null + ) { + $context = $this->newContext( $request, $language, $user ); + + $output = new OutputPage( $context ); + $context->setOutput( $output ); + + $page = $this->newSpecialPage(); + $page->setContext( $context ); + $output->setTitle( $page->getPageTitle() ); + + $html = $this->getHTMLFromSpecialPage( $page, $subPage ); + $response = $context->getRequest()->response(); + + if ( $response instanceof FauxResponse ) { + $code = $response->getStatusCode(); + + if ( $code > 0 ) { + $response->header( 'Status: ' . $code . ' ' . HttpStatus::getMessage( $code ) ); + } + } + + return array( $html, $response ); + } + + /** + * @param WebRequest|null $request + * @param Language|string|null $language + * @param User|null $user + * + * @return DerivativeContext + */ + private function newContext( + WebRequest $request = null, + $language = null, + User $user = null + ) { + $context = new DerivativeContext( RequestContext::getMain() ); + + $context->setRequest( $request ?: new FauxRequest() ); + + if ( $language !== null ) { + $context->setLanguage( $language ); + } + + if ( $user !== null ) { + $context->setUser( $user ); + } + + $this->setEditTokenFromUser( $context ); + + return $context; + } + + /** + * If we are trying to edit and no token is set, supply one. + * + * @param DerivativeContext $context + */ + private function setEditTokenFromUser( DerivativeContext $context ) { + $request = $context->getRequest(); + + // Edits via GET are a security issue and should not succeed. On the other hand, not all + // POST requests are edits, but should ignore unused parameters. + if ( !$request->getCheck( 'wpEditToken' ) && $request->wasPosted() ) { + $request->setVal( 'wpEditToken', $context->getUser()->getEditToken() ); + } + } + + /** + * @param SpecialPage $page + * @param string $subPage + * + * @throws Exception + * @return string HTML + */ + private function getHTMLFromSpecialPage( SpecialPage $page, $subPage ) { + ob_start(); + + try { + $page->execute( $subPage ); + + $output = $page->getOutput(); + + if ( $output->getRedirect() !== '' ) { + $output->output(); + $html = ob_get_contents(); + } elseif ( $output->isDisabled() ) { + $html = ob_get_contents(); + } else { + $html = $output->getHTML(); + } + } catch ( Exception $ex ) { + ob_end_clean(); + + // Re-throw exception after "finally" handling because PHP 5.3 doesn't have "finally". + throw $ex; + } + + ob_end_clean(); + + return $html; + } + +} diff --git a/tests/phpunit/includes/specials/SpecialPreferencesTest.php b/tests/phpunit/includes/specials/SpecialPreferencesTest.php index 4f6c4116..1545d7ec 100644 --- a/tests/phpunit/includes/specials/SpecialPreferencesTest.php +++ b/tests/phpunit/includes/specials/SpecialPreferencesTest.php @@ -4,10 +4,11 @@ * * Copyright © 2013, Antoine Musso * Copyright © 2013, Wikimedia Foundation Inc. - * */ /** + * @group Database + * * @covers SpecialPreferences */ class SpecialPreferencesTest extends MediaWikiTestCase { diff --git a/tests/phpunit/includes/specials/SpecialSearchTest.php b/tests/phpunit/includes/specials/SpecialSearchTest.php index 83489c65..13c28381 100644 --- a/tests/phpunit/includes/specials/SpecialSearchTest.php +++ b/tests/phpunit/includes/specials/SpecialSearchTest.php @@ -136,9 +136,113 @@ class SpecialSearchTest extends MediaWikiTestCase { # Compare :-] $this->assertRegExp( - '/' . preg_quote( $term ) . '/', + '/' . preg_quote( $term, '/' ) . '/', $pageTitle, "Search term '{$term}' should not be expanded in Special:Search <title>" ); } + + public function provideRewriteQueryWithSuggestion() { + return array( + array( + 'With suggestion and no rewritten query shows did you mean', + '/Did you mean: <a[^>]+>first suggestion/', + new SpecialSearchTestMockResultSet( 'first suggestion', null, array( + SearchResult::newFromTitle( Title::newMainPage() ), + ) ), + ), + + array( + 'With rewritten query informs user of change', + '/Showing results for <a[^>]+>first suggestion/', + new SpecialSearchTestMockResultSet( 'asdf', 'first suggestion', array( + SearchResult::newFromTitle( Title::newMainPage() ), + ) ), + ), + + array( + 'When both queries have no results user gets no results', + '/There were no results matching the query/', + new SpecialSearchTestMockResultSet( 'first suggestion', 'first suggestion', array() ), + ), + ); + } + + /** + * @dataProvider provideRewriteQueryWithSuggestion + */ + public function testRewriteQueryWithSuggestion( $message, $expectRegex, $results ) { + $mockSearchEngine = $this->mockSearchEngine( $results ); + $search = $this->getMockBuilder( 'SpecialSearch' ) + ->setMethods( array( 'getSearchEngine' ) ) + ->getMock(); + $search->expects( $this->any() ) + ->method( 'getSearchEngine' ) + ->will( $this->returnValue( $mockSearchEngine ) ); + + $search->getContext()->setTitle( Title::makeTitle( NS_SPECIAL, 'Search' ) ); + $search->load(); + $search->showResults( 'this is a fake search' ); + + $html = $search->getContext()->getOutput()->getHTML(); + foreach ( (array)$expectRegex as $regex ) { + $this->assertRegExp( $regex, $html, $message ); + } + } + + protected function mockSearchEngine( $results ) { + $mock = $this->getMockBuilder( 'SearchEngine' ) + ->setMethods( array( 'searchText', 'searchTitle' ) ) + ->getMock(); + + $mock->expects( $this->any() ) + ->method( 'searchText' ) + ->will( $this->returnValue( $results ) ); + + return $mock; + } +} + +class SpecialSearchTestMockResultSet extends SearchResultSet { + protected $results; + protected $suggestion; + + public function __construct( $suggestion = null, $rewrittenQuery = null, array $results = array(), $containedSyntax = false) { + $this->suggestion = $suggestion; + $this->rewrittenQuery = $rewrittenQuery; + $this->results = $results; + $this->containedSyntax = $containedSyntax; + } + + public function numRows() { + return count( $this->results ); + } + + public function getTotalHits() { + return $this->numRows(); + } + + public function hasSuggestion() { + return $this->suggestion !== null; + } + + public function getSuggestionQuery() { + return $this->suggestion; + } + + public function getSuggestionSnippet() { + return $this->suggestion; + } + + public function hasRewrittenQuery() { + return $this->rewrittenQuery !== null; + } + + public function getQueryAfterRewrite() { + return $this->rewrittenQuery; + } + + public function getQueryAfterRewriteSnippet() { + return htmlspecialchars( $this->rewrittenQuery ); + } } diff --git a/tests/phpunit/includes/title/ForeignTitleTest.php b/tests/phpunit/includes/title/ForeignTitleTest.php index 599d2a33..10b7e28f 100644 --- a/tests/phpunit/includes/title/ForeignTitleTest.php +++ b/tests/phpunit/includes/title/ForeignTitleTest.php @@ -59,7 +59,7 @@ class ForeignTitleTest extends MediaWikiTestCase { $this->assertEquals( $expectedText, $title->getText() ); } - public function testUnknownNamespaceCheck( ) { + public function testUnknownNamespaceCheck() { $title = new ForeignTitle( null, 'this', 'that' ); $this->assertEquals( false, $title->isNamespaceIdKnown() ); @@ -67,7 +67,7 @@ class ForeignTitleTest extends MediaWikiTestCase { $this->assertEquals( 'that', $title->getText() ); } - public function testUnknownNamespaceError( ) { + public function testUnknownNamespaceError() { $this->setExpectedException( 'MWException' ); $title = new ForeignTitle( null, 'this', 'that' ); $title->getNamespaceId(); diff --git a/tests/phpunit/includes/title/MediaWikiPageLinkRendererTest.php b/tests/phpunit/includes/title/MediaWikiPageLinkRendererTest.php index cd0d0b1c..1e5f9d01 100644 --- a/tests/phpunit/includes/title/MediaWikiPageLinkRendererTest.php +++ b/tests/phpunit/includes/title/MediaWikiPageLinkRendererTest.php @@ -158,7 +158,7 @@ class MediaWikiPageLinkRendererTest extends MediaWikiTestCase { function ( TitleValue $title ) { return str_replace( '_', ' ', "$title" ); } - )); + ) ); $renderer = new MediaWikiPageLinkRenderer( $formatter, '/' ); $actual = $renderer->renderWikitextLink( $title, $text ); diff --git a/tests/phpunit/includes/upload/UploadStashTest.php b/tests/phpunit/includes/upload/UploadStashTest.php index d5d1188e..8f9eabe9 100644 --- a/tests/phpunit/includes/upload/UploadStashTest.php +++ b/tests/phpunit/includes/upload/UploadStashTest.php @@ -20,7 +20,7 @@ class UploadStashTest extends MediaWikiTestCase { parent::setUp(); // Setup a file for bug 29408 - $this->bug29408File = __DIR__ . '/bug29408'; + $this->bug29408File = wfTempDir() . '/bug29408'; file_put_contents( $this->bug29408File, "\x00" ); self::$users = array( diff --git a/tests/phpunit/includes/utils/AvroValidatorTest.php b/tests/phpunit/includes/utils/AvroValidatorTest.php new file mode 100644 index 00000000..52c242c1 --- /dev/null +++ b/tests/phpunit/includes/utils/AvroValidatorTest.php @@ -0,0 +1,96 @@ +<?php +/** + * Tests for IP validity functions. + * + * Ported from /t/inc/IP.t by avar. + * + * @group IP + * @todo Test methods in this call should be split into a method and a + * dataprovider. + */ + +class AvroValidatorTest extends PHPUnit_Framework_TestCase { + public function setUp() { + if ( !class_exists( 'AvroSchema' ) ) { + $this->markTestSkipped( 'Avro is required to run the AvroValidatorTest' ); + } + parent::setUp(); + } + + public function getErrorsProvider() { + $stringSchema = AvroSchema::parse( json_encode( array( 'type' => 'string' ) ) ); + $recordSchema = AvroSchema::parse( json_encode( array( + 'type' => 'record', + 'name' => 'ut', + 'fields' => array( + array( 'name' => 'id', 'type' => 'int', 'required' => true ), + ), + ) ) ); + $enumSchema = AvroSchema::parse( json_encode( array( + 'type' => 'record', + 'name' => 'ut', + 'fields' => array( + array( 'name' => 'count', 'type' => array( 'int', 'null' ) ), + ), + ) ) ); + + return array( + array( + 'No errors with a simple string serialization', + $stringSchema, 'foobar', array(), + ), + + array( + 'Cannot serialize integer into string', + $stringSchema, 5, 'Expected string, but recieved integer', + ), + + array( + 'Cannot serialize array into string', + $stringSchema, array(), 'Expected string, but recieved array', + ), + + array( + 'allows and ignores extra fields', + $recordSchema, array( 'id' => 4, 'foo' => 'bar' ), array(), + ), + + array( + 'detects missing fields', + $recordSchema, array(), array( 'id' => 'Missing expected field' ), + ), + + array( + 'handles first element in enum', + $enumSchema, array( 'count' => 4 ), array(), + ), + + array( + 'handles second element in enum', + $enumSchema, array( 'count' => null ), array(), + ), + + array( + 'rejects element not in union', + $enumSchema, array( 'count' => 'invalid' ), array( 'count' => array( + 'Expected any one of these to be true', + array( + 'Expected integer, but recieved string', + 'Expected null, but recieved string', + ) + ) ) + ), + ); + } + + /** + * @dataProvider getErrorsProvider + */ + public function testGetErrors( $message, $schema, $datum, $expected ) { + $this->assertEquals( + $expected, + AvroValidator::getErrors( $schema, $datum ), + $message + ); + } +} diff --git a/tests/phpunit/includes/utils/BatchRowUpdateTest.php b/tests/phpunit/includes/utils/BatchRowUpdateTest.php new file mode 100644 index 00000000..a2b35f39 --- /dev/null +++ b/tests/phpunit/includes/utils/BatchRowUpdateTest.php @@ -0,0 +1,243 @@ +<?php + +/** + * Tests for BatchRowUpdate and its components + * + * @group db + */ +class BatchRowUpdateTest extends MediaWikiTestCase { + + public function testWriterBasicFunctionality() { + $db = $this->mockDb(); + $writer = new BatchRowWriter( $db, 'echo_event' ); + + $updates = array( + self::mockUpdate( array( 'something' => 'changed' ) ), + self::mockUpdate( array( 'otherthing' => 'changed' ) ), + self::mockUpdate( array( 'and' => 'something', 'else' => 'changed' ) ), + ); + + $db->expects( $this->exactly( count( $updates ) ) ) + ->method( 'update' ); + + $writer->write( $updates ); + } + + static protected function mockUpdate( array $changes ) { + static $i = 0; + return array( + 'primaryKey' => array( 'event_id' => $i++ ), + 'changes' => $changes, + ); + } + + public function testReaderBasicIterate() { + $db = $this->mockDb(); + $batchSize = 2; + $reader = new BatchRowIterator( $db, 'some_table', 'id_field', $batchSize ); + + $response = $this->genSelectResult( $batchSize, /*numRows*/ 5, function() { + static $i = 0; + return array( 'id_field' => ++$i ); + } ); + $db->expects( $this->exactly( count( $response ) ) ) + ->method( 'select' ) + ->will( $this->consecutivelyReturnFromSelect( $response ) ); + + $pos = 0; + foreach ( $reader as $rows ) { + $this->assertEquals( $response[$pos], $rows, "Testing row in position $pos" ); + $pos++; + } + // -1 is because the final array() marks the end and isnt included + $this->assertEquals( count( $response ) - 1, $pos ); + } + + static public function provider_readerGetPrimaryKey() { + $row = array( + 'id_field' => 42, + 'some_col' => 'dvorak', + 'other_col' => 'samurai', + ); + return array( + + array( + 'Must return single column pk when requested', + array( 'id_field' => 42 ), + $row + ), + + array( + 'Must return multiple column pks when requested', + array( 'id_field' => 42, 'other_col' => 'samurai' ), + $row + ), + + ); + } + + /** + * @dataProvider provider_readerGetPrimaryKey + */ + public function testReaderGetPrimaryKey( $message, array $expected, array $row ) { + $reader = new BatchRowIterator( $this->mockDb(), 'some_table', array_keys( $expected ), 8675309 ); + $this->assertEquals( $expected, $reader->extractPrimaryKeys( (object) $row ), $message ); + } + + static public function provider_readerSetFetchColumns() { + return array( + + array( + 'Must merge primary keys into select conditions', + // Expected column select + array( 'foo', 'bar' ), + // primary keys + array( 'foo' ), + // setFetchColumn + array( 'bar' ) + ), + + array( + 'Must not merge primary keys into the all columns selector', + // Expected column select + array( '*' ), + // primary keys + array( 'foo' ), + // setFetchColumn + array( '*' ), + ), + + array( + 'Must not duplicate primary keys into column selector', + // Expected column select. + // TODO: figure out how to only assert the array_values portion and not the keys + array( 0 => 'foo', 1 => 'bar', 3 => 'baz' ), + // primary keys + array( 'foo', 'bar', ), + // setFetchColumn + array( 'bar', 'baz' ), + ), + ); + } + + /** + * @dataProvider provider_readerSetFetchColumns + */ + public function testReaderSetFetchColumns( $message, array $columns, array $primaryKeys, array $fetchColumns ) { + $db = $this->mockDb(); + $db->expects( $this->once() ) + ->method( 'select' ) + ->with( 'some_table', $columns ) // only testing second parameter of DatabaseBase::select + ->will( $this->returnValue( new ArrayIterator( array() ) ) ); + + $reader = new BatchRowIterator( $db, 'some_table', $primaryKeys, 22 ); + $reader->setFetchColumns( $fetchColumns ); + // triggers first database select + $reader->rewind(); + } + + static public function provider_readerSelectConditions() { + return array( + + array( + "With single primary key must generate id > 'value'", + // Expected second iteration + array( "( id_field > '3' )" ), + // Primary key(s) + 'id_field', + ), + + array( + 'With multiple primary keys the first conditions must use >= and the final condition must use >', + // Expected second iteration + array( "( id_field = '3' AND foo > '103' ) OR ( id_field > '3' )" ), + // Primary key(s) + array( 'id_field', 'foo' ), + ), + + ); + } + + /** + * Slightly hackish to use reflection, but asserting different parameters + * to consecutive calls of DatabaseBase::select in phpunit is error prone + * + * @dataProvider provider_readerSelectConditions + */ + public function testReaderSelectConditionsMultiplePrimaryKeys( $message, $expectedSecondIteration, $primaryKeys, $batchSize = 3 ) { + $results = $this->genSelectResult( $batchSize, $batchSize * 3, function() { + static $i = 0, $j = 100, $k = 1000; + return array( 'id_field' => ++$i, 'foo' => ++$j, 'bar' => ++$k ); + } ); + $db = $this->mockDbConsecutiveSelect( $results ); + + $conditions = array( 'bar' => 42, 'baz' => 'hai' ); + $reader = new BatchRowIterator( $db, 'some_table', $primaryKeys, $batchSize ); + $reader->addConditions( $conditions ); + + $buildConditions = new ReflectionMethod( $reader, 'buildConditions' ); + $buildConditions->setAccessible( true ); + + // On first iteration only the passed conditions must be used + $this->assertEquals( $conditions, $buildConditions->invoke( $reader ), + 'First iteration must return only the conditions passed in addConditions' ); + $reader->rewind(); + + // Second iteration must use the maximum primary key of last set + $this->assertEquals( + $conditions + $expectedSecondIteration, + $buildConditions->invoke( $reader ), + $message + ); + } + + protected function mockDbConsecutiveSelect( array $retvals ) { + $db = $this->mockDb(); + $db->expects( $this->any() ) + ->method( 'select' ) + ->will( $this->consecutivelyReturnFromSelect( $retvals ) ); + $db->expects( $this->any() ) + ->method( 'addQuotes' ) + ->will( $this->returnCallback( function( $value ) { + return "'$value'"; // not real quoting: doesn't matter in test + } ) ); + + return $db; + } + + protected function consecutivelyReturnFromSelect( array $results ) { + $retvals = array(); + foreach ( $results as $rows ) { + // The DatabaseBase::select method returns iterators, so we do too. + $retvals[] = $this->returnValue( new ArrayIterator( $rows ) ); + } + + return call_user_func_array( array( $this, 'onConsecutiveCalls' ), $retvals ); + } + + + protected function genSelectResult( $batchSize, $numRows, $rowGenerator ) { + $res = array(); + for ( $i = 0; $i < $numRows; $i += $batchSize ) { + $rows = array(); + for ( $j = 0; $j < $batchSize && $i + $j < $numRows; $j++ ) { + $rows [] = (object) call_user_func( $rowGenerator ); + } + $res[] = $rows; + } + $res[] = array(); // termination condition requires empty result for last row + return $res; + } + + protected function mockDb() { + // Cant mock from DatabaseType or DatabaseBase, they dont + // have the full gamut of methods + $databaseMysql = $this->getMockBuilder( 'DatabaseMysql' ) + ->disableOriginalConstructor() + ->getMock(); + $databaseMysql->expects( $this->any() ) + ->method( 'isOpen' ) + ->will( $this->returnValue( true ) ); + return $databaseMysql; + } +} diff --git a/tests/phpunit/includes/utils/IPTest.php b/tests/phpunit/includes/utils/IPTest.php index 886ee908..34aff796 100644 --- a/tests/phpunit/includes/utils/IPTest.php +++ b/tests/phpunit/includes/utils/IPTest.php @@ -11,29 +11,37 @@ class IPTest extends PHPUnit_Framework_TestCase { /** - * not sure it should be tested with boolean false. hashar 20100924 * @covers IP::isIPAddress + * @dataProvider provideInvalidIPs */ - public function testisIPAddress() { - $this->assertFalse( IP::isIPAddress( false ), 'Boolean false is not an IP' ); - $this->assertFalse( IP::isIPAddress( true ), 'Boolean true is not an IP' ); - $this->assertFalse( IP::isIPAddress( "" ), 'Empty string is not an IP' ); - $this->assertFalse( IP::isIPAddress( 'abc' ), 'Garbage IP string' ); - $this->assertFalse( IP::isIPAddress( ':' ), 'Single ":" is not an IP' ); - $this->assertFalse( IP::isIPAddress( '2001:0DB8::A:1::1' ), 'IPv6 with a double :: occurrence' ); - $this->assertFalse( - IP::isIPAddress( '2001:0DB8::A:1::' ), - 'IPv6 with a double :: occurrence, last at end' - ); - $this->assertFalse( - IP::isIPAddress( '::2001:0DB8::5:1' ), - 'IPv6 with a double :: occurrence, firt at beginning' + public function isNotIPAddress( $val, $desc ) { + $this->assertFalse( IP::isIPAddress( $val ), $desc ); + } + + /** + * Provide a list of things that aren't IP addresses + */ + public function provideInvalidIPs() { + return array( + array( false, 'Boolean false is not an IP' ), + array( true, 'Boolean true is not an IP' ), + array( '', 'Empty string is not an IP' ), + array( 'abc', 'Garbage IP string' ), + array( ':', 'Single ":" is not an IP' ), + array( '2001:0DB8::A:1::1', 'IPv6 with a double :: occurrence' ), + array( '2001:0DB8::A:1::', 'IPv6 with a double :: occurrence, last at end' ), + array( '::2001:0DB8::5:1', 'IPv6 with a double :: occurrence, firt at beginning' ), + array( '124.24.52', 'IPv4 not enough quads' ), + array( '24.324.52.13', 'IPv4 out of range' ), + array( '.24.52.13', 'IPv4 starts with period' ), + array( 'fc:100:300', 'IPv6 with only 3 words' ), ); - $this->assertFalse( IP::isIPAddress( '124.24.52' ), 'IPv4 not enough quads' ); - $this->assertFalse( IP::isIPAddress( '24.324.52.13' ), 'IPv4 out of range' ); - $this->assertFalse( IP::isIPAddress( '.24.52.13' ), 'IPv4 starts with period' ); - $this->assertFalse( IP::isIPAddress( 'fc:100:300' ), 'IPv6 with only 3 words' ); + } + /** + * @covers IP::isIPAddress + */ + public function testisIPAddress() { $this->assertTrue( IP::isIPAddress( '::' ), 'RFC 4291 IPv6 Unspecified Address' ); $this->assertTrue( IP::isIPAddress( '::1' ), 'RFC 4291 IPv6 Loopback Address' ); $this->assertTrue( IP::isIPAddress( '74.24.52.13/20', 'IPv4 range' ) ); @@ -107,20 +115,42 @@ class IPTest extends PHPUnit_Framework_TestCase { /** * @covers IP::isIPv4 + * @dataProvider provideInvalidIPv4Addresses + */ + public function testisNotIPv4( $bogusIP, $desc ) { + $this->assertFalse( IP::isIPv4( $bogusIP ), $desc ); + } + + public function provideInvalidIPv4Addresses() { + return array( + array( false, 'Boolean false is not an IP' ), + array( true, 'Boolean true is not an IP' ), + array( '', 'Empty string is not an IP' ), + array( 'abc', 'Letters are not an IP' ), + array( ':', 'A colon is not an IP' ), + array( '124.24.52', 'IPv4 not enough quads' ), + array( '24.324.52.13', 'IPv4 out of range' ), + array( '.24.52.13', 'IPv4 starts with period' ), + ); + } + + /** + * @covers IP::isIPv4 + * @dataProvider provideValidIPv4Address */ - public function testisIPv4() { - $this->assertFalse( IP::isIPv4( false ), 'Boolean false is not an IP' ); - $this->assertFalse( IP::isIPv4( true ), 'Boolean true is not an IP' ); - $this->assertFalse( IP::isIPv4( "" ), 'Empty string is not an IP' ); - $this->assertFalse( IP::isIPv4( 'abc' ) ); - $this->assertFalse( IP::isIPv4( ':' ) ); - $this->assertFalse( IP::isIPv4( '124.24.52' ), 'IPv4 not enough quads' ); - $this->assertFalse( IP::isIPv4( '24.324.52.13' ), 'IPv4 out of range' ); - $this->assertFalse( IP::isIPv4( '.24.52.13' ), 'IPv4 starts with period' ); + public function testIsIPv4( $ip, $desc ) { + $this->assertTrue( IP::isIPv4( $ip ), $desc ); + } - $this->assertTrue( IP::isIPv4( '124.24.52.13' ) ); - $this->assertTrue( IP::isIPv4( '1.24.52.13' ) ); - $this->assertTrue( IP::isIPv4( '74.24.52.13/20', 'IPv4 range' ) ); + /** + * Provide some IPv4 addresses and ranges + */ + public function provideValidIPv4Address() { + return array( + array( '124.24.52.13', 'Valid IPv4 address' ), + array( '1.24.52.13', 'Another valid IPv4 address' ), + array( '74.24.52.13/20', 'An IPv4 range' ), + ); } /** @@ -224,49 +254,56 @@ class IPTest extends PHPUnit_Framework_TestCase { } /** - * @covers IP::isValidBlock + * Provide some valid IP blocks */ - public function testValidBlocks() { - $valid = array( - '116.17.184.5/32', - '0.17.184.5/30', - '16.17.184.1/24', - '30.242.52.14/1', - '10.232.52.13/8', - '30.242.52.14/0', - '::e:f:2001/96', - '::c:f:2001/128', - '::10:f:2001/70', - '::fe:f:2001/1', - '::6d:f:2001/8', - '::fe:f:2001/0', + public function provideValidBlocks() { + return array( + array( '116.17.184.5/32' ), + array( '0.17.184.5/30' ), + array( '16.17.184.1/24' ), + array( '30.242.52.14/1' ), + array( '10.232.52.13/8' ), + array( '30.242.52.14/0' ), + array( '::e:f:2001/96' ), + array( '::c:f:2001/128' ), + array( '::10:f:2001/70' ), + array( '::fe:f:2001/1' ), + array( '::6d:f:2001/8' ), + array( '::fe:f:2001/0' ), ); - foreach ( $valid as $i ) { - $this->assertTrue( IP::isValidBlock( $i ), "$i is a valid IP block" ); - } } /** * @covers IP::isValidBlock + * @dataProvider provideValidBlocks */ - public function testInvalidBlocks() { - $invalid = array( - '116.17.184.5/33', - '0.17.184.5/130', - '16.17.184.1/-1', - '10.232.52.13/*', - '7.232.52.13/ab', - '11.232.52.13/', - '::e:f:2001/129', - '::c:f:2001/228', - '::10:f:2001/-1', - '::6d:f:2001/*', - '::86:f:2001/ab', - '::23:f:2001/', + public function testValidBlocks( $block ) { + $this->assertTrue( IP::isValidBlock( $block ), "$block is a valid IP block" ); + } + + /** + * @covers IP::isValidBlock + * @dataProvider provideInvalidBlocks + */ + public function testInvalidBlocks( $invalid ) { + $this->assertFalse( IP::isValidBlock( $invalid ), "$invalid is not a valid IP block" ); + } + + public function provideInvalidBlocks() { + return array( + array( '116.17.184.5/33' ), + array( '0.17.184.5/130' ), + array( '16.17.184.1/-1' ), + array( '10.232.52.13/*' ), + array( '7.232.52.13/ab' ), + array( '11.232.52.13/' ), + array( '::e:f:2001/129' ), + array( '::c:f:2001/228' ), + array( '::10:f:2001/-1' ), + array( '::6d:f:2001/*' ), + array( '::86:f:2001/ab' ), + array( '::23:f:2001/' ), ); - foreach ( $invalid as $i ) { - $this->assertFalse( IP::isValidBlock( $i ), "$i is not a valid IP block" ); - } } /** @@ -333,16 +370,31 @@ class IPTest extends PHPUnit_Framework_TestCase { /** * @covers IP::isPublic + * @dataProvider provideIsPublic */ - public function testPrivateIPs() { - $private = array( 'fc00::3', 'fc00::ff', '::1', '10.0.0.1', '172.16.0.1', '192.168.0.1' ); - foreach ( $private as $p ) { - $this->assertFalse( IP::isPublic( $p ), "$p is not a public IP address" ); - } - $public = array( '2001:5c0:1000:a::133', 'fc::3', '00FC::' ); - foreach ( $public as $p ) { - $this->assertTrue( IP::isPublic( $p ), "$p is a public IP address" ); - } + public function testIsPublic( $expected, $input ) { + $result = IP::isPublic( $input ); + $this->assertEquals( $expected, $result ); + } + + /** + * Provider for IP::testIsPublic() + */ + public static function provideIsPublic() { + return array( + array( false, 'fc00::3' ), # RFC 4193 (local) + array( false, 'fc00::ff' ), # RFC 4193 (local) + array( false, '127.1.2.3' ), # loopback + array( false, '::1' ), # loopback + array( false, 'fe80::1' ), # link-local + array( false, '169.254.1.1' ), # link-local + array( false, '10.0.0.1' ), # RFC 1918 (private) + array( false, '172.16.0.1' ), # RFC 1918 (private) + array( false, '192.168.0.1' ), # RFC 1918 (private) + array( true, '2001:5c0:1000:a::133' ), # public + array( true, 'fc::3' ), # public + array( true, '00FC::' ) # public + ); } // Private wrapper used to test CIDR Parsing. @@ -359,40 +411,55 @@ class IPTest extends PHPUnit_Framework_TestCase { /** * @covers IP::hexToQuad + * @dataProvider provideIPsAndHexes */ - public function testHexToQuad() { - $this->assertEquals( '0.0.0.1', IP::hexToQuad( '00000001' ) ); - $this->assertEquals( '255.0.0.0', IP::hexToQuad( 'FF000000' ) ); - $this->assertEquals( '255.255.255.255', IP::hexToQuad( 'FFFFFFFF' ) ); - $this->assertEquals( '10.188.222.255', IP::hexToQuad( '0ABCDEFF' ) ); - // hex not left-padded... - $this->assertEquals( '0.0.0.0', IP::hexToQuad( '0' ) ); - $this->assertEquals( '0.0.0.1', IP::hexToQuad( '1' ) ); - $this->assertEquals( '0.0.0.255', IP::hexToQuad( 'FF' ) ); - $this->assertEquals( '0.0.255.0', IP::hexToQuad( 'FF00' ) ); + public function testHexToQuad( $ip, $hex ) { + $this->assertEquals( $ip, IP::hexToQuad( $hex ) ); + } + + /** + * Provide some IP addresses and their equivalent hex representations + */ + public function provideIPsandHexes() { + return array( + array( '0.0.0.1', '00000001' ), + array( '255.0.0.0', 'FF000000' ), + array( '255.255.255.255', 'FFFFFFFF' ), + array( '10.188.222.255', '0ABCDEFF' ), + // hex not left-padded... + array( '0.0.0.0', '0' ), + array( '0.0.0.1', '1' ), + array( '0.0.0.255', 'FF' ), + array( '0.0.255.0', 'FF00' ), + ); } /** * @covers IP::hexToOctet + * @dataProvider provideOctetsAndHexes */ - public function testHexToOctet() { - $this->assertEquals( '0:0:0:0:0:0:0:1', - IP::hexToOctet( '00000000000000000000000000000001' ) ); - $this->assertEquals( '0:0:0:0:0:0:FF:3', - IP::hexToOctet( '00000000000000000000000000FF0003' ) ); - $this->assertEquals( '0:0:0:0:0:0:FF00:6', - IP::hexToOctet( '000000000000000000000000FF000006' ) ); - $this->assertEquals( '0:0:0:0:0:0:FCCF:FAFF', - IP::hexToOctet( '000000000000000000000000FCCFFAFF' ) ); - $this->assertEquals( 'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', - IP::hexToOctet( 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' ) ); - // hex not left-padded... - $this->assertEquals( '0:0:0:0:0:0:0:0', IP::hexToOctet( '0' ) ); - $this->assertEquals( '0:0:0:0:0:0:0:1', IP::hexToOctet( '1' ) ); - $this->assertEquals( '0:0:0:0:0:0:0:FF', IP::hexToOctet( 'FF' ) ); - $this->assertEquals( '0:0:0:0:0:0:0:FFD0', IP::hexToOctet( 'FFD0' ) ); - $this->assertEquals( '0:0:0:0:0:0:FA00:0', IP::hexToOctet( 'FA000000' ) ); - $this->assertEquals( '0:0:0:0:0:0:FCCF:FAFF', IP::hexToOctet( 'FCCFFAFF' ) ); + public function testHexToOctet( $octet, $hex ) { + $this->assertEquals( $octet, IP::hexToOctet( $hex ) ); + } + + /** + * Provide some hex and octet representations of the same IPs + */ + public function provideOctetsAndHexes() { + return array( + array( '0:0:0:0:0:0:0:1', '00000000000000000000000000000001' ), + array( '0:0:0:0:0:0:FF:3', '00000000000000000000000000FF0003' ), + array( '0:0:0:0:0:0:FF00:6', '000000000000000000000000FF000006' ), + array( '0:0:0:0:0:0:FCCF:FAFF', '000000000000000000000000FCCFFAFF' ), + array( 'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' ), + // hex not left-padded... + array( '0:0:0:0:0:0:0:0', '0' ), + array( '0:0:0:0:0:0:0:1', '1' ), + array( '0:0:0:0:0:0:0:FF', 'FF' ), + array( '0:0:0:0:0:0:0:FFD0', 'FFD0' ), + array( '0:0:0:0:0:0:FA00:0', 'FA000000' ), + array( '0:0:0:0:0:0:FCCF:FAFF', 'FCCFFAFF' ), + ); } /** diff --git a/tests/phpunit/includes/utils/MWFunctionTest.php b/tests/phpunit/includes/utils/MWFunctionTest.php deleted file mode 100644 index f4d17999..00000000 --- a/tests/phpunit/includes/utils/MWFunctionTest.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php - -/** - * @covers MWFunction - */ -class MWFunctionTest extends MediaWikiTestCase { - public function testNewObjFunction() { - $arg1 = 'Foo'; - $arg2 = 'Bar'; - $arg3 = array( 'Baz' ); - $arg4 = new ExampleObject; - - $args = array( $arg1, $arg2, $arg3, $arg4 ); - - $newObject = new MWBlankClass( $arg1, $arg2, $arg3, $arg4 ); - $this->hideDeprecated( 'MWFunction::newObj' ); - $this->assertEquals( - MWFunction::newObj( 'MWBlankClass', $args )->args, - $newObject->args - ); - } -} - -class MWBlankClass { - - public $args = array(); - - function __construct( $arg1, $arg2, $arg3, $arg4 ) { - $this->args = array( $arg1, $arg2, $arg3, $arg4 ); - } -} - -class ExampleObject { -} diff --git a/tests/phpunit/includes/utils/UIDGeneratorTest.php b/tests/phpunit/includes/utils/UIDGeneratorTest.php index 0e11ccad..fedcc762 100644 --- a/tests/phpunit/includes/utils/UIDGeneratorTest.php +++ b/tests/phpunit/includes/utils/UIDGeneratorTest.php @@ -1,6 +1,6 @@ <?php -class UIDGeneratorTest extends MediaWikiTestCase { +class UIDGeneratorTest extends PHPUnit_Framework_TestCase { protected function tearDown() { // Bug: 44850 @@ -28,7 +28,7 @@ class UIDGeneratorTest extends MediaWikiTestCase { $lastId = array_shift( $ids ); - $this->assertArrayEquals( array_unique( $ids ), $ids, "All generated IDs are unique." ); + $this->assertSame( array_unique( $ids ), $ids, "All generated IDs are unique." ); foreach ( $ids as $id ) { $id_bin = wfBaseConvert( $id, 10, 2 ); @@ -105,8 +105,8 @@ class UIDGeneratorTest extends MediaWikiTestCase { $id1 = UIDGenerator::newSequentialPerNodeID( 'test', 32 ); $id2 = UIDGenerator::newSequentialPerNodeID( 'test', 32 ); - $this->assertType( 'float', $id1, "ID returned as float" ); - $this->assertType( 'float', $id2, "ID returned as float" ); + $this->assertInternalType( 'float', $id1, "ID returned as float" ); + $this->assertInternalType( 'float', $id2, "ID returned as float" ); $this->assertGreaterThan( 0, $id1, "ID greater than 1" ); $this->assertGreaterThan( $id1, $id2, "IDs increasing in value" ); } @@ -118,7 +118,7 @@ class UIDGeneratorTest extends MediaWikiTestCase { $ids = UIDGenerator::newSequentialPerNodeIDs( 'test', 32, 5 ); $lastId = null; foreach ( $ids as $id ) { - $this->assertType( 'float', $id, "ID returned as float" ); + $this->assertInternalType( 'float', $id, "ID returned as float" ); $this->assertGreaterThan( 0, $id, "ID greater than 1" ); if ( $lastId ) { $this->assertGreaterThan( $lastId, $id, "IDs increasing in value" ); |