From: Máté Szabó Date: Sun, 30 Jun 2019 13:23:53 +0000 (+0200) Subject: Move trivially compatible tests to the unit tests suite X-Git-Tag: 1.34.0-rc.0~1234 X-Git-Url: http://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=commitdiff_plain;h=344481f60d92ac9295129c5089c0fe467ff497b9 Move trivially compatible tests to the unit tests suite This changeset resumes work on T89432 and related tickets by porting an initial set of tests to the new unit test suite separated out in I69b92db3e70093570e05cc0a64c7780a278b321a. The tests were only ported if they worked immediately without requiring any changes other than changing the test case class to MediaWikiUnitTestCase and moving the test to the new suite. If a test failed for any reason (even trivial misconfiguration), it was NOT ported. With this change, the unit tests suite now consits of a total of 455 tests. As before, you can run these tests via the following command: $ composer phpunit:unit Bug: T84948 Bug: T89432 Bug: T87781 Change-Id: Ibb8175981092d7f41864e641cc3c118af70a5c76 --- diff --git a/tests/common/TestSetup.php b/tests/common/TestSetup.php index e24c4c5442..a42f573bb3 100644 --- a/tests/common/TestSetup.php +++ b/tests/common/TestSetup.php @@ -18,6 +18,9 @@ class TestSetup { global $wgSessionProviders, $wgSessionPbkdf2Iterations; global $wgJobTypeConf; global $wgAuthManagerConfig; + global $wgShowExceptionDetails; + + $wgShowExceptionDetails = true; // wfWarn should cause tests to fail $wgDevelopmentWarnings = true; diff --git a/tests/common/TestsAutoLoader.php b/tests/common/TestsAutoLoader.php index e1dde22436..c35e80fada 100644 --- a/tests/common/TestsAutoLoader.php +++ b/tests/common/TestsAutoLoader.php @@ -54,6 +54,7 @@ $wgAutoloadClasses += [ 'HamcrestPHPUnitIntegration' => "$testDir/phpunit/HamcrestPHPUnitIntegration.php", 'LessFileCompilationTest' => "$testDir/phpunit/LessFileCompilationTest.php", 'MediaWikiCoversValidator' => "$testDir/phpunit/MediaWikiCoversValidator.php", + 'MediaWikiGroupValidator' => "$testDir/phpunit/MediaWikiGroupValidator.php", 'MediaWikiLangTestCase' => "$testDir/phpunit/MediaWikiLangTestCase.php", 'MediaWikiLoggerPHPUnitTestListener' => "$testDir/phpunit/MediaWikiLoggerPHPUnitTestListener.php", 'MediaWikiPHPUnitCommand' => "$testDir/phpunit/MediaWikiPHPUnitCommand.php", diff --git a/tests/phpunit/MediaWikiGroupValidator.php b/tests/phpunit/MediaWikiGroupValidator.php new file mode 100644 index 0000000000..4daff34aca --- /dev/null +++ b/tests/phpunit/MediaWikiGroupValidator.php @@ -0,0 +1,38 @@ +getDocComment() ); + } +} diff --git a/tests/phpunit/MediaWikiIntegrationTestCase.php b/tests/phpunit/MediaWikiIntegrationTestCase.php index a5c9ab19a2..3216d213a4 100644 --- a/tests/phpunit/MediaWikiIntegrationTestCase.php +++ b/tests/phpunit/MediaWikiIntegrationTestCase.php @@ -24,6 +24,7 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase { use MediaWikiCoversValidator; use PHPUnit4And6Compat; + use MediaWikiGroupValidator; /** * The original service locator. This is overridden during setUp(). @@ -1319,17 +1320,6 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase { return $this->tablesUsed || $this->isTestInDatabaseGroup(); } - /** - * @return bool - * @since 1.32 - */ - protected function isTestInDatabaseGroup() { - // If the test class says it belongs to the Database group, it needs the database. - // NOTE: This ONLY checks for the group in the class level doc comment. - $rc = new ReflectionClass( $this ); - return (bool)preg_match( '/@group +Database/im', $rc->getDocComment() ); - } - /** * Insert a new page. * diff --git a/tests/phpunit/MediaWikiUnitTestCase.php b/tests/phpunit/MediaWikiUnitTestCase.php index 06f0c9cd01..c1dc0f9099 100644 --- a/tests/phpunit/MediaWikiUnitTestCase.php +++ b/tests/phpunit/MediaWikiUnitTestCase.php @@ -30,4 +30,17 @@ use PHPUnit\Framework\TestCase; abstract class MediaWikiUnitTestCase extends TestCase { use PHPUnit4And6Compat; use MediaWikiCoversValidator; + use MediaWikiGroupValidator; + + /** + * @throws ReflectionException + */ + protected function setUp() { + parent::setUp(); + if ( $this->isTestInDatabaseGroup() ) { + throw new \Exception( get_class( $this ) . + ' extends MediaWikiUnitTestCase, and may not have the @group Database annotation.' ); + } + } + } diff --git a/tests/phpunit/bootstrap.php b/tests/phpunit/bootstrap.php index 258c822553..4b1ade2906 100644 --- a/tests/phpunit/bootstrap.php +++ b/tests/phpunit/bootstrap.php @@ -65,3 +65,4 @@ require_once "$IP/tests/common/TestSetup.php"; wfRequireOnceInGlobalScope( "$IP/includes/AutoLoader.php" ); wfRequireOnceInGlobalScope( "$IP/tests/common/TestsAutoLoader.php" ); +wfRequireOnceInGlobalScope( "$IP/includes/Defines.php" ); diff --git a/tests/phpunit/includes/FauxResponseTest.php b/tests/phpunit/includes/FauxResponseTest.php deleted file mode 100644 index 8085bc710c..0000000000 --- a/tests/phpunit/includes/FauxResponseTest.php +++ /dev/null @@ -1,146 +0,0 @@ -response = new FauxResponse; - } - - /** - * @covers FauxResponse::setCookie - * @covers FauxResponse::getCookie - * @covers FauxResponse::getCookieData - * @covers FauxResponse::getCookies - */ - public function testCookie() { - $expire = time() + 100; - $cookie = [ - 'value' => 'val', - 'path' => '/path', - 'domain' => 'domain', - 'secure' => true, - 'httpOnly' => false, - 'raw' => false, - 'expire' => $expire, - ]; - - $this->assertEquals( null, $this->response->getCookie( 'xkey' ), 'Non-existing cookie' ); - $this->response->setCookie( 'key', 'val', $expire, [ - 'prefix' => 'x', - 'path' => '/path', - 'domain' => 'domain', - 'secure' => 1, - 'httpOnly' => 0, - ] ); - $this->assertEquals( 'val', $this->response->getCookie( 'xkey' ), 'Existing cookie' ); - $this->assertEquals( $cookie, $this->response->getCookieData( 'xkey' ), - 'Existing cookie (data)' ); - $this->assertEquals( [ 'xkey' => $cookie ], $this->response->getCookies(), - 'Existing cookies' ); - } - - /** - * @covers FauxResponse::getheader - * @covers FauxResponse::header - */ - public function testHeader() { - $this->assertEquals( null, $this->response->getHeader( 'Location' ), 'Non-existing header' ); - - $this->response->header( 'Location: http://localhost/' ); - $this->assertEquals( - 'http://localhost/', - $this->response->getHeader( 'Location' ), - 'Set header' - ); - - $this->response->header( 'Location: http://127.0.0.1/' ); - $this->assertEquals( - 'http://127.0.0.1/', - $this->response->getHeader( 'Location' ), - 'Same header' - ); - - $this->response->header( 'Location: http://127.0.0.2/', false ); - $this->assertEquals( - 'http://127.0.0.1/', - $this->response->getHeader( 'Location' ), - 'Same header with override disabled' - ); - - $this->response->header( 'Location: http://localhost/' ); - $this->assertEquals( - 'http://localhost/', - $this->response->getHeader( 'LOCATION' ), - 'Get header case insensitive' - ); - } - - /** - * @covers FauxResponse::getStatusCode - */ - public function testResponseCode() { - $this->response->header( 'HTTP/1.1 200' ); - $this->assertEquals( 200, $this->response->getStatusCode(), 'Header with no message' ); - - $this->response->header( 'HTTP/1.x 201' ); - $this->assertEquals( - 201, - $this->response->getStatusCode(), - 'Header with no message and protocol 1.x' - ); - - $this->response->header( 'HTTP/1.1 202 OK' ); - $this->assertEquals( 202, $this->response->getStatusCode(), 'Normal header' ); - - $this->response->header( 'HTTP/1.x 203 OK' ); - $this->assertEquals( - 203, - $this->response->getStatusCode(), - 'Normal header with no message and protocol 1.x' - ); - - $this->response->header( 'HTTP/1.x 204 OK', false, 205 ); - $this->assertEquals( - 205, - $this->response->getStatusCode(), - '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, - $this->response->getStatusCode(), - 'Third parameter with another header' - ); - } -} diff --git a/tests/phpunit/includes/FormOptionsInitializationTest.php b/tests/phpunit/includes/FormOptionsInitializationTest.php deleted file mode 100644 index 2c78618aa1..0000000000 --- a/tests/phpunit/includes/FormOptionsInitializationTest.php +++ /dev/null @@ -1,70 +0,0 @@ -object = TestingAccessWrapper::newFromObject( new FormOptions() ); - } - - /** - * @covers FormOptions::add - */ - public function testAddStringOption() { - $this->object->add( 'foo', 'string value' ); - $this->assertEquals( - [ - 'foo' => [ - 'default' => 'string value', - 'consumed' => false, - 'type' => FormOptions::STRING, - 'value' => null, - ] - ], - $this->object->options - ); - } - - /** - * @covers FormOptions::add - */ - public function testAddIntegers() { - $this->object->add( 'one', 1 ); - $this->object->add( 'negone', -1 ); - $this->assertEquals( - [ - 'negone' => [ - 'default' => -1, - 'value' => null, - 'consumed' => false, - 'type' => FormOptions::INT, - ], - 'one' => [ - 'default' => 1, - 'value' => null, - 'consumed' => false, - 'type' => FormOptions::INT, - ] - ], - $this->object->options - ); - } -} diff --git a/tests/phpunit/includes/FormOptionsTest.php b/tests/phpunit/includes/FormOptionsTest.php deleted file mode 100644 index da08670f57..0000000000 --- a/tests/phpunit/includes/FormOptionsTest.php +++ /dev/null @@ -1,105 +0,0 @@ -object = new FormOptions; - $this->object->add( 'string1', 'string one' ); - $this->object->add( 'string2', 'string two' ); - $this->object->add( 'integer', 0 ); - $this->object->add( 'float', 0.0 ); - $this->object->add( 'intnull', 0, FormOptions::INTNULL ); - } - - /** Helpers for testGuessType() */ - /* @{ */ - private function assertGuessBoolean( $data ) { - $this->guess( FormOptions::BOOL, $data ); - } - - private function assertGuessInt( $data ) { - $this->guess( FormOptions::INT, $data ); - } - - private function assertGuessFloat( $data ) { - $this->guess( FormOptions::FLOAT, $data ); - } - - private function assertGuessString( $data ) { - $this->guess( FormOptions::STRING, $data ); - } - - private function assertGuessArray( $data ) { - $this->guess( FormOptions::ARR, $data ); - } - - /** Generic helper */ - private function guess( $expected, $data ) { - $this->assertEquals( - $expected, - FormOptions::guessType( $data ) - ); - } - - /* @} */ - - /** - * Reuse helpers above assertGuessBoolean assertGuessInt assertGuessString - * @covers FormOptions::guessType - */ - public function testGuessTypeDetection() { - $this->assertGuessBoolean( true ); - $this->assertGuessBoolean( false ); - - $this->assertGuessInt( 0 ); - $this->assertGuessInt( -5 ); - $this->assertGuessInt( 5 ); - $this->assertGuessInt( 0x0F ); - - $this->assertGuessFloat( 0.0 ); - $this->assertGuessFloat( 1.5 ); - $this->assertGuessFloat( 1e3 ); - - $this->assertGuessString( 'true' ); - $this->assertGuessString( 'false' ); - $this->assertGuessString( '5' ); - $this->assertGuessString( '0' ); - $this->assertGuessString( '1.5' ); - - $this->assertGuessArray( [ 'foo' ] ); - } - - /** - * @expectedException MWException - * @covers FormOptions::guessType - */ - public function testGuessTypeOnNullThrowException() { - $this->object->guessType( null ); - } -} diff --git a/tests/phpunit/includes/LicensesTest.php b/tests/phpunit/includes/LicensesTest.php deleted file mode 100644 index 0e96bf44ee..0000000000 --- a/tests/phpunit/includes/LicensesTest.php +++ /dev/null @@ -1,25 +0,0 @@ - 'FooField', - 'type' => 'select', - 'section' => 'description', - 'id' => 'wpLicense', - 'label' => 'A label text', # Note can't test label-message because $wgOut is not defined - 'name' => 'AnotherName', - 'licenses' => $str, - ] ); - $this->assertThat( $lc, $this->isInstanceOf( Licenses::class ) ); - } -} diff --git a/tests/phpunit/includes/Rest/HeaderContainerTest.php b/tests/phpunit/includes/Rest/HeaderContainerTest.php deleted file mode 100644 index e0dbfdf4f6..0000000000 --- a/tests/phpunit/includes/Rest/HeaderContainerTest.php +++ /dev/null @@ -1,172 +0,0 @@ - [ - [ - [ 'Test', 'foo' ] - ], - [ 'Test' => [ 'foo' ] ], - [ 'Test' => 'foo' ] - ], - 'replace' => [ - [ - [ 'Test', 'foo' ], - [ 'Test', 'bar' ], - ], - [ 'Test' => [ 'bar' ] ], - [ 'Test' => 'bar' ], - ], - 'array value' => [ - [ - [ 'Test', [ '1', '2' ] ], - [ 'Test', [ '3', '4' ] ], - ], - [ 'Test' => [ '3', '4' ] ], - [ 'Test' => '3, 4' ] - ], - 'preserve most recent case' => [ - [ - [ 'test', 'foo' ], - [ 'tesT', 'bar' ], - ], - [ 'tesT' => [ 'bar' ] ], - [ 'tesT' => 'bar' ] - ], - 'empty' => [ [], [], [] ], - ]; - } - - /** @dataProvider provideSetHeader */ - public function testSetHeader( $setOps, $headers, $lines ) { - $hc = new HeaderContainer; - foreach ( $setOps as list( $name, $value ) ) { - $hc->setHeader( $name, $value ); - } - $this->assertSame( $headers, $hc->getHeaders() ); - $this->assertSame( $lines, $hc->getHeaderLines() ); - } - - public static function provideAddHeader() { - return [ - 'simple' => [ - [ - [ 'Test', 'foo' ] - ], - [ 'Test' => [ 'foo' ] ], - [ 'Test' => 'foo' ] - ], - 'add' => [ - [ - [ 'Test', 'foo' ], - [ 'Test', 'bar' ], - ], - [ 'Test' => [ 'foo', 'bar' ] ], - [ 'Test' => 'foo, bar' ], - ], - 'array value' => [ - [ - [ 'Test', [ '1', '2' ] ], - [ 'Test', [ '3', '4' ] ], - ], - [ 'Test' => [ '1', '2', '3', '4' ] ], - [ 'Test' => '1, 2, 3, 4' ] - ], - 'preserve original case' => [ - [ - [ 'Test', 'foo' ], - [ 'tesT', 'bar' ], - ], - [ 'Test' => [ 'foo', 'bar' ] ], - [ 'Test' => 'foo, bar' ] - ], - ]; - } - - /** @dataProvider provideAddHeader */ - public function testAddHeader( $addOps, $headers, $lines ) { - $hc = new HeaderContainer; - foreach ( $addOps as list( $name, $value ) ) { - $hc->addHeader( $name, $value ); - } - $this->assertSame( $headers, $hc->getHeaders() ); - $this->assertSame( $lines, $hc->getHeaderLines() ); - } - - public static function provideRemoveHeader() { - return [ - 'simple' => [ - [ [ 'Test', 'foo' ] ], - [ 'Test' ], - [], - [] - ], - 'case mismatch' => [ - [ [ 'Test', 'foo' ] ], - [ 'tesT' ], - [], - [] - ], - 'remove nonexistent' => [ - [ [ 'A', '1' ] ], - [ 'B' ], - [ 'A' => [ '1' ] ], - [ 'A' => '1' ] - ], - ]; - } - - /** @dataProvider provideRemoveHeader */ - public function testRemoveHeader( $addOps, $removeOps, $headers, $lines ) { - $hc = new HeaderContainer; - foreach ( $addOps as list( $name, $value ) ) { - $hc->addHeader( $name, $value ); - } - foreach ( $removeOps as $name ) { - $hc->removeHeader( $name ); - } - $this->assertSame( $headers, $hc->getHeaders() ); - $this->assertSame( $lines, $hc->getHeaderLines() ); - } - - public function testHasHeader() { - $hc = new HeaderContainer; - $hc->addHeader( 'A', '1' ); - $hc->addHeader( 'B', '2' ); - $hc->addHeader( 'C', '3' ); - $hc->removeHeader( 'B' ); - $hc->removeHeader( 'c' ); - $this->assertTrue( $hc->hasHeader( 'A' ) ); - $this->assertTrue( $hc->hasHeader( 'a' ) ); - $this->assertFalse( $hc->hasHeader( 'B' ) ); - $this->assertFalse( $hc->hasHeader( 'c' ) ); - $this->assertFalse( $hc->hasHeader( 'C' ) ); - } - - public function testGetRawHeaderLines() { - $hc = new HeaderContainer; - $hc->addHeader( 'A', '1' ); - $hc->addHeader( 'a', '2' ); - $hc->addHeader( 'b', '3' ); - $hc->addHeader( 'Set-Cookie', 'x' ); - $hc->addHeader( 'SET-cookie', 'y' ); - $this->assertSame( - [ - 'A: 1, 2', - 'b: 3', - 'Set-Cookie: x', - 'Set-Cookie: y', - ], - $hc->getRawHeaderLines() - ); - } -} diff --git a/tests/phpunit/includes/Rest/PathTemplateMatcher/PathMatcherTest.php b/tests/phpunit/includes/Rest/PathTemplateMatcher/PathMatcherTest.php deleted file mode 100644 index 935cec19e9..0000000000 --- a/tests/phpunit/includes/Rest/PathTemplateMatcher/PathMatcherTest.php +++ /dev/null @@ -1,77 +0,0 @@ - [], 'userData' => 0 ] ], - [ '/b', false ], - [ '/b/1', [ 'params' => [ 'x' => '1' ], 'userData' => 1 ] ], - [ '/c/1/d', [ 'params' => [ 'x' => '1' ], 'userData' => 2 ] ], - [ '/c/1/e', [ 'params' => [ 'x' => '1' ], 'userData' => 3 ] ], - [ '/c/000/e', [ 'params' => [ 'x' => '000' ], 'userData' => 3 ] ], - [ '/c/1/f', false ], - [ '/c//e', [ 'params' => [ 'x' => '' ], 'userData' => 3 ] ], - [ '/c///e', false ], - ]; - } - - public function createNormalRouter() { - $pm = new PathMatcher; - foreach ( self::$normalRoutes as $i => $route ) { - $pm->add( $route, $i ); - } - return $pm; - } - - /** @dataProvider provideConflictingRoutes */ - public function testAddConflict( $attempt, $expectedUserData, $expectedTemplate ) { - $pm = $this->createNormalRouter(); - $actualTemplate = null; - $actualUserData = null; - try { - $pm->add( $attempt, 'conflict' ); - } catch ( PathConflict $pc ) { - $actualTemplate = $pc->existingTemplate; - $actualUserData = $pc->existingUserData; - } - $this->assertSame( $expectedUserData, $actualUserData ); - $this->assertSame( $expectedTemplate, $actualTemplate ); - } - - /** @dataProvider provideMatch */ - public function testMatch( $path, $expectedResult ) { - $pm = $this->createNormalRouter(); - $result = $pm->match( $path ); - $this->assertSame( $expectedResult, $result ); - } -} diff --git a/tests/phpunit/includes/Rest/StringStreamTest.php b/tests/phpunit/includes/Rest/StringStreamTest.php deleted file mode 100644 index f474643879..0000000000 --- a/tests/phpunit/includes/Rest/StringStreamTest.php +++ /dev/null @@ -1,131 +0,0 @@ -write( $input ); - $ss->seek( 1 ); - $ss->seek( $offset, $whence ); - $destStream = fopen( 'php://memory', 'w+' ); - $ss->copyToStream( $destStream ); - fseek( $destStream, 0 ); - $result = stream_get_contents( $destStream ); - $this->assertSame( $expected, $result ); - } - - public function testGetSize() { - $ss = new StringStream; - $this->assertSame( 0, $ss->getSize() ); - $ss->write( "hello" ); - $this->assertSame( 5, $ss->getSize() ); - $ss->rewind(); - $this->assertSame( 5, $ss->getSize() ); - } - - public function testTell() { - $ss = new StringStream; - $this->assertSame( $ss->tell(), 0 ); - $ss->write( "abc" ); - $this->assertSame( $ss->tell(), 3 ); - $ss->seek( 0 ); - $ss->read( 1 ); - $this->assertSame( $ss->tell(), 1 ); - } - - public function testEof() { - $ss = new StringStream( 'abc' ); - $this->assertFalse( $ss->eof() ); - $ss->read( 1 ); - $this->assertFalse( $ss->eof() ); - $ss->read( 1 ); - $this->assertFalse( $ss->eof() ); - $ss->read( 1 ); - $this->assertTrue( $ss->eof() ); - $ss->rewind(); - $this->assertFalse( $ss->eof() ); - } - - public function testIsSeekable() { - $ss = new StringStream; - $this->assertTrue( $ss->isSeekable() ); - } - - public function testIsReadable() { - $ss = new StringStream; - $this->assertTrue( $ss->isReadable() ); - } - - public function testIsWritable() { - $ss = new StringStream; - $this->assertTrue( $ss->isWritable() ); - } - - public function testSeekWrite() { - $ss = new StringStream; - $this->assertSame( '', (string)$ss ); - $ss->write( 'a' ); - $this->assertSame( 'a', (string)$ss ); - $ss->write( 'b' ); - $this->assertSame( 'ab', (string)$ss ); - $ss->seek( 1 ); - $ss->write( 'c' ); - $this->assertSame( 'ac', (string)$ss ); - } - - /** @dataProvider provideSeekGetContents */ - public function testSeekGetContents( $input, $offset, $whence, $expected ) { - $ss = new StringStream( $input ); - $ss->seek( 1 ); - $ss->seek( $offset, $whence ); - $this->assertSame( $expected, $ss->getContents() ); - } - - public static function provideSeekRead() { - return [ - [ 'abcde', 0, SEEK_SET, 1, 'a' ], - [ 'abcde', 0, SEEK_SET, 2, 'ab' ], - [ 'abcde', 4, SEEK_SET, 2, 'e' ], - [ 'abcde', 5, SEEK_SET, 1, '' ], - [ 'abcde', 1, SEEK_CUR, 1, 'c' ], - [ 'abcde', 0, SEEK_END, 1, '' ], - [ 'abcde', -1, SEEK_END, 1, 'e' ], - ]; - } - - /** @dataProvider provideSeekRead */ - public function testSeekRead( $input, $offset, $whence, $length, $expected ) { - $ss = new StringStream( $input ); - $ss->seek( 1 ); - $ss->seek( $offset, $whence ); - $this->assertSame( $expected, $ss->read( $length ) ); - } - - /** @expectedException \InvalidArgumentException */ - public function testReadBeyondEnd() { - $ss = new StringStream( 'abc' ); - $ss->seek( 1, SEEK_END ); - } - - /** @expectedException \InvalidArgumentException */ - public function testReadBeforeStart() { - $ss = new StringStream( 'abc' ); - $ss->seek( -1 ); - } -} diff --git a/tests/phpunit/includes/Revision/FallbackSlotRoleHandlerTest.php b/tests/phpunit/includes/Revision/FallbackSlotRoleHandlerTest.php deleted file mode 100644 index 898a35f23f..0000000000 --- a/tests/phpunit/includes/Revision/FallbackSlotRoleHandlerTest.php +++ /dev/null @@ -1,73 +0,0 @@ -createMock( Title::class ); - } - - /** - * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::__construct - * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getRole() - * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getNameMessageKey() - * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getDefaultModel() - * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getOutputLayoutHints() - */ - public function testConstruction() { - $handler = new FallbackSlotRoleHandler( 'foo' ); - $this->assertSame( 'foo', $handler->getRole() ); - $this->assertSame( 'slot-name-foo', $handler->getNameMessageKey() ); - - $title = $this->makeBlankTitleObject(); - $this->assertSame( CONTENT_MODEL_TEXT, $handler->getDefaultModel( $title ) ); - - $hints = $handler->getOutputLayoutHints(); - $this->assertArrayHasKey( 'display', $hints ); - $this->assertArrayHasKey( 'region', $hints ); - $this->assertArrayHasKey( 'placement', $hints ); - } - - /** - * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::isAllowedModel() - */ - public function testIsAllowedModel() { - $handler = new FallbackSlotRoleHandler( 'foo', 'FooModel' ); - - // For the fallback handler, no models are allowed - $title = $this->makeBlankTitleObject(); - $this->assertFalse( $handler->isAllowedModel( 'FooModel', $title ) ); - $this->assertFalse( $handler->isAllowedModel( 'QuaxModel', $title ) ); - } - - /** - * @covers \MediaWiki\Revision\SlotRoleHandler::isAllowedModel() - */ - public function testIsAllowedOn() { - $handler = new FallbackSlotRoleHandler( 'foo', 'FooModel' ); - - $title = $this->makeBlankTitleObject(); - $this->assertFalse( $handler->isAllowedOn( $title ) ); - } - - /** - * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::supportsArticleCount() - */ - public function testSupportsArticleCount() { - $handler = new FallbackSlotRoleHandler( 'foo', 'FooModel' ); - - $this->assertFalse( $handler->supportsArticleCount() ); - } - -} diff --git a/tests/phpunit/includes/Revision/SlotRoleHandlerTest.php b/tests/phpunit/includes/Revision/SlotRoleHandlerTest.php deleted file mode 100644 index 372a879313..0000000000 --- a/tests/phpunit/includes/Revision/SlotRoleHandlerTest.php +++ /dev/null @@ -1,65 +0,0 @@ -createMock( Title::class ); - } - - /** - * @covers \MediaWiki\Revision\SlotRoleHandler::__construct - * @covers \MediaWiki\Revision\SlotRoleHandler::getRole() - * @covers \MediaWiki\Revision\SlotRoleHandler::getNameMessageKey() - * @covers \MediaWiki\Revision\SlotRoleHandler::getDefaultModel() - * @covers \MediaWiki\Revision\SlotRoleHandler::getOutputLayoutHints() - */ - public function testConstruction() { - $handler = new SlotRoleHandler( 'foo', 'FooModel', [ 'frob' => 'niz' ] ); - $this->assertSame( 'foo', $handler->getRole() ); - $this->assertSame( 'slot-name-foo', $handler->getNameMessageKey() ); - - $title = $this->makeBlankTitleObject(); - $this->assertSame( 'FooModel', $handler->getDefaultModel( $title ) ); - - $hints = $handler->getOutputLayoutHints(); - $this->assertArrayHasKey( 'frob', $hints ); - $this->assertSame( 'niz', $hints['frob'] ); - - $this->assertArrayHasKey( 'display', $hints ); - $this->assertArrayHasKey( 'region', $hints ); - $this->assertArrayHasKey( 'placement', $hints ); - } - - /** - * @covers \MediaWiki\Revision\SlotRoleHandler::isAllowedModel() - */ - public function testIsAllowedModel() { - $handler = new SlotRoleHandler( 'foo', 'FooModel' ); - - $title = $this->makeBlankTitleObject(); - $this->assertTrue( $handler->isAllowedModel( 'FooModel', $title ) ); - $this->assertFalse( $handler->isAllowedModel( 'QuaxModel', $title ) ); - } - - /** - * @covers \MediaWiki\Revision\SlotRoleHandler::supportsArticleCount() - */ - public function testSupportsArticleCount() { - $handler = new SlotRoleHandler( 'foo', 'FooModel' ); - - $this->assertFalse( $handler->supportsArticleCount() ); - } - -} diff --git a/tests/phpunit/includes/ServiceWiringTest.php b/tests/phpunit/includes/ServiceWiringTest.php deleted file mode 100644 index 02e06f8dda..0000000000 --- a/tests/phpunit/includes/ServiceWiringTest.php +++ /dev/null @@ -1,16 +0,0 @@ -assertSame( $sortedServices, $services, - 'Please keep services sorted alphabetically' ); - } -} diff --git a/tests/phpunit/includes/SiteConfigurationTest.php b/tests/phpunit/includes/SiteConfigurationTest.php deleted file mode 100644 index 3b7226245f..0000000000 --- a/tests/phpunit/includes/SiteConfigurationTest.php +++ /dev/null @@ -1,379 +0,0 @@ -mConf = new SiteConfiguration; - - $this->mConf->suffixes = [ 'wikipedia' => 'wiki' ]; - $this->mConf->wikis = [ 'enwiki', 'dewiki', 'frwiki' ]; - $this->mConf->settings = [ - 'SimpleKey' => [ - 'wiki' => 'wiki', - 'tag' => 'tag', - 'enwiki' => 'enwiki', - 'dewiki' => 'dewiki', - 'frwiki' => 'frwiki', - ], - - 'Fallback' => [ - 'default' => 'default', - 'wiki' => 'wiki', - 'tag' => 'tag', - 'frwiki' => 'frwiki', - 'null_wiki' => null, - ], - - 'WithParams' => [ - 'default' => '$lang $site $wiki', - ], - - '+SomeGlobal' => [ - 'wiki' => [ - 'wiki' => 'wiki', - ], - 'tag' => [ - 'tag' => 'tag', - ], - 'enwiki' => [ - 'enwiki' => 'enwiki', - ], - 'dewiki' => [ - 'dewiki' => 'dewiki', - ], - 'frwiki' => [ - 'frwiki' => 'frwiki', - ], - ], - - 'MergeIt' => [ - '+wiki' => [ - 'wiki' => 'wiki', - ], - '+tag' => [ - 'tag' => 'tag', - ], - 'default' => [ - 'default' => 'default', - ], - '+enwiki' => [ - 'enwiki' => 'enwiki', - ], - '+dewiki' => [ - 'dewiki' => 'dewiki', - ], - '+frwiki' => [ - 'frwiki' => 'frwiki', - ], - ], - ]; - - $GLOBALS['SomeGlobal'] = [ 'SomeGlobal' => 'SomeGlobal' ]; - } - - /** - * This function is used as a callback within the tests below - */ - public static function getSiteParamsCallback( $conf, $wiki ) { - $site = null; - $lang = null; - foreach ( $conf->suffixes as $suffix ) { - if ( substr( $wiki, -strlen( $suffix ) ) == $suffix ) { - $site = $suffix; - $lang = substr( $wiki, 0, -strlen( $suffix ) ); - break; - } - } - - return [ - 'suffix' => $site, - 'lang' => $lang, - 'params' => [ - 'lang' => $lang, - 'site' => $site, - 'wiki' => $wiki, - ], - 'tags' => [ 'tag' ], - ]; - } - - /** - * @covers SiteConfiguration::siteFromDB - */ - public function testSiteFromDb() { - $this->assertEquals( - [ 'wikipedia', 'en' ], - $this->mConf->siteFromDB( 'enwiki' ), - 'siteFromDB()' - ); - $this->assertEquals( - [ 'wikipedia', '' ], - $this->mConf->siteFromDB( 'wiki' ), - 'siteFromDB() on a suffix' - ); - $this->assertEquals( - [ null, null ], - $this->mConf->siteFromDB( 'wikien' ), - 'siteFromDB() on a non-existing wiki' - ); - - $this->mConf->suffixes = [ 'wiki', '' ]; - $this->assertEquals( - [ '', 'wikien' ], - $this->mConf->siteFromDB( 'wikien' ), - 'siteFromDB() on a non-existing wiki (2)' - ); - } - - /** - * @covers SiteConfiguration::getLocalDatabases - */ - public function testGetLocalDatabases() { - $this->assertEquals( - [ 'enwiki', 'dewiki', 'frwiki' ], - $this->mConf->getLocalDatabases(), - 'getLocalDatabases()' - ); - } - - /** - * @covers SiteConfiguration::get - */ - public function testGetConfVariables() { - // Simple - $this->assertEquals( - 'enwiki', - $this->mConf->get( 'SimpleKey', 'enwiki', 'wiki' ), - 'get(): simple setting on an existing wiki' - ); - $this->assertEquals( - 'dewiki', - $this->mConf->get( 'SimpleKey', 'dewiki', 'wiki' ), - 'get(): simple setting on an existing wiki (2)' - ); - $this->assertEquals( - 'frwiki', - $this->mConf->get( 'SimpleKey', 'frwiki', 'wiki' ), - 'get(): simple setting on an existing wiki (3)' - ); - $this->assertEquals( - 'wiki', - $this->mConf->get( 'SimpleKey', 'wiki', 'wiki' ), - 'get(): simple setting on an suffix' - ); - $this->assertEquals( - 'wiki', - $this->mConf->get( 'SimpleKey', 'eswiki', 'wiki' ), - 'get(): simple setting on an non-existing wiki' - ); - - // Fallback - $this->assertEquals( - 'wiki', - $this->mConf->get( 'Fallback', 'enwiki', 'wiki' ), - 'get(): fallback setting on an existing wiki' - ); - $this->assertEquals( - 'tag', - $this->mConf->get( 'Fallback', 'dewiki', 'wiki', [], [ 'tag' ] ), - 'get(): fallback setting on an existing wiki (with wiki tag)' - ); - $this->assertEquals( - 'frwiki', - $this->mConf->get( 'Fallback', 'frwiki', 'wiki', [], [ 'tag' ] ), - 'get(): no fallback if wiki has its own setting (matching tag)' - ); - $this->assertSame( - // Potential regression test for T192855 - null, - $this->mConf->get( 'Fallback', 'null_wiki', 'wiki', [], [ 'tag' ] ), - 'get(): no fallback if wiki has its own setting (matching tag and uses null)' - ); - $this->assertEquals( - 'wiki', - $this->mConf->get( 'Fallback', 'wiki', 'wiki' ), - 'get(): fallback setting on an suffix' - ); - $this->assertEquals( - 'wiki', - $this->mConf->get( 'Fallback', 'wiki', 'wiki', [], [ 'tag' ] ), - 'get(): fallback setting on an suffix (with wiki tag)' - ); - $this->assertEquals( - 'wiki', - $this->mConf->get( 'Fallback', 'eswiki', 'wiki' ), - 'get(): fallback setting on an non-existing wiki' - ); - $this->assertEquals( - 'tag', - $this->mConf->get( 'Fallback', 'eswiki', 'wiki', [], [ 'tag' ] ), - 'get(): fallback setting on an non-existing wiki (with wiki tag)' - ); - - // Merging - $common = [ 'wiki' => 'wiki', 'default' => 'default' ]; - $commonTag = [ 'tag' => 'tag', 'wiki' => 'wiki', 'default' => 'default' ]; - $this->assertEquals( - [ 'enwiki' => 'enwiki' ] + $common, - $this->mConf->get( 'MergeIt', 'enwiki', 'wiki' ), - 'get(): merging setting on an existing wiki' - ); - $this->assertEquals( - [ 'enwiki' => 'enwiki' ] + $commonTag, - $this->mConf->get( 'MergeIt', 'enwiki', 'wiki', [], [ 'tag' ] ), - 'get(): merging setting on an existing wiki (with tag)' - ); - $this->assertEquals( - [ 'dewiki' => 'dewiki' ] + $common, - $this->mConf->get( 'MergeIt', 'dewiki', 'wiki' ), - 'get(): merging setting on an existing wiki (2)' - ); - $this->assertEquals( - [ 'dewiki' => 'dewiki' ] + $commonTag, - $this->mConf->get( 'MergeIt', 'dewiki', 'wiki', [], [ 'tag' ] ), - 'get(): merging setting on an existing wiki (2) (with tag)' - ); - $this->assertEquals( - [ 'frwiki' => 'frwiki' ] + $common, - $this->mConf->get( 'MergeIt', 'frwiki', 'wiki' ), - 'get(): merging setting on an existing wiki (3)' - ); - $this->assertEquals( - [ 'frwiki' => 'frwiki' ] + $commonTag, - $this->mConf->get( 'MergeIt', 'frwiki', 'wiki', [], [ 'tag' ] ), - 'get(): merging setting on an existing wiki (3) (with tag)' - ); - $this->assertEquals( - [ 'wiki' => 'wiki' ] + $common, - $this->mConf->get( 'MergeIt', 'wiki', 'wiki' ), - 'get(): merging setting on an suffix' - ); - $this->assertEquals( - [ 'wiki' => 'wiki' ] + $commonTag, - $this->mConf->get( 'MergeIt', 'wiki', 'wiki', [], [ 'tag' ] ), - 'get(): merging setting on an suffix (with tag)' - ); - $this->assertEquals( - $common, - $this->mConf->get( 'MergeIt', 'eswiki', 'wiki' ), - 'get(): merging setting on an non-existing wiki' - ); - $this->assertEquals( - $commonTag, - $this->mConf->get( 'MergeIt', 'eswiki', 'wiki', [], [ 'tag' ] ), - 'get(): merging setting on an non-existing wiki (with tag)' - ); - } - - /** - * @covers SiteConfiguration::siteFromDB - */ - public function testSiteFromDbWithCallback() { - $this->mConf->siteParamsCallback = 'SiteConfigurationTest::getSiteParamsCallback'; - - $this->assertEquals( - [ 'wiki', 'en' ], - $this->mConf->siteFromDB( 'enwiki' ), - 'siteFromDB() with callback' - ); - $this->assertEquals( - [ 'wiki', '' ], - $this->mConf->siteFromDB( 'wiki' ), - 'siteFromDB() with callback on a suffix' - ); - $this->assertEquals( - [ null, null ], - $this->mConf->siteFromDB( 'wikien' ), - 'siteFromDB() with callback on a non-existing wiki' - ); - } - - /** - * @covers SiteConfiguration::get - */ - public function testParameterReplacement() { - $this->mConf->siteParamsCallback = 'SiteConfigurationTest::getSiteParamsCallback'; - - $this->assertEquals( - 'en wiki enwiki', - $this->mConf->get( 'WithParams', 'enwiki', 'wiki' ), - 'get(): parameter replacement on an existing wiki' - ); - $this->assertEquals( - 'de wiki dewiki', - $this->mConf->get( 'WithParams', 'dewiki', 'wiki' ), - 'get(): parameter replacement on an existing wiki (2)' - ); - $this->assertEquals( - 'fr wiki frwiki', - $this->mConf->get( 'WithParams', 'frwiki', 'wiki' ), - 'get(): parameter replacement on an existing wiki (3)' - ); - $this->assertEquals( - ' wiki wiki', - $this->mConf->get( 'WithParams', 'wiki', 'wiki' ), - 'get(): parameter replacement on an suffix' - ); - $this->assertEquals( - 'es wiki eswiki', - $this->mConf->get( 'WithParams', 'eswiki', 'wiki' ), - 'get(): parameter replacement on an non-existing wiki' - ); - } - - /** - * @covers SiteConfiguration::getAll - */ - public function testGetAllGlobals() { - $this->mConf->siteParamsCallback = 'SiteConfigurationTest::getSiteParamsCallback'; - - $getall = [ - 'SimpleKey' => 'enwiki', - 'Fallback' => 'tag', - 'WithParams' => 'en wiki enwiki', - 'SomeGlobal' => [ 'enwiki' => 'enwiki' ] + $GLOBALS['SomeGlobal'], - 'MergeIt' => [ - 'enwiki' => 'enwiki', - 'tag' => 'tag', - 'wiki' => 'wiki', - 'default' => 'default' - ], - ]; - $this->assertEquals( $getall, $this->mConf->getAll( 'enwiki' ), 'getAll()' ); - - $this->mConf->extractAllGlobals( 'enwiki', 'wiki' ); - - $this->assertEquals( - $getall['SimpleKey'], - $GLOBALS['SimpleKey'], - 'extractAllGlobals(): simple setting' - ); - $this->assertEquals( - $getall['Fallback'], - $GLOBALS['Fallback'], - 'extractAllGlobals(): fallback setting' - ); - $this->assertEquals( - $getall['WithParams'], - $GLOBALS['WithParams'], - 'extractAllGlobals(): parameter replacement' - ); - $this->assertEquals( - $getall['SomeGlobal'], - $GLOBALS['SomeGlobal'], - 'extractAllGlobals(): merging with global' - ); - $this->assertEquals( - $getall['MergeIt'], - $GLOBALS['MergeIt'], - 'extractAllGlobals(): merging setting' - ); - } -} diff --git a/tests/phpunit/includes/Storage/PreparedEditTest.php b/tests/phpunit/includes/Storage/PreparedEditTest.php deleted file mode 100644 index 29999ee535..0000000000 --- a/tests/phpunit/includes/Storage/PreparedEditTest.php +++ /dev/null @@ -1,22 +0,0 @@ -parserOutputCallback = function () { - return new ParserOutput(); - }; - - $this->assertEquals( $output, $edit->getOutput() ); - $this->assertEquals( $output, $edit->output ); - } -} diff --git a/tests/phpunit/includes/XmlSelectTest.php b/tests/phpunit/includes/XmlSelectTest.php deleted file mode 100644 index 52e20bdb99..0000000000 --- a/tests/phpunit/includes/XmlSelectTest.php +++ /dev/null @@ -1,182 +0,0 @@ -select = new XmlSelect(); - } - - protected function tearDown() { - parent::tearDown(); - $this->select = null; - } - - /** - * @covers XmlSelect::__construct - */ - public function testConstructWithoutParameters() { - $this->assertEquals( '', $this->select->getHTML() ); - } - - /** - * Parameters are $name (false), $id (false), $default (false) - * @dataProvider provideConstructionParameters - * @covers XmlSelect::__construct - */ - public function testConstructParameters( $name, $id, $default, $expected ) { - $this->select = new XmlSelect( $name, $id, $default ); - $this->assertEquals( $expected, $this->select->getHTML() ); - } - - /** - * Provide parameters for testConstructParameters() which use three - * parameters: - * - $name (default: false) - * - $id (default: false) - * - $default (default: false) - * Provides a fourth parameters representing the expected HTML output - */ - public static function provideConstructionParameters() { - return [ - /** - * Values are set following a 3-bit Gray code where two successive - * values differ by only one value. - * See https://en.wikipedia.org/wiki/Gray_code - */ - # $name $id $default - [ false, false, false, '' ], - [ false, false, 'foo', '' ], - [ false, 'id', 'foo', '' ], - [ false, 'id', false, '' ], - [ 'name', 'id', false, '' ], - [ 'name', 'id', 'foo', '' ], - [ 'name', false, 'foo', '' ], - [ 'name', false, false, '' ], - ]; - } - - /** - * @covers XmlSelect::addOption - */ - public function testAddOption() { - $this->select->addOption( 'foo' ); - $this->assertEquals( - '', - $this->select->getHTML() - ); - } - - /** - * @covers XmlSelect::addOption - */ - public function testAddOptionWithDefault() { - $this->select->addOption( 'foo', true ); - $this->assertEquals( - '', - $this->select->getHTML() - ); - } - - /** - * @covers XmlSelect::addOption - */ - public function testAddOptionWithFalse() { - $this->select->addOption( 'foo', false ); - $this->assertEquals( - '', - $this->select->getHTML() - ); - } - - /** - * @covers XmlSelect::addOption - */ - public function testAddOptionWithValueZero() { - $this->select->addOption( 'foo', 0 ); - $this->assertEquals( - '', - $this->select->getHTML() - ); - } - - /** - * @covers XmlSelect::setDefault - */ - public function testSetDefault() { - $this->select->setDefault( 'bar1' ); - $this->select->addOption( 'foo1' ); - $this->select->addOption( 'bar1' ); - $this->select->addOption( 'foo2' ); - $this->assertEquals( - '', $this->select->getHTML() ); - } - - /** - * Adding default later on should set the correct selection or - * raise an exception. - * To handle this, we need to render the options in getHtml() - * @covers XmlSelect::setDefault - */ - public function testSetDefaultAfterAddingOptions() { - $this->select->addOption( 'foo1' ); - $this->select->addOption( 'bar1' ); - $this->select->addOption( 'foo2' ); - $this->select->setDefault( 'bar1' ); # setting default after adding options - $this->assertEquals( - '', $this->select->getHTML() ); - } - - /** - * @covers XmlSelect::setAttribute - * @covers XmlSelect::getAttribute - */ - public function testGetAttributes() { - # create some attributes - $this->select->setAttribute( 'dummy', 0x777 ); - $this->select->setAttribute( 'string', 'euro €' ); - $this->select->setAttribute( 1911, 'razor' ); - - # verify we can retrieve them - $this->assertEquals( - $this->select->getAttribute( 'dummy' ), - 0x777 - ); - $this->assertEquals( - $this->select->getAttribute( 'string' ), - 'euro €' - ); - $this->assertEquals( - $this->select->getAttribute( 1911 ), - 'razor' - ); - - # inexistent keys should give us 'null' - $this->assertEquals( - $this->select->getAttribute( 'I DO NOT EXIT' ), - null - ); - - # verify string / integer - $this->assertEquals( - $this->select->getAttribute( '1911' ), - 'razor' - ); - $this->assertEquals( - $this->select->getAttribute( 'dummy' ), - 0x777 - ); - } -} diff --git a/tests/phpunit/includes/auth/AuthenticationResponseTest.php b/tests/phpunit/includes/auth/AuthenticationResponseTest.php deleted file mode 100644 index c79682275f..0000000000 --- a/tests/phpunit/includes/auth/AuthenticationResponseTest.php +++ /dev/null @@ -1,112 +0,0 @@ -messageType = 'warning'; - foreach ( $expect as $field => $value ) { - $res->$field = $value; - } - $ret = call_user_func_array( "MediaWiki\\Auth\\AuthenticationResponse::$constructor", $args ); - $this->assertEquals( $res, $ret ); - } else { - try { - call_user_func_array( "MediaWiki\\Auth\\AuthenticationResponse::$constructor", $args ); - $this->fail( 'Expected exception not thrown' ); - } catch ( \Exception $ex ) { - $this->assertEquals( $expect, $ex ); - } - } - } - - public function provideConstructors() { - $req = $this->getMockForAbstractClass( AuthenticationRequest::class ); - $msg = new \Message( 'mainpage' ); - - return [ - [ 'newPass', [], [ - 'status' => AuthenticationResponse::PASS, - ] ], - [ 'newPass', [ 'name' ], [ - 'status' => AuthenticationResponse::PASS, - 'username' => 'name', - ] ], - [ 'newPass', [ 'name', null ], [ - 'status' => AuthenticationResponse::PASS, - 'username' => 'name', - ] ], - - [ 'newFail', [ $msg ], [ - 'status' => AuthenticationResponse::FAIL, - 'message' => $msg, - 'messageType' => 'error', - ] ], - - [ 'newRestart', [ $msg ], [ - 'status' => AuthenticationResponse::RESTART, - 'message' => $msg, - ] ], - - [ 'newAbstain', [], [ - 'status' => AuthenticationResponse::ABSTAIN, - ] ], - - [ 'newUI', [ [ $req ], $msg ], [ - 'status' => AuthenticationResponse::UI, - 'neededRequests' => [ $req ], - 'message' => $msg, - 'messageType' => 'warning', - ] ], - - [ 'newUI', [ [ $req ], $msg, 'warning' ], [ - 'status' => AuthenticationResponse::UI, - 'neededRequests' => [ $req ], - 'message' => $msg, - 'messageType' => 'warning', - ] ], - - [ 'newUI', [ [ $req ], $msg, 'error' ], [ - 'status' => AuthenticationResponse::UI, - 'neededRequests' => [ $req ], - 'message' => $msg, - 'messageType' => 'error', - ] ], - [ 'newUI', [ [], $msg ], - new \InvalidArgumentException( '$reqs may not be empty' ) - ], - - [ 'newRedirect', [ [ $req ], 'http://example.org/redir' ], [ - 'status' => AuthenticationResponse::REDIRECT, - 'neededRequests' => [ $req ], - 'redirectTarget' => 'http://example.org/redir', - ] ], - [ - 'newRedirect', - [ [ $req ], 'http://example.org/redir', [ 'foo' => 'bar' ] ], - [ - 'status' => AuthenticationResponse::REDIRECT, - 'neededRequests' => [ $req ], - 'redirectTarget' => 'http://example.org/redir', - 'redirectApiData' => [ 'foo' => 'bar' ], - ] - ], - [ 'newRedirect', [ [], 'http://example.org/redir' ], - new \InvalidArgumentException( '$reqs may not be empty' ) - ], - ]; - } - -} diff --git a/tests/phpunit/includes/changes/ChangesListFilterGroupTest.php b/tests/phpunit/includes/changes/ChangesListFilterGroupTest.php deleted file mode 100644 index 6190516e68..0000000000 --- a/tests/phpunit/includes/changes/ChangesListFilterGroupTest.php +++ /dev/null @@ -1,79 +0,0 @@ - 'some_type', - 'name' => 'group_name', - 'priority' => 1, - 'filters' => [], - ] - ); - } - - public function testAutoPriorities() { - $group = new MockChangesListFilterGroup( - [ - 'type' => 'some_type', - 'name' => 'groupName', - 'isFullCoverage' => true, - 'priority' => 1, - 'filters' => [ - [ 'name' => 'hidefoo' ], - [ 'name' => 'hidebar' ], - [ 'name' => 'hidebaz' ], - ], - ] - ); - - $filters = $group->getFilters(); - $this->assertEquals( - [ - -2, - -3, - -4, - ], - array_map( - function ( $f ) { - return $f->getPriority(); - }, - array_values( $filters ) - ) - ); - } - - // Get without warnings - public function testGetFilter() { - $group = new MockChangesListFilterGroup( - [ - 'type' => 'some_type', - 'name' => 'groupName', - 'isFullCoverage' => true, - 'priority' => 1, - 'filters' => [ - [ 'name' => 'foo' ], - ], - ] - ); - - $this->assertEquals( - 'foo', - $group->getFilter( 'foo' )->getName() - ); - - $this->assertEquals( - null, - $group->getFilter( 'bar' ) - ); - } -} diff --git a/tests/phpunit/includes/config/HashConfigTest.php b/tests/phpunit/includes/config/HashConfigTest.php deleted file mode 100644 index bac8311cd4..0000000000 --- a/tests/phpunit/includes/config/HashConfigTest.php +++ /dev/null @@ -1,63 +0,0 @@ -assertInstanceOf( HashConfig::class, $conf ); - } - - /** - * @covers HashConfig::__construct - */ - public function testConstructor() { - $conf = new HashConfig(); - $this->assertInstanceOf( HashConfig::class, $conf ); - - // Test passing arguments to the constructor - $conf2 = new HashConfig( [ - 'one' => '1', - ] ); - $this->assertEquals( '1', $conf2->get( 'one' ) ); - } - - /** - * @covers HashConfig::get - */ - public function testGet() { - $conf = new HashConfig( [ - 'one' => '1', - ] ); - $this->assertEquals( '1', $conf->get( 'one' ) ); - $this->setExpectedException( ConfigException::class, 'HashConfig::get: undefined option' ); - $conf->get( 'two' ); - } - - /** - * @covers HashConfig::has - */ - public function testHas() { - $conf = new HashConfig( [ - 'one' => '1', - ] ); - $this->assertTrue( $conf->has( 'one' ) ); - $this->assertFalse( $conf->has( 'two' ) ); - } - - /** - * @covers HashConfig::set - */ - public function testSet() { - $conf = new HashConfig( [ - 'one' => '1', - ] ); - $conf->set( 'two', '2' ); - $this->assertEquals( '2', $conf->get( 'two' ) ); - // Check that set overwrites - $conf->set( 'one', '3' ); - $this->assertEquals( '3', $conf->get( 'one' ) ); - } -} diff --git a/tests/phpunit/includes/config/MultiConfigTest.php b/tests/phpunit/includes/config/MultiConfigTest.php deleted file mode 100644 index fc2839513b..0000000000 --- a/tests/phpunit/includes/config/MultiConfigTest.php +++ /dev/null @@ -1,39 +0,0 @@ - 'bar' ] ), - new HashConfig( [ 'foo' => 'baz', 'bar' => 'foo' ] ), - new HashConfig( [ 'bar' => 'baz' ] ), - ] ); - - $this->assertEquals( 'bar', $multi->get( 'foo' ) ); - $this->assertEquals( 'foo', $multi->get( 'bar' ) ); - $this->setExpectedException( ConfigException::class, 'MultiConfig::get: undefined option:' ); - $multi->get( 'notset' ); - } - - /** - * @covers MultiConfig::has - */ - public function testHas() { - $conf = new MultiConfig( [ - new HashConfig( [ 'foo' => 'foo' ] ), - new HashConfig( [ 'something' => 'bleh' ] ), - new HashConfig( [ 'meh' => 'eh' ] ), - ] ); - - $this->assertTrue( $conf->has( 'foo' ) ); - $this->assertTrue( $conf->has( 'something' ) ); - $this->assertTrue( $conf->has( 'meh' ) ); - $this->assertFalse( $conf->has( 'what' ) ); - } -} diff --git a/tests/phpunit/includes/config/ServiceOptionsTest.php b/tests/phpunit/includes/config/ServiceOptionsTest.php deleted file mode 100644 index 966cf411c7..0000000000 --- a/tests/phpunit/includes/config/ServiceOptionsTest.php +++ /dev/null @@ -1,149 +0,0 @@ - $val ) { - $this->assertSame( $val, $options->get( $key ) ); - } - - // This is lumped in the same test because there's no support for depending on a test that - // has a data provider. - $options->assertRequiredOptions( array_keys( $expected ) ); - - // Suppress warning if no assertions were run. This is expected for empty arguments. - $this->assertTrue( true ); - } - - public function provideConstructor() { - return [ - 'No keys' => [ [], [], [ 'a' => 'aval' ] ], - 'Simple array source' => [ - [ 'a' => 'aval', 'b' => 'bval' ], - [ 'a', 'b' ], - [ 'a' => 'aval', 'b' => 'bval', 'c' => 'cval' ], - ], - 'Simple HashConfig source' => [ - [ 'a' => 'aval', 'b' => 'bval' ], - [ 'a', 'b' ], - new HashConfig( [ 'a' => 'aval', 'b' => 'bval', 'c' => 'cval' ] ), - ], - 'Three different sources' => [ - [ 'a' => 'aval', 'b' => 'bval' ], - [ 'a', 'b' ], - [ 'z' => 'zval' ], - new HashConfig( [ 'a' => 'aval', 'c' => 'cval' ] ), - [ 'b' => 'bval', 'd' => 'dval' ], - ], - 'null key' => [ - [ 'a' => null ], - [ 'a' ], - [ 'a' => null ], - ], - 'Numeric option name' => [ - [ '0' => 'nothing' ], - [ '0' ], - [ '0' => 'nothing' ], - ], - 'Multiple sources for one key' => [ - [ 'a' => 'winner' ], - [ 'a' ], - [ 'a' => 'winner' ], - [ 'a' => 'second place' ], - ], - 'Object value is passed by reference' => [ - [ 'a' => self::$testObj ], - [ 'a' ], - [ 'a' => self::$testObj ], - ], - ]; - } - - /** - * @covers ::__construct - */ - public function testKeyNotFound() { - $this->setExpectedException( InvalidArgumentException::class, - 'Key "a" not found in input sources' ); - - new ServiceOptions( [ 'a' ], [ 'b' => 'bval' ], [ 'c' => 'cval' ] ); - } - - /** - * @covers ::__construct - * @covers ::assertRequiredOptions - */ - public function testOutOfOrderAssertRequiredOptions() { - $options = new ServiceOptions( [ 'a', 'b' ], [ 'a' => '', 'b' => '' ] ); - $options->assertRequiredOptions( [ 'b', 'a' ] ); - $this->assertTrue( true, 'No exception thrown' ); - } - - /** - * @covers ::__construct - * @covers ::get - */ - public function testGetUnrecognized() { - $this->setExpectedException( InvalidArgumentException::class, - 'Unrecognized option "b"' ); - - $options = new ServiceOptions( [ 'a' ], [ 'a' => '' ] ); - $options->get( 'b' ); - } - - /** - * @covers ::__construct - * @covers ::assertRequiredOptions - */ - public function testExtraKeys() { - $this->setExpectedException( Wikimedia\Assert\PreconditionException::class, - 'Precondition failed: Unsupported options passed: b, c!' ); - - $options = new ServiceOptions( [ 'a', 'b', 'c' ], [ 'a' => '', 'b' => '', 'c' => '' ] ); - $options->assertRequiredOptions( [ 'a' ] ); - } - - /** - * @covers ::__construct - * @covers ::assertRequiredOptions - */ - public function testMissingKeys() { - $this->setExpectedException( Wikimedia\Assert\PreconditionException::class, - 'Precondition failed: Required options missing: a, b!' ); - - $options = new ServiceOptions( [ 'c' ], [ 'c' => '' ] ); - $options->assertRequiredOptions( [ 'a', 'b', 'c' ] ); - } - - /** - * @covers ::__construct - * @covers ::assertRequiredOptions - */ - public function testExtraAndMissingKeys() { - $this->setExpectedException( Wikimedia\Assert\PreconditionException::class, - 'Precondition failed: Unsupported options passed: b! Required options missing: c!' ); - - $options = new ServiceOptions( [ 'a', 'b' ], [ 'a' => '', 'b' => '' ] ); - $options->assertRequiredOptions( [ 'a', 'c' ] ); - } -} diff --git a/tests/phpunit/includes/content/JsonContentHandlerTest.php b/tests/phpunit/includes/content/JsonContentHandlerTest.php deleted file mode 100644 index abfb6733a5..0000000000 --- a/tests/phpunit/includes/content/JsonContentHandlerTest.php +++ /dev/null @@ -1,14 +0,0 @@ -makeEmptyContent(); - $this->assertInstanceOf( JsonContent::class, $content ); - $this->assertTrue( $content->isValid() ); - } -} diff --git a/tests/phpunit/includes/debug/logger/MonologSpiTest.php b/tests/phpunit/includes/debug/logger/MonologSpiTest.php deleted file mode 100644 index fda3ac614a..0000000000 --- a/tests/phpunit/includes/debug/logger/MonologSpiTest.php +++ /dev/null @@ -1,136 +0,0 @@ - [ - '@default' => [ - 'processors' => [ 'constructor' ], - 'handlers' => [ 'constructor' ], - ], - ], - 'processors' => [ - 'constructor' => [ - 'class' => 'constructor', - ], - ], - 'handlers' => [ - 'constructor' => [ - 'class' => 'constructor', - 'formatter' => 'constructor', - ], - ], - 'formatters' => [ - 'constructor' => [ - 'class' => 'constructor', - ], - ], - ]; - - $fixture = new MonologSpi( $base ); - $this->assertSame( - $base, - TestingAccessWrapper::newFromObject( $fixture )->config - ); - - $fixture->mergeConfig( [ - 'loggers' => [ - 'merged' => [ - 'processors' => [ 'merged' ], - 'handlers' => [ 'merged' ], - ], - ], - 'processors' => [ - 'merged' => [ - 'class' => 'merged', - ], - ], - 'magic' => [ - 'idkfa' => [ 'xyzzy' ], - ], - 'handlers' => [ - 'merged' => [ - 'class' => 'merged', - 'formatter' => 'merged', - ], - ], - 'formatters' => [ - 'merged' => [ - 'class' => 'merged', - ], - ], - ] ); - $this->assertSame( - [ - 'loggers' => [ - '@default' => [ - 'processors' => [ 'constructor' ], - 'handlers' => [ 'constructor' ], - ], - 'merged' => [ - 'processors' => [ 'merged' ], - 'handlers' => [ 'merged' ], - ], - ], - 'processors' => [ - 'constructor' => [ - 'class' => 'constructor', - ], - 'merged' => [ - 'class' => 'merged', - ], - ], - 'handlers' => [ - 'constructor' => [ - 'class' => 'constructor', - 'formatter' => 'constructor', - ], - 'merged' => [ - 'class' => 'merged', - 'formatter' => 'merged', - ], - ], - 'formatters' => [ - 'constructor' => [ - 'class' => 'constructor', - ], - 'merged' => [ - 'class' => 'merged', - ], - ], - 'magic' => [ - 'idkfa' => [ '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 deleted file mode 100644 index baa4df7390..0000000000 --- a/tests/phpunit/includes/debug/logger/monolog/AvroFormatterTest.php +++ /dev/null @@ -1,76 +0,0 @@ -markTestSkipped( 'Avro is required for the AvroFormatterTest' ); - } - parent::setUp(); - } - - public function testSchemaNotAvailable() { - $formatter = new AvroFormatter( [] ); - $this->setExpectedException( - 'PHPUnit_Framework_Error_Notice', - "The schema for channel 'marty' is not available" - ); - $formatter->format( [ 'channel' => 'marty' ] ); - } - - public function testSchemaNotAvailableReturnValue() { - $formatter = new AvroFormatter( [] ); - $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 - \Wikimedia\suppressWarnings(); - $res = $formatter->format( [ 'channel' => 'marty' ] ); - \Wikimedia\restoreWarnings(); - PHPUnit_Framework_Error_Notice::$enabled = $noticeEnabled; - $this->assertNull( $res ); - } - - public function testDoesSomethingWhenSchemaAvailable() { - $formatter = new AvroFormatter( [ - 'string' => [ - 'schema' => [ 'type' => 'string' ], - 'revision' => 1010101, - ] - ] ); - $res = $formatter->format( [ - 'channel' => 'string', - 'context' => 'better to be', - ] ); - $this->assertNotNull( $res ); - // basically just tell us if avro changes its string encoding, or if - // we completely fail to generate a log message. - $this->assertEquals( 'AAAAAAAAD2m1GGJldHRlciB0byBiZQ==', base64_encode( $res ) ); - } -} diff --git a/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php b/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php deleted file mode 100644 index 4c0ca04fef..0000000000 --- a/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php +++ /dev/null @@ -1,227 +0,0 @@ -markTestSkipped( 'Monolog and Kafka are required for the KafkaHandlerTest' ); - } - - parent::setUp(); - } - - public function topicNamingProvider() { - return [ - [ [], 'monolog_foo' ], - [ [ 'alias' => [ '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( [ 'A' ] ) ); - $produce->expects( $this->once() ) - ->method( 'setMessages' ) - ->with( $expect, $this->anything(), $this->anything() ); - $produce->expects( $this->any() ) - ->method( 'send' ) - ->will( $this->returnValue( true ) ); - - $handler = new KafkaHandler( $produce, $options ); - $handler->handle( [ - 'channel' => 'foo', - 'level' => Logger::EMERGENCY, - 'extra' => [], - 'context' => [], - ] ); - } - - public function swallowsExceptionsWhenRequested() { - return [ - // defaults to false - [ [], true ], - // also try false explicitly - [ [ 'swallowExceptions' => false ], true ], - // turn it on - [ [ '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 ) ); - $produce->expects( $this->any() ) - ->method( 'send' ) - ->will( $this->returnValue( true ) ); - - if ( $expectException ) { - $this->setExpectedException( 'Kafka\Exception' ); - } - - $handler = new KafkaHandler( $produce, $options ); - $handler->handle( [ - 'channel' => 'foo', - 'level' => Logger::EMERGENCY, - 'extra' => [], - 'context' => [], - ] ); - - 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( [ '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( [ - 'channel' => 'foo', - 'level' => Logger::EMERGENCY, - 'extra' => [], - 'context' => [], - ] ); - - 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( [ 'A' ] ) ); - $mockMethod = $produce->expects( $this->exactly( 2 ) ) - ->method( 'setMessages' ); - $produce->expects( $this->any() ) - ->method( 'send' ) - ->will( $this->returnValue( true ) ); - // evil hax - $matcher = TestingAccessWrapper::newFromObject( $mockMethod )->matcher; - TestingAccessWrapper::newFromObject( $matcher )->parametersMatcher = - new \PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters( [ - [ $this->anything(), $this->anything(), [ 'words' ] ], - [ $this->anything(), $this->anything(), [ 'lines' ] ] - ] ); - - $formatter = $this->createMock( \Monolog\Formatter\FormatterInterface::class ); - $formatter->expects( $this->any() ) - ->method( 'format' ) - ->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) ); - - $handler = new KafkaHandler( $produce, [] ); - $handler->setFormatter( $formatter ); - for ( $i = 0; $i < 3; ++$i ) { - $handler->handle( [ - 'channel' => 'foo', - 'level' => Logger::EMERGENCY, - 'extra' => [], - 'context' => [], - ] ); - } - } - - public function testBatchHandlesNullFormatterResult() { - $produce = $this->getMockBuilder( 'Kafka\Produce' ) - ->disableOriginalConstructor() - ->getMock(); - $produce->expects( $this->any() ) - ->method( 'getAvailablePartitions' ) - ->will( $this->returnValue( [ 'A' ] ) ); - $produce->expects( $this->once() ) - ->method( 'setMessages' ) - ->with( $this->anything(), $this->anything(), [ 'words', 'lines' ] ); - $produce->expects( $this->any() ) - ->method( 'send' ) - ->will( $this->returnValue( true ) ); - - $formatter = $this->createMock( \Monolog\Formatter\FormatterInterface::class ); - $formatter->expects( $this->any() ) - ->method( 'format' ) - ->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) ); - - $handler = new KafkaHandler( $produce, [] ); - $handler->setFormatter( $formatter ); - $handler->handleBatch( [ - [ - 'channel' => 'foo', - 'level' => Logger::EMERGENCY, - 'extra' => [], - 'context' => [], - ], - [ - 'channel' => 'foo', - 'level' => Logger::EMERGENCY, - 'extra' => [], - 'context' => [], - ], - [ - 'channel' => 'foo', - 'level' => Logger::EMERGENCY, - 'extra' => [], - 'context' => [], - ], - ] ); - } -} diff --git a/tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php b/tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php deleted file mode 100644 index bdd5c81118..0000000000 --- a/tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php +++ /dev/null @@ -1,122 +0,0 @@ -markTestSkipped( 'This test requires monolog to be installed' ); - } - parent::setUp(); - } - - /** - * @covers MediaWiki\Logger\Monolog\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 MediaWiki\Logger\Monolog\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 ); - } - - /** - * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException - */ - public function testNormalizeExceptionErrorNoTrace() { - if ( !class_exists( AssertionError::class ) ) { - $this->markTestSkipped( 'AssertionError class does not exist' ); - } - - $fixture = new LineFormatter(); - $fixture->includeStacktraces( false ); - $fixture = TestingAccessWrapper::newFromObject( $fixture ); - $boom = new InvalidArgumentException( 'boom', 0, - new LengthException( 'too long', 0, - new AssertionError( 'Spock wuz here' ) - ) - ); - $out = $fixture->normalizeException( $boom ); - $this->assertContains( "\n[Exception InvalidArgumentException]", $out ); - $this->assertContains( "\nCaused by: [Exception LengthException]", $out ); - $this->assertContains( "\nCaused by: [Error AssertionError]", $out ); - $this->assertNotContains( "\n #0", $out ); - } - - /** - * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException - */ - public function testNormalizeExceptionErrorTrace() { - if ( !class_exists( AssertionError::class ) ) { - $this->markTestSkipped( 'AssertionError class does not exist' ); - } - - $fixture = new LineFormatter(); - $fixture->includeStacktraces( true ); - $fixture = TestingAccessWrapper::newFromObject( $fixture ); - $boom = new InvalidArgumentException( 'boom', 0, - new LengthException( 'too long', 0, - new AssertionError( 'Spock wuz here' ) - ) - ); - $out = $fixture->normalizeException( $boom ); - $this->assertContains( "\n[Exception InvalidArgumentException]", $out ); - $this->assertContains( "\nCaused by: [Exception LengthException]", $out ); - $this->assertContains( "\nCaused by: [Error AssertionError]", $out ); - $this->assertContains( "\n #0", $out ); - } -} diff --git a/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php b/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php deleted file mode 100644 index 8d94404cd4..0000000000 --- a/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php +++ /dev/null @@ -1,134 +0,0 @@ -format( $input ); - $this->assertEquals( $expectedOutput, $output ); - } - - private function getMockDiff( $edits ) { - $diff = $this->getMockBuilder( Diff::class ) - ->disableOriginalConstructor() - ->getMock(); - $diff->expects( $this->any() ) - ->method( 'getEdits' ) - ->will( $this->returnValue( $edits ) ); - return $diff; - } - - private function getMockDiffOp( $type = null, $orig = [], $closing = [] ) { - $diffOp = $this->getMockBuilder( DiffOp::class ) - ->disableOriginalConstructor() - ->getMock(); - $diffOp->expects( $this->any() ) - ->method( 'getType' ) - ->will( $this->returnValue( $type ) ); - $diffOp->expects( $this->any() ) - ->method( 'getOrig' ) - ->will( $this->returnValue( $orig ) ); - if ( $type === 'change' ) { - $diffOp->expects( $this->any() ) - ->method( 'getClosing' ) - ->with( $this->isType( 'integer' ) ) - ->will( $this->returnCallback( function () { - return 'mockLine'; - } ) ); - } else { - $diffOp->expects( $this->any() ) - ->method( 'getClosing' ) - ->will( $this->returnValue( $closing ) ); - } - return $diffOp; - } - - public function provideTestFormat() { - $emptyArrayTestCases = [ - $this->getMockDiff( [] ), - $this->getMockDiff( [ $this->getMockDiffOp( 'add' ) ] ), - $this->getMockDiff( [ $this->getMockDiffOp( 'delete' ) ] ), - $this->getMockDiff( [ $this->getMockDiffOp( 'change' ) ] ), - $this->getMockDiff( [ $this->getMockDiffOp( 'copy' ) ] ), - $this->getMockDiff( [ $this->getMockDiffOp( 'FOOBARBAZ' ) ] ), - $this->getMockDiff( [ $this->getMockDiffOp( 'add', 'line' ) ] ), - $this->getMockDiff( [ $this->getMockDiffOp( 'delete', [], [ 'line' ] ) ] ), - $this->getMockDiff( [ $this->getMockDiffOp( 'copy', [], [ 'line' ] ) ] ), - ]; - - $otherTestCases = []; - $otherTestCases[] = [ - $this->getMockDiff( [ $this->getMockDiffOp( 'add', [], [ 'a1' ] ) ] ), - [ [ 'action' => 'add', 'new' => 'a1', 'newline' => 1 ] ], - ]; - $otherTestCases[] = [ - $this->getMockDiff( [ $this->getMockDiffOp( 'add', [], [ 'a1', 'a2' ] ) ] ), - [ - [ 'action' => 'add', 'new' => 'a1', 'newline' => 1 ], - [ 'action' => 'add', 'new' => 'a2', 'newline' => 2 ], - ], - ]; - $otherTestCases[] = [ - $this->getMockDiff( [ $this->getMockDiffOp( 'delete', [ 'd1' ] ) ] ), - [ [ 'action' => 'delete', 'old' => 'd1', 'oldline' => 1 ] ], - ]; - $otherTestCases[] = [ - $this->getMockDiff( [ $this->getMockDiffOp( 'delete', [ 'd1', 'd2' ] ) ] ), - [ - [ 'action' => 'delete', 'old' => 'd1', 'oldline' => 1 ], - [ 'action' => 'delete', 'old' => 'd2', 'oldline' => 2 ], - ], - ]; - $otherTestCases[] = [ - $this->getMockDiff( [ $this->getMockDiffOp( 'change', [ 'd1' ], [ 'a1' ] ) ] ), - [ [ - 'action' => 'change', - 'old' => 'd1', - 'new' => 'mockLine', - 'newline' => 1, 'oldline' => 1 - ] ], - ]; - $otherTestCases[] = [ - $this->getMockDiff( [ $this->getMockDiffOp( - 'change', - [ 'd1', 'd2' ], - [ 'a1', 'a2' ] - ) ] ), - [ - [ - 'action' => 'change', - 'old' => 'd1', - 'new' => 'mockLine', - 'newline' => 1, 'oldline' => 1 - ], - [ - 'action' => 'change', - 'old' => 'd2', - 'new' => 'mockLine', - 'newline' => 2, 'oldline' => 2 - ], - ], - ]; - - $testCases = []; - foreach ( $emptyArrayTestCases as $testCase ) { - $testCases[] = [ $testCase, [] ]; - } - foreach ( $otherTestCases as $testCase ) { - $testCases[] = [ $testCase[0], $testCase[1] ]; - } - return $testCases; - } - -} diff --git a/tests/phpunit/includes/diff/DiffOpTest.php b/tests/phpunit/includes/diff/DiffOpTest.php deleted file mode 100644 index 3026fad6bd..0000000000 --- a/tests/phpunit/includes/diff/DiffOpTest.php +++ /dev/null @@ -1,68 +0,0 @@ -type = 'foo'; - $this->assertEquals( 'foo', $obj->getType() ); - } - - /** - * @covers DiffOp::getOrig - */ - public function testGetOrig() { - $obj = new FakeDiffOp(); - $obj->orig = [ 'foo' ]; - $this->assertEquals( [ 'foo' ], $obj->getOrig() ); - } - - /** - * @covers DiffOp::getClosing - */ - public function testGetClosing() { - $obj = new FakeDiffOp(); - $obj->closing = [ 'foo' ]; - $this->assertEquals( [ 'foo' ], $obj->getClosing() ); - } - - /** - * @covers DiffOp::getClosing - */ - public function testGetClosingWithParameter() { - $obj = new FakeDiffOp(); - $obj->closing = [ 'foo', 'bar', 'baz' ]; - $this->assertEquals( 'foo', $obj->getClosing( 0 ) ); - $this->assertEquals( 'bar', $obj->getClosing( 1 ) ); - $this->assertEquals( 'baz', $obj->getClosing( 2 ) ); - $this->assertEquals( null, $obj->getClosing( 3 ) ); - } - - /** - * @covers DiffOp::norig - */ - public function testNorig() { - $obj = new FakeDiffOp(); - $this->assertEquals( 0, $obj->norig() ); - $obj->orig = [ 'foo' ]; - $this->assertEquals( 1, $obj->norig() ); - } - - /** - * @covers DiffOp::nclosing - */ - public function testNclosing() { - $obj = new FakeDiffOp(); - $this->assertEquals( 0, $obj->nclosing() ); - $obj->closing = [ 'foo' ]; - $this->assertEquals( 1, $obj->nclosing() ); - } - -} diff --git a/tests/phpunit/includes/diff/DiffTest.php b/tests/phpunit/includes/diff/DiffTest.php deleted file mode 100644 index da6d7d9544..0000000000 --- a/tests/phpunit/includes/diff/DiffTest.php +++ /dev/null @@ -1,19 +0,0 @@ -edits = 'FooBarBaz'; - $this->assertEquals( 'FooBarBaz', $obj->getEdits() ); - } - -} diff --git a/tests/phpunit/includes/exception/MWExceptionHandlerTest.php b/tests/phpunit/includes/exception/MWExceptionHandlerTest.php deleted file mode 100644 index 6606065660..0000000000 --- a/tests/phpunit/includes/exception/MWExceptionHandlerTest.php +++ /dev/null @@ -1,74 +0,0 @@ -getTrace(); - $hasObject = false; - $hasArray = false; - foreach ( $trace as $frame ) { - if ( !isset( $frame['args'] ) ) { - continue; - } - foreach ( $frame['args'] as $arg ) { - $hasObject = $hasObject || is_object( $arg ); - $hasArray = $hasArray || is_array( $arg ); - } - - if ( $hasObject && $hasArray ) { - break; - } - } - $this->assertTrue( $hasObject, - "The stacktrace must have a function having an object has parameter" ); - $this->assertTrue( $hasArray, - "The stacktrace must have a function having an array has parameter" ); - - # Now we redact the trace.. and make sure no function arguments are - # arrays or objects. - $redacted = MWExceptionHandler::getRedactedTrace( $e ); - - foreach ( $redacted as $frame ) { - if ( !isset( $frame['args'] ) ) { - continue; - } - foreach ( $frame['args'] as $arg ) { - $this->assertNotInternalType( 'array', $arg ); - $this->assertNotInternalType( 'object', $arg ); - } - } - - $this->assertEquals( 'value', $refvar, 'Ensuring reference variable wasn\'t changed' ); - } - - /** - * Helper function for testExpandArgumentsInCall - * - * Pass it an object and an array, and something by reference :-) - * - * @throws Exception - */ - protected static function helperThrowAnException( $a, $b, &$c ) { - throw new Exception(); - } -} diff --git a/tests/phpunit/includes/installer/InstallDocFormatterTest.php b/tests/phpunit/includes/installer/InstallDocFormatterTest.php deleted file mode 100644 index 9584d4b8c4..0000000000 --- a/tests/phpunit/includes/installer/InstallDocFormatterTest.php +++ /dev/null @@ -1,83 +0,0 @@ -assertEquals( - $expected, - InstallDocFormatter::format( $unformattedText ), - $message - ); - } - - /** - * Provider for testFormat() - */ - public static function provideDocFormattingTests() { - # Format: (expected string, unformattedText string, optional message) - return [ - # Escape some wikitext - [ 'Install <tag>', 'Install ', 'Escaping <' ], - [ 'Install {{template}}', 'Install {{template}}', 'Escaping [[' ], - [ 'Install [[page]]', 'Install [[page]]', 'Escaping {{' ], - [ 'Install __TOC__', 'Install __TOC__', 'Escaping __' ], - [ 'Install ', "Install \r", 'Removing \r' ], - - # Transform \t{1,2} into :{1,2} - [ ':One indentation', "\tOne indentation", 'Replacing a single \t' ], - [ '::Two indentations', "\t\tTwo indentations", 'Replacing 2 x \t' ], - - # Transform 'T123' links - [ - '[https://phabricator.wikimedia.org/T123 T123]', - 'T123', 'Testing T123 links' ], - [ - 'bug [https://phabricator.wikimedia.org/T123 T123]', - 'bug T123', 'Testing bug T123 links' ], - [ - '([https://phabricator.wikimedia.org/T987654 T987654])', - '(T987654)', 'Testing (T987654) links' ], - - # "Tabc" shouldn't work - [ 'Tfoobar', 'Tfoobar', "Don't match T followed by non-digits" ], - [ 'T!!fakefake!!', 'T!!fakefake!!', "Don't match T followed by non-digits" ], - - # Transform 'bug 123' links - [ - '[https://bugzilla.wikimedia.org/123 bug 123]', - 'bug 123', 'Testing bug 123 links' ], - [ - '([https://bugzilla.wikimedia.org/987654 bug 987654])', - '(bug 987654)', 'Testing (bug 987654) links' ], - - # "bug abc" shouldn't work - [ 'bug foobar', 'bug foobar', "Don't match bug followed by non-digits" ], - [ 'bug !!fakefake!!', 'bug !!fakefake!!', "Don't match bug followed by non-digits" ], - - # Transform '$wgFooBar' links - [ - '' - . '[https://www.mediawiki.org/wiki/Manual:$wgFooBar $wgFooBar]', - '$wgFooBar', 'Testing basic $wgFooBar' ], - [ - '' - . '[https://www.mediawiki.org/wiki/Manual:$wgFooBar45 $wgFooBar45]', - '$wgFooBar45', 'Testing $wgFooBar45 (with numbers)' ], - [ - '' - . '[https://www.mediawiki.org/wiki/Manual:$wgFoo_Bar $wgFoo_Bar]', - '$wgFoo_Bar', 'Testing $wgFoo_Bar (with underscore)' ], - - # Icky variables that shouldn't link - [ - '$myAwesomeVariable', - '$myAwesomeVariable', - 'Testing $myAwesomeVariable (not starting with $wg)' - ], - [ '$()not!a&Var', '$()not!a&Var', 'Testing $()not!a&Var (obviously not a variable)' ], - ]; - } -} diff --git a/tests/phpunit/includes/installer/OracleInstallerTest.php b/tests/phpunit/includes/installer/OracleInstallerTest.php deleted file mode 100644 index e255089de4..0000000000 --- a/tests/phpunit/includes/installer/OracleInstallerTest.php +++ /dev/null @@ -1,49 +0,0 @@ -assertEquals( $expected, - OracleInstaller::checkConnectStringFormat( $connectString ), - $msg - ); - } - - /** - * Provider to test OracleInstaller::checkConnectStringFormat() - */ - function provideOracleConnectStrings() { - // expected result, connectString[, message] - return [ - [ true, 'simple_01', 'Simple TNS name' ], - [ true, 'simple_01.world', 'TNS name with domain' ], - [ true, 'simple_01.domain.net', 'TNS name with domain' ], - [ true, 'host123', 'Host only' ], - [ true, 'host123.domain.net', 'FQDN only' ], - [ true, '//host123.domain.net', 'FQDN URL only' ], - [ true, '123.223.213.132', 'Host IP only' ], - [ true, 'host:1521', 'Host and port' ], - [ true, 'host:1521/service', 'Host, port and service' ], - [ true, 'host:1521/service:shared', 'Host, port, service and shared server type' ], - [ true, 'host:1521/service:dedicated', 'Host, port, service and dedicated server type' ], - [ true, 'host:1521/service:pooled', 'Host, port, service and pooled server type' ], - [ - true, - 'host:1521/service:shared/instance1', - 'Host, port, service, server type and instance' - ], - [ true, 'host:1521//instance1', 'Host, port and instance' ], - ]; - } - -} diff --git a/tests/phpunit/includes/interwiki/InterwikiLookupAdapterTest.php b/tests/phpunit/includes/interwiki/InterwikiLookupAdapterTest.php deleted file mode 100644 index 0a13de1d96..0000000000 --- a/tests/phpunit/includes/interwiki/InterwikiLookupAdapterTest.php +++ /dev/null @@ -1,133 +0,0 @@ -interwikiLookup = new InterwikiLookupAdapter( - $this->getSiteLookup( $this->getSites() ) - ); - } - - public function testIsValidInterwiki() { - $this->assertTrue( - $this->interwikiLookup->isValidInterwiki( 'enwt' ), - 'enwt known prefix is valid' - ); - $this->assertTrue( - $this->interwikiLookup->isValidInterwiki( 'foo' ), - 'foo site known prefix is valid' - ); - $this->assertFalse( - $this->interwikiLookup->isValidInterwiki( 'xyz' ), - 'unknown prefix is not valid' - ); - } - - public function testFetch() { - $interwiki = $this->interwikiLookup->fetch( '' ); - $this->assertNull( $interwiki ); - - $interwiki = $this->interwikiLookup->fetch( 'xyz' ); - $this->assertFalse( $interwiki ); - - $interwiki = $this->interwikiLookup->fetch( 'foo' ); - $this->assertInstanceOf( Interwiki::class, $interwiki ); - $this->assertSame( 'foobar', $interwiki->getWikiID() ); - - $interwiki = $this->interwikiLookup->fetch( 'enwt' ); - $this->assertInstanceOf( Interwiki::class, $interwiki ); - - $this->assertSame( 'https://en.wiktionary.org/wiki/$1', $interwiki->getURL(), 'getURL' ); - $this->assertSame( 'https://en.wiktionary.org/w/api.php', $interwiki->getAPI(), 'getAPI' ); - $this->assertSame( 'enwiktionary', $interwiki->getWikiID(), 'getWikiID' ); - $this->assertTrue( $interwiki->isLocal(), 'isLocal' ); - } - - public function testGetAllPrefixes() { - $foo = [ - 'iw_prefix' => 'foo', - 'iw_url' => '', - 'iw_api' => '', - 'iw_wikiid' => 'foobar', - 'iw_local' => false, - 'iw_trans' => false, - ]; - $enwt = [ - 'iw_prefix' => 'enwt', - 'iw_url' => 'https://en.wiktionary.org/wiki/$1', - 'iw_api' => 'https://en.wiktionary.org/w/api.php', - 'iw_wikiid' => 'enwiktionary', - 'iw_local' => true, - 'iw_trans' => false, - ]; - - $this->assertEquals( - [ $foo, $enwt ], - $this->interwikiLookup->getAllPrefixes(), - 'getAllPrefixes()' - ); - - $this->assertEquals( - [ $foo ], - $this->interwikiLookup->getAllPrefixes( false ), - 'get external prefixes' - ); - - $this->assertEquals( - [ $enwt ], - $this->interwikiLookup->getAllPrefixes( true ), - 'get local prefixes' - ); - } - - private function getSiteLookup( SiteList $sites ) { - $siteLookup = $this->getMockBuilder( SiteLookup::class ) - ->disableOriginalConstructor() - ->getMock(); - - $siteLookup->expects( $this->any() ) - ->method( 'getSites' ) - ->will( $this->returnValue( $sites ) ); - - return $siteLookup; - } - - private function getSites() { - $sites = []; - - $site = new Site(); - $site->setGlobalId( 'foobar' ); - $site->addInterwikiId( 'foo' ); - $site->setSource( 'external' ); - $sites[] = $site; - - $site = new MediaWikiSite(); - $site->setGlobalId( 'enwiktionary' ); - $site->setGroup( 'wiktionary' ); - $site->setLanguageCode( 'en' ); - $site->addNavigationId( 'enwiktionary' ); - $site->addInterwikiId( 'enwt' ); - $site->setSource( 'local' ); - $site->setPath( MediaWikiSite::PATH_PAGE, "https://en.wiktionary.org/wiki/$1" ); - $site->setPath( MediaWikiSite::PATH_FILE, "https://en.wiktionary.org/w/$1" ); - $sites[] = $site; - - return new SiteList( $sites ); - } - -} diff --git a/tests/phpunit/includes/libs/objectcache/ReplicatedBagOStuffTest.php b/tests/phpunit/includes/libs/objectcache/ReplicatedBagOStuffTest.php deleted file mode 100644 index 550ec0bd09..0000000000 --- a/tests/phpunit/includes/libs/objectcache/ReplicatedBagOStuffTest.php +++ /dev/null @@ -1,62 +0,0 @@ -writeCache = new HashBagOStuff(); - $this->readCache = new HashBagOStuff(); - $this->cache = new ReplicatedBagOStuff( [ - 'writeFactory' => $this->writeCache, - 'readFactory' => $this->readCache, - ] ); - } - - /** - * @covers ReplicatedBagOStuff::set - */ - public function testSet() { - $key = 'a key'; - $value = 'a value'; - $this->cache->set( $key, $value ); - - // Write to master. - $this->assertEquals( $value, $this->writeCache->get( $key ) ); - // Don't write to replica. Replication is deferred to backend. - $this->assertFalse( $this->readCache->get( $key ) ); - } - - /** - * @covers ReplicatedBagOStuff::get - */ - public function testGet() { - $key = 'a key'; - - $write = 'one value'; - $this->writeCache->set( $key, $write ); - $read = 'another value'; - $this->readCache->set( $key, $read ); - - // Read from replica. - $this->assertEquals( $read, $this->cache->get( $key ) ); - } - - /** - * @covers ReplicatedBagOStuff::get - */ - public function testGetAbsent() { - $key = 'a key'; - $value = 'a value'; - $this->writeCache->set( $key, $value ); - - // Don't read from master. No failover if value is absent. - $this->assertFalse( $this->cache->get( $key ) ); - } -} diff --git a/tests/phpunit/includes/media/IPTCTest.php b/tests/phpunit/includes/media/IPTCTest.php deleted file mode 100644 index 4b3ba0755c..0000000000 --- a/tests/phpunit/includes/media/IPTCTest.php +++ /dev/null @@ -1,85 +0,0 @@ -assertEquals( 'UTF-8', $res ); - } - - /** - * @covers IPTC::parse - */ - public function testIPTCParseNoCharset88591() { - // basically IPTC for keyword with value of 0xBC which is 1/4 in iso-8859-1 - // This data doesn't specify a charset. We're supposed to guess - // (which basically means utf-8 if valid, windows 1252 (iso 8859-1) if not) - $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x06\x1c\x02\x19\x00\x01\xBC"; - $res = IPTC::parse( $iptcData ); - $this->assertEquals( [ '¼' ], $res['Keywords'] ); - } - - /** - * @covers IPTC::parse - */ - public function testIPTCParseNoCharset88591b() { - /* This one contains a sequence that's valid iso 8859-1 but not valid utf8 */ - /* \xC3 = Ã, \xB8 = ¸ */ - $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x09\x1c\x02\x19\x00\x04\xC3\xC3\xC3\xB8"; - $res = IPTC::parse( $iptcData ); - $this->assertEquals( [ 'ÃÃø' ], $res['Keywords'] ); - } - - /** - * Same as testIPTCParseNoCharset88591b, but forcing the charset to utf-8. - * What should happen is the first "\xC3\xC3" should be dropped as invalid, - * leaving \xC3\xB8, which is ø - * @covers IPTC::parse - */ - public function testIPTCParseForcedUTFButInvalid() { - $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x11\x1c\x02\x19\x00\x04\xC3\xC3\xC3\xB8" - . "\x1c\x01\x5A\x00\x03\x1B\x25\x47"; - $res = IPTC::parse( $iptcData ); - $this->assertEquals( [ 'ø' ], $res['Keywords'] ); - } - - /** - * @covers IPTC::parse - */ - public function testIPTCParseNoCharsetUTF8() { - $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x07\x1c\x02\x19\x00\x02¼"; - $res = IPTC::parse( $iptcData ); - $this->assertEquals( [ '¼' ], $res['Keywords'] ); - } - - /** - * Testing something that has 2 values for keyword - * @covers IPTC::parse - */ - public function testIPTCParseMulti() { - $iptcData = /* identifier */ "Photoshop 3.0\08BIM\4\4" - /* length */ . "\0\0\0\0\0\x0D" - . "\x1c\x02\x19" . "\x00\x01" . "\xBC" - . "\x1c\x02\x19" . "\x00\x02" . "\xBC\xBD"; - $res = IPTC::parse( $iptcData ); - $this->assertEquals( [ '¼', '¼½' ], $res['Keywords'] ); - } - - /** - * @covers IPTC::parse - */ - public function testIPTCParseUTF8() { - // This has the magic "\x1c\x01\x5A\x00\x03\x1B\x25\x47" which marks content as UTF8. - $iptcData = - "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x0F\x1c\x02\x19\x00\x02¼\x1c\x01\x5A\x00\x03\x1B\x25\x47"; - $res = IPTC::parse( $iptcData ); - $this->assertEquals( [ '¼' ], $res['Keywords'] ); - } -} diff --git a/tests/phpunit/includes/media/MediaHandlerTest.php b/tests/phpunit/includes/media/MediaHandlerTest.php deleted file mode 100644 index 7a052f6035..0000000000 --- a/tests/phpunit/includes/media/MediaHandlerTest.php +++ /dev/null @@ -1,68 +0,0 @@ -assertEquals( $expected, - $result, - "($width, $height, $max) wanted: {$expected}x$y, got: {z$result}x$y2" ); - } - - public static function provideTestFitBoxWidth() { - return array_merge( - static::generateTestFitBoxWidthData( 50, 50, [ - 50 => 50, - 17 => 17, - 18 => 18 ] - ), - static::generateTestFitBoxWidthData( 366, 300, [ - 50 => 61, - 17 => 21, - 18 => 22 ] - ), - static::generateTestFitBoxWidthData( 300, 366, [ - 50 => 41, - 17 => 14, - 18 => 15 ] - ), - static::generateTestFitBoxWidthData( 100, 400, [ - 50 => 12, - 17 => 4, - 18 => 4 ] - ) - ); - } - - /** - * Generate single test cases by combining the dimensions and tests contents - * - * It creates: - * [$width, $height, $max, $expected], - * [$width, $height, $max2, $expected2], ... - * out of parameters: - * $width, $height, { $max => $expected, $max2 => $expected2, ... } - * - * @param int $width - * @param int $height - * @param array $tests associative array of $max => $expected values - * @return array - */ - private static function generateTestFitBoxWidthData( $width, $height, $tests ) { - $result = []; - foreach ( $tests as $max => $expected ) { - $result[] = [ $width, $height, $max, $expected ]; - } - return $result; - } -} diff --git a/tests/phpunit/includes/objectcache/MemcachedBagOStuffTest.php b/tests/phpunit/includes/objectcache/MemcachedBagOStuffTest.php deleted file mode 100644 index 45971daced..0000000000 --- a/tests/phpunit/includes/objectcache/MemcachedBagOStuffTest.php +++ /dev/null @@ -1,107 +0,0 @@ -cache = new MemcachedPhpBagOStuff( [ 'keyspace' => 'test', 'servers' => [] ] ); - } - - /** - * @covers MemcachedBagOStuff::makeKey - */ - public function testKeyNormalization() { - $this->assertEquals( - 'test:vanilla', - $this->cache->makeKey( 'vanilla' ) - ); - - $this->assertEquals( - 'test:punctuation_marks_are_ok:!@$^&*()', - $this->cache->makeKey( 'punctuation_marks_are_ok', '!@$^&*()' ) - ); - - $this->assertEquals( - 'test:but_spaces:hashes%23:and%0Anewlines:are_not', - $this->cache->makeKey( 'but spaces', 'hashes#', "and\nnewlines", 'are_not' ) - ); - - $this->assertEquals( - 'test:this:key:contains:%F0%9D%95%9E%F0%9D%95%A6%F0%9D%95%9D%F0%9D%95%A5%F0%9' . - 'D%95%9A%F0%9D%95%93%F0%9D%95%AA%F0%9D%95%A5%F0%9D%95%96:characters', - $this->cache->makeKey( 'this', 'key', 'contains', '𝕞𝕦𝕝𝕥𝕚𝕓𝕪𝕥𝕖', 'characters' ) - ); - - $this->assertEquals( - 'test:this:key:contains:#c118f92685a635cb843039de50014c9c', - $this->cache->makeKey( 'this', 'key', 'contains', '𝕥𝕠𝕠 𝕞𝕒𝕟𝕪 𝕞𝕦𝕝𝕥𝕚𝕓𝕪𝕥𝕖 𝕔𝕙𝕒𝕣𝕒𝕔𝕥𝕖𝕣𝕤' ) - ); - - $this->assertEquals( - 'test:BagOStuff-long-key:##dc89dcb43b28614da27660240af478b5', - $this->cache->makeKey( '𝕖𝕧𝕖𝕟', '𝕚𝕗', '𝕨𝕖', '𝕄𝔻𝟝', '𝕖𝕒𝕔𝕙', - '𝕒𝕣𝕘𝕦𝕞𝕖𝕟𝕥', '𝕥𝕙𝕚𝕤', '𝕜𝕖𝕪', '𝕨𝕠𝕦𝕝𝕕', '𝕤𝕥𝕚𝕝𝕝', '𝕓𝕖', '𝕥𝕠𝕠', '𝕝𝕠𝕟𝕘' ) - ); - - $this->assertEquals( - 'test:%23%235820ad1d105aa4dc698585c39df73e19', - $this->cache->makeKey( '##5820ad1d105aa4dc698585c39df73e19' ) - ); - - $this->assertEquals( - 'test:percent_is_escaped:!@$%25^&*()', - $this->cache->makeKey( 'percent_is_escaped', '!@$%^&*()' ) - ); - - $this->assertEquals( - 'test:colon_is_escaped:!@$%3A^&*()', - $this->cache->makeKey( 'colon_is_escaped', '!@$:^&*()' ) - ); - - $this->assertEquals( - 'test:long_key_part_hashed:#0244f7b1811d982dd932dd7de01465ac', - $this->cache->makeKey( 'long_key_part_hashed', str_repeat( 'y', 500 ) ) - ); - } - - /** - * @dataProvider validKeyProvider - * @covers MemcachedBagOStuff::validateKeyEncoding - */ - public function testValidateKeyEncoding( $key ) { - $this->assertSame( $key, $this->cache->validateKeyEncoding( $key ) ); - } - - public function validKeyProvider() { - return [ - 'empty' => [ '' ], - 'digits' => [ '09' ], - 'letters' => [ 'AZaz' ], - 'ASCII special characters' => [ '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' ], - ]; - } - - /** - * @dataProvider invalidKeyProvider - * @covers MemcachedBagOStuff::validateKeyEncoding - */ - public function testValidateKeyEncodingThrowsException( $key ) { - $this->setExpectedException( Exception::class ); - $this->cache->validateKeyEncoding( $key ); - } - - public function invalidKeyProvider() { - return [ - [ "\x00" ], - [ ' ' ], - [ "\x1F" ], - [ "\x7F" ], - [ "\x80" ], - [ "\xFF" ], - ]; - } -} diff --git a/tests/phpunit/includes/objectcache/RESTBagOStuffTest.php b/tests/phpunit/includes/objectcache/RESTBagOStuffTest.php deleted file mode 100644 index dfbca706d6..0000000000 --- a/tests/phpunit/includes/objectcache/RESTBagOStuffTest.php +++ /dev/null @@ -1,96 +0,0 @@ -client = - $this->getMockBuilder( MultiHttpClient::class ) - ->setConstructorArgs( [ [] ] ) - ->setMethods( [ 'run' ] ) - ->getMock(); - $this->bag = new RESTBagOStuff( [ 'client' => $this->client, 'url' => 'http://test/rest/' ] ); - } - - public function testGet() { - $this->client->expects( $this->once() )->method( 'run' )->with( [ - 'method' => 'GET', - 'url' => 'http://test/rest/42xyz42', - 'headers' => [] - // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) - ] )->willReturn( [ 200, 'OK', [], '"somedata"', 0 ] ); - $result = $this->bag->get( '42xyz42' ); - $this->assertEquals( 'somedata', $result ); - } - - public function testGetNotExist() { - $this->client->expects( $this->once() )->method( 'run' )->with( [ - 'method' => 'GET', - 'url' => 'http://test/rest/42xyz42', - 'headers' => [] - // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) - ] )->willReturn( [ 404, 'Not found', [], 'Nothing to see here', 0 ] ); - $result = $this->bag->get( '42xyz42' ); - $this->assertFalse( $result ); - } - - public function testGetBadClient() { - $this->client->expects( $this->once() )->method( 'run' )->with( [ - 'method' => 'GET', - 'url' => 'http://test/rest/42xyz42', - 'headers' => [] - // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) - ] )->willReturn( [ 0, '', [], '', 'cURL has failed you today' ] ); - $result = $this->bag->get( '42xyz42' ); - $this->assertFalse( $result ); - $this->assertEquals( BagOStuff::ERR_UNREACHABLE, $this->bag->getLastError() ); - } - - public function testGetBadServer() { - $this->client->expects( $this->once() )->method( 'run' )->with( [ - 'method' => 'GET', - 'url' => 'http://test/rest/42xyz42', - 'headers' => [] - // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) - ] )->willReturn( [ 500, 'Too busy', [], 'Server is too busy', '' ] ); - $result = $this->bag->get( '42xyz42' ); - $this->assertFalse( $result ); - $this->assertEquals( BagOStuff::ERR_UNEXPECTED, $this->bag->getLastError() ); - } - - public function testPut() { - $this->client->expects( $this->once() )->method( 'run' )->with( [ - 'method' => 'PUT', - 'url' => 'http://test/rest/42xyz42', - 'body' => '"postdata"', - 'headers' => [] - // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) - ] )->willReturn( [ 200, 'OK', [], 'Done', 0 ] ); - $result = $this->bag->set( '42xyz42', 'postdata' ); - $this->assertTrue( $result ); - } - - public function testDelete() { - $this->client->expects( $this->once() )->method( 'run' )->with( [ - 'method' => 'DELETE', - 'url' => 'http://test/rest/42xyz42', - 'headers' => [] - // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) - ] )->willReturn( [ 200, 'OK', [], 'Done', 0 ] ); - $result = $this->bag->delete( '42xyz42' ); - $this->assertTrue( $result ); - } -} diff --git a/tests/phpunit/includes/parser/TidyTest.php b/tests/phpunit/includes/parser/TidyTest.php deleted file mode 100644 index 898ef2d163..0000000000 --- a/tests/phpunit/includes/parser/TidyTest.php +++ /dev/null @@ -1,64 +0,0 @@ -markTestSkipped( 'Tidy not found' ); - } - } - - /** - * @dataProvider provideTestWrapping - */ - public function testTidyWrapping( $expected, $text, $msg = '' ) { - $text = MWTidy::tidy( $text ); - // We don't care about where Tidy wants to stick is

s - $text = trim( preg_replace( '##', '', $text ) ); - // Windows, we love you! - $text = str_replace( "\r", '', $text ); - $this->assertEquals( $expected, $text, $msg ); - } - - public static function provideTestWrapping() { - $testMathML = <<<'MathML' - - - a - - - x - 2 - - + - b - - x - + - c - - -MathML; - return [ - [ - 'foo', - 'foo', - ' should survive tidy' - ], - [ - 'foo', - 'foo', - ' should survive tidy' - ], - [ 'foo', 'foo', ' should survive tidy' ], - [ "foo", 'foo', ' should survive tidy' ], - [ "foo", 'foo', ' should survive tidy' ], - [ $testMathML, $testMathML, ' should survive tidy' ], - ]; - } -} diff --git a/tests/phpunit/includes/password/PasswordTest.php b/tests/phpunit/includes/password/PasswordTest.php deleted file mode 100644 index 61a5147277..0000000000 --- a/tests/phpunit/includes/password/PasswordTest.php +++ /dev/null @@ -1,33 +0,0 @@ -newFromPlaintext( null ); - - $this->assertInstanceOf( InvalidPassword::class, $invalid ); - } -} diff --git a/tests/phpunit/includes/preferences/FiltersTest.php b/tests/phpunit/includes/preferences/FiltersTest.php deleted file mode 100644 index 60b01b880c..0000000000 --- a/tests/phpunit/includes/preferences/FiltersTest.php +++ /dev/null @@ -1,141 +0,0 @@ -filterFromForm( '0' ) ); - self::assertSame( 3, $filter->filterFromForm( '3' ) ); - self::assertSame( '123', $filter->filterForForm( '123' ) ); - } - - /** - * @covers MediaWiki\Preferences\TimezoneFilter::filterFromForm() - * @dataProvider provideTimezoneFilter - * - * @param string $input - * @param string $expected - */ - public function testTimezoneFilter( $input, $expected ) { - $filter = new TimezoneFilter(); - $result = $filter->filterFromForm( $input ); - self::assertEquals( $expected, $result ); - } - - public function provideTimezoneFilter() { - return [ - [ 'ZoneInfo', 'Offset|0' ], - [ 'ZoneInfo|bogus', 'Offset|0' ], - [ 'System', 'System' ], - [ '2:30', 'Offset|150' ], - ]; - } - - /** - * @covers MediaWiki\Preferences\MultiUsernameFilter::filterFromForm() - * @dataProvider provideMultiUsernameFilterFrom - * - * @param string $input - * @param string|null $expected - */ - public function testMultiUsernameFilterFrom( $input, $expected ) { - $filter = $this->makeMultiUsernameFilter(); - $result = $filter->filterFromForm( $input ); - self::assertSame( $expected, $result ); - } - - public function provideMultiUsernameFilterFrom() { - return [ - [ '', null ], - [ "\n\n\n", null ], - [ 'Foo', '1' ], - [ "\n\n\nFoo\nBar\n", "1\n2" ], - [ "Baz\nInvalid\nFoo", "3\n1" ], - [ "Invalid", null ], - [ "Invalid\n\n\nInvalid\n", null ], - ]; - } - - /** - * @covers MediaWiki\Preferences\MultiUsernameFilter::filterForForm() - * @dataProvider provideMultiUsernameFilterFor - * - * @param string $input - * @param string $expected - */ - public function testMultiUsernameFilterFor( $input, $expected ) { - $filter = $this->makeMultiUsernameFilter(); - $result = $filter->filterForForm( $input ); - self::assertSame( $expected, $result ); - } - - public function provideMultiUsernameFilterFor() { - return [ - [ '', '' ], - [ "\n", '' ], - [ '1', 'Foo' ], - [ "\n1\n\n2\377\n", "Foo\nBar" ], - [ "666\n667", '' ], - ]; - } - - private function makeMultiUsernameFilter() { - $userMapping = [ - 'Foo' => 1, - 'Bar' => 2, - 'Baz' => 3, - ]; - $flipped = array_flip( $userMapping ); - $idLookup = self::getMockBuilder( CentralIdLookup::class ) - ->disableOriginalConstructor() - ->setMethods( [ 'centralIdsFromNames', 'namesFromCentralIds' ] ) - ->getMockForAbstractClass(); - - $idLookup->method( 'centralIdsFromNames' ) - ->will( self::returnCallback( function ( $names ) use ( $userMapping ) { - $ids = []; - foreach ( $names as $name ) { - $ids[] = $userMapping[$name] ?? null; - } - return array_filter( $ids, 'is_numeric' ); - } ) ); - $idLookup->method( 'namesFromCentralIds' ) - ->will( self::returnCallback( function ( $ids ) use ( $flipped ) { - $names = []; - foreach ( $ids as $id ) { - $names[] = $flipped[$id] ?? null; - } - return array_filter( $names, 'is_string' ); - } ) ); - - return new MultiUsernameFilter( $idLookup ); - } -} diff --git a/tests/phpunit/includes/registration/ExtensionProcessorTest.php b/tests/phpunit/includes/registration/ExtensionProcessorTest.php deleted file mode 100644 index cdd5c63eff..0000000000 --- a/tests/phpunit/includes/registration/ExtensionProcessorTest.php +++ /dev/null @@ -1,829 +0,0 @@ -dir = __DIR__ . '/FooBar/extension.json'; - $this->dirname = dirname( $this->dir ); - } - - /** - * 'name' is absolutely required - * - * @var array - */ - public static $default = [ - 'name' => 'FooBar', - ]; - - public function testExtractInfo() { - // Test that attributes that begin with @ are ignored - $processor = new ExtensionProcessor(); - $processor->extractInfo( $this->dir, self::$default + [ - '@metadata' => [ 'foobarbaz' ], - 'AnAttribute' => [ 'omg' ], - 'AutoloadClasses' => [ 'FooBar' => 'includes/FooBar.php' ], - 'SpecialPages' => [ 'Foo' => 'SpecialFoo' ], - 'callback' => 'FooBar::onRegistration', - ], 1 ); - - $extracted = $processor->getExtractedInfo(); - $attributes = $extracted['attributes']; - $this->assertArrayHasKey( 'AnAttribute', $attributes ); - $this->assertArrayNotHasKey( '@metadata', $attributes ); - $this->assertArrayNotHasKey( 'AutoloadClasses', $attributes ); - $this->assertSame( - [ 'FooBar' => 'FooBar::onRegistration' ], - $extracted['callbacks'] - ); - $this->assertSame( - [ 'Foo' => 'SpecialFoo' ], - $extracted['globals']['wgSpecialPages'] - ); - } - - public function testExtractNamespaces() { - // Test that namespace IDs can be overwritten - if ( !defined( 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X' ) ) { - define( 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X', 123456 ); - } - - $processor = new ExtensionProcessor(); - $processor->extractInfo( $this->dir, self::$default + [ - 'namespaces' => [ - [ - 'id' => 332200, - 'constant' => 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A', - 'name' => 'Test_A', - 'defaultcontentmodel' => 'TestModel', - 'gender' => [ - 'male' => 'Male test', - 'female' => 'Female test', - ], - 'subpages' => true, - 'content' => true, - 'protection' => 'userright', - ], - [ // Test_X will use ID 123456 not 334400 - 'id' => 334400, - 'constant' => 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X', - 'name' => 'Test_X', - 'defaultcontentmodel' => 'TestModel' - ], - ] - ], 1 ); - - $extracted = $processor->getExtractedInfo(); - - $this->assertArrayHasKey( - 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A', - $extracted['defines'] - ); - $this->assertArrayNotHasKey( - 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X', - $extracted['defines'] - ); - - $this->assertSame( - $extracted['defines']['MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A'], - 332200 - ); - - $this->assertArrayHasKey( 'ExtensionNamespaces', $extracted['attributes'] ); - $this->assertArrayHasKey( 123456, $extracted['attributes']['ExtensionNamespaces'] ); - $this->assertArrayHasKey( 332200, $extracted['attributes']['ExtensionNamespaces'] ); - $this->assertArrayNotHasKey( 334400, $extracted['attributes']['ExtensionNamespaces'] ); - - $this->assertSame( 'Test_X', $extracted['attributes']['ExtensionNamespaces'][123456] ); - $this->assertSame( 'Test_A', $extracted['attributes']['ExtensionNamespaces'][332200] ); - $this->assertSame( - [ 'male' => 'Male test', 'female' => 'Female test' ], - $extracted['globals']['wgExtraGenderNamespaces'][332200] - ); - // A has subpages, X does not - $this->assertTrue( $extracted['globals']['wgNamespacesWithSubpages'][332200] ); - $this->assertArrayNotHasKey( 123456, $extracted['globals']['wgNamespacesWithSubpages'] ); - } - - public static function provideRegisterHooks() { - $merge = [ ExtensionRegistry::MERGE_STRATEGY => 'array_merge_recursive' ]; - // Format: - // Current $wgHooks - // Content in extension.json - // Expected value of $wgHooks - return [ - // No hooks - [ - [], - self::$default, - $merge, - ], - // No current hooks, adding one for "FooBaz" in string format - [ - [], - [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default, - [ 'FooBaz' => [ 'FooBazCallback' ] ] + $merge, - ], - // Hook for "FooBaz", adding another one - [ - [ 'FooBaz' => [ 'PriorCallback' ] ], - [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default, - [ 'FooBaz' => [ 'PriorCallback', 'FooBazCallback' ] ] + $merge, - ], - // No current hooks, adding one for "FooBaz" in verbose array format - [ - [], - [ 'Hooks' => [ 'FooBaz' => [ 'FooBazCallback' ] ] ] + self::$default, - [ 'FooBaz' => [ 'FooBazCallback' ] ] + $merge, - ], - // Hook for "BarBaz", adding one for "FooBaz" - [ - [ 'BarBaz' => [ 'BarBazCallback' ] ], - [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default, - [ - 'BarBaz' => [ 'BarBazCallback' ], - 'FooBaz' => [ 'FooBazCallback' ], - ] + $merge, - ], - // Callbacks for FooBaz wrapped in an array - [ - [], - [ 'Hooks' => [ 'FooBaz' => [ 'Callback1' ] ] ] + self::$default, - [ - 'FooBaz' => [ 'Callback1' ], - ] + $merge, - ], - // Multiple callbacks for FooBaz hook - [ - [], - [ 'Hooks' => [ 'FooBaz' => [ 'Callback1', 'Callback2' ] ] ] + self::$default, - [ - 'FooBaz' => [ 'Callback1', 'Callback2' ], - ] + $merge, - ], - ]; - } - - /** - * @dataProvider provideRegisterHooks - */ - public function testRegisterHooks( $pre, $info, $expected ) { - $processor = new MockExtensionProcessor( [ 'wgHooks' => $pre ] ); - $processor->extractInfo( $this->dir, $info, 1 ); - $extracted = $processor->getExtractedInfo(); - $this->assertEquals( $expected, $extracted['globals']['wgHooks'] ); - } - - public function testExtractConfig1() { - $processor = new ExtensionProcessor; - $info = [ - 'config' => [ - 'Bar' => 'somevalue', - 'Foo' => 10, - '@IGNORED' => 'yes', - ], - ] + self::$default; - $info2 = [ - 'config' => [ - '_prefix' => 'eg', - 'Bar' => 'somevalue' - ], - 'name' => 'FooBar2', - ]; - $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'] ); - $this->assertArrayNotHasKey( 'wg@IGNORED', $extracted['globals'] ); - // Custom prefix: - $this->assertEquals( 'somevalue', $extracted['globals']['egBar'] ); - } - - public function testExtractConfig2() { - $processor = new ExtensionProcessor; - $info = [ - 'config' => [ - 'Bar' => [ 'value' => 'somevalue' ], - 'Foo' => [ 'value' => 10 ], - 'Path' => [ 'value' => 'foo.txt', 'path' => true ], - 'Namespaces' => [ - 'value' => [ - '10' => true, - '12' => false, - ], - 'merge_strategy' => 'array_plus', - ], - ], - ] + self::$default; - $info2 = [ - 'config' => [ - 'Bar' => [ 'value' => 'somevalue' ], - ], - 'config_prefix' => 'eg', - 'name' => 'FooBar2', - ]; - $processor->extractInfo( $this->dir, $info, 2 ); - $processor->extractInfo( $this->dir, $info2, 2 ); - $extracted = $processor->getExtractedInfo(); - $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] ); - $this->assertEquals( 10, $extracted['globals']['wgFoo'] ); - $this->assertEquals( "{$this->dirname}/foo.txt", $extracted['globals']['wgPath'] ); - // Custom prefix: - $this->assertEquals( 'somevalue', $extracted['globals']['egBar'] ); - $this->assertSame( - [ 10 => true, 12 => false, ExtensionRegistry::MERGE_STRATEGY => 'array_plus' ], - $extracted['globals']['wgNamespaces'] - ); - } - - /** - * @expectedException RuntimeException - */ - public function testDuplicateConfigKey1() { - $processor = new ExtensionProcessor; - $info = [ - 'config' => [ - 'Bar' => '', - ] - ] + self::$default; - $info2 = [ - 'config' => [ - 'Bar' => 'g', - ], - 'name' => 'FooBar2', - ]; - $processor->extractInfo( $this->dir, $info, 1 ); - $processor->extractInfo( $this->dir, $info2, 1 ); - } - - /** - * @expectedException RuntimeException - */ - public function testDuplicateConfigKey2() { - $processor = new ExtensionProcessor; - $info = [ - 'config' => [ - 'Bar' => [ 'value' => 'somevalue' ], - ] - ] + self::$default; - $info2 = [ - 'config' => [ - 'Bar' => [ 'value' => 'somevalue' ], - ], - 'name' => 'FooBar2', - ]; - $processor->extractInfo( $this->dir, $info, 2 ); - $processor->extractInfo( $this->dir, $info2, 2 ); - } - - public static function provideExtractExtensionMessagesFiles() { - $dir = __DIR__ . '/FooBar/'; - return [ - [ - [ 'ExtensionMessagesFiles' => [ 'FooBarAlias' => 'FooBar.alias.php' ] ], - [ 'wgExtensionMessagesFiles' => [ 'FooBarAlias' => $dir . 'FooBar.alias.php' ] ] - ], - [ - [ - 'ExtensionMessagesFiles' => [ - 'FooBarAlias' => 'FooBar.alias.php', - 'FooBarMagic' => 'FooBar.magic.i18n.php', - ], - ], - [ - 'wgExtensionMessagesFiles' => [ - 'FooBarAlias' => $dir . 'FooBar.alias.php', - 'FooBarMagic' => $dir . 'FooBar.magic.i18n.php', - ], - ], - ], - ]; - } - - /** - * @dataProvider provideExtractExtensionMessagesFiles - */ - public function testExtractExtensionMessagesFiles( $input, $expected ) { - $processor = new ExtensionProcessor(); - $processor->extractInfo( $this->dir, $input + self::$default, 1 ); - $out = $processor->getExtractedInfo(); - foreach ( $expected as $key => $value ) { - $this->assertEquals( $value, $out['globals'][$key] ); - } - } - - public static function provideExtractMessagesDirs() { - $dir = __DIR__ . '/FooBar/'; - return [ - [ - [ 'MessagesDirs' => [ 'VisualEditor' => 'i18n' ] ], - [ 'wgMessagesDirs' => [ 'VisualEditor' => [ $dir . 'i18n' ] ] ] - ], - [ - [ 'MessagesDirs' => [ 'VisualEditor' => [ 'i18n', 'foobar' ] ] ], - [ 'wgMessagesDirs' => [ 'VisualEditor' => [ $dir . 'i18n', $dir . 'foobar' ] ] ] - ], - ]; - } - - /** - * @dataProvider provideExtractMessagesDirs - */ - public function testExtractMessagesDirs( $input, $expected ) { - $processor = new ExtensionProcessor(); - $processor->extractInfo( $this->dir, $input + self::$default, 1 ); - $out = $processor->getExtractedInfo(); - foreach ( $expected as $key => $value ) { - $this->assertEquals( $value, $out['globals'][$key] ); - } - } - - public function testExtractCredits() { - $processor = new ExtensionProcessor(); - $processor->extractInfo( $this->dir, self::$default, 1 ); - $this->setExpectedException( Exception::class ); - $processor->extractInfo( $this->dir, self::$default, 1 ); - } - - /** - * @dataProvider provideExtractResourceLoaderModules - */ - public function testExtractResourceLoaderModules( - $input, - array $expectedGlobals, - array $expectedAttribs = [] - ) { - $processor = new ExtensionProcessor(); - $processor->extractInfo( $this->dir, $input + self::$default, 1 ); - $out = $processor->getExtractedInfo(); - foreach ( $expectedGlobals as $key => $value ) { - $this->assertEquals( $value, $out['globals'][$key] ); - } - foreach ( $expectedAttribs as $key => $value ) { - $this->assertEquals( $value, $out['attributes'][$key] ); - } - } - - public static function provideExtractResourceLoaderModules() { - $dir = __DIR__ . '/FooBar'; - return [ - // Generic module with localBasePath/remoteExtPath specified - [ - // Input - [ - 'ResourceModules' => [ - 'test.foo' => [ - 'styles' => 'foobar.js', - 'localBasePath' => '', - 'remoteExtPath' => 'FooBar', - ], - ], - ], - // Expected - [ - 'wgResourceModules' => [ - 'test.foo' => [ - 'styles' => 'foobar.js', - 'localBasePath' => $dir, - 'remoteExtPath' => 'FooBar', - ], - ], - ], - ], - // ResourceFileModulePaths specified: - [ - // Input - [ - 'ResourceFileModulePaths' => [ - 'localBasePath' => 'modules', - 'remoteExtPath' => 'FooBar/modules', - ], - 'ResourceModules' => [ - // No paths - 'test.foo' => [ - 'styles' => 'foo.js', - ], - // Different paths set - 'test.bar' => [ - 'styles' => 'bar.js', - 'localBasePath' => 'subdir', - 'remoteExtPath' => 'FooBar/subdir', - ], - // Custom class with no paths set - 'test.class' => [ - 'class' => 'FooBarModule', - 'extra' => 'argument', - ], - // Custom class with a localBasePath - 'test.class.with.path' => [ - 'class' => 'FooBarPathModule', - 'extra' => 'argument', - 'localBasePath' => '', - ] - ], - ], - // Expected - [ - 'wgResourceModules' => [ - 'test.foo' => [ - 'styles' => 'foo.js', - 'localBasePath' => "$dir/modules", - 'remoteExtPath' => 'FooBar/modules', - ], - 'test.bar' => [ - 'styles' => 'bar.js', - 'localBasePath' => "$dir/subdir", - 'remoteExtPath' => 'FooBar/subdir', - ], - 'test.class' => [ - 'class' => 'FooBarModule', - 'extra' => 'argument', - 'localBasePath' => "$dir/modules", - 'remoteExtPath' => 'FooBar/modules', - ], - 'test.class.with.path' => [ - 'class' => 'FooBarPathModule', - 'extra' => 'argument', - 'localBasePath' => $dir, - 'remoteExtPath' => 'FooBar/modules', - ] - ], - ], - ], - // ResourceModuleSkinStyles with file module paths - [ - // Input - [ - 'ResourceFileModulePaths' => [ - 'localBasePath' => '', - 'remoteSkinPath' => 'FooBar', - ], - 'ResourceModuleSkinStyles' => [ - 'foobar' => [ - 'test.foo' => 'foo.css', - ] - ], - ], - // Expected - [ - 'wgResourceModuleSkinStyles' => [ - 'foobar' => [ - 'test.foo' => 'foo.css', - 'localBasePath' => $dir, - 'remoteSkinPath' => 'FooBar', - ], - ], - ], - ], - // ResourceModuleSkinStyles with file module paths and an override - [ - // Input - [ - 'ResourceFileModulePaths' => [ - 'localBasePath' => '', - 'remoteSkinPath' => 'FooBar', - ], - 'ResourceModuleSkinStyles' => [ - 'foobar' => [ - 'test.foo' => 'foo.css', - 'remoteSkinPath' => 'BarFoo' - ], - ], - ], - // Expected - [ - 'wgResourceModuleSkinStyles' => [ - 'foobar' => [ - 'test.foo' => 'foo.css', - 'localBasePath' => $dir, - 'remoteSkinPath' => 'BarFoo', - ], - ], - ], - ], - 'QUnit test module' => [ - // Input - [ - 'QUnitTestModule' => [ - 'localBasePath' => '', - 'remoteExtPath' => 'Foo', - 'scripts' => 'bar.js', - ], - ], - // Expected - [], - [ - 'QUnitTestModules' => [ - 'test.FooBar' => [ - 'localBasePath' => $dir, - 'remoteExtPath' => 'Foo', - 'scripts' => 'bar.js', - ], - ], - ], - ], - ]; - } - - public static function provideSetToGlobal() { - return [ - [ - [ 'wgAPIModules', 'wgAvailableRights' ], - [], - [ - 'APIModules' => [ 'foobar' => 'ApiFooBar' ], - 'AvailableRights' => [ 'foobar', 'unfoobar' ], - ], - [ - 'wgAPIModules' => [ 'foobar' => 'ApiFooBar' ], - 'wgAvailableRights' => [ 'foobar', 'unfoobar' ], - ], - ], - [ - [ 'wgAPIModules', 'wgAvailableRights' ], - [ - 'wgAPIModules' => [ 'barbaz' => 'ApiBarBaz' ], - 'wgAvailableRights' => [ 'barbaz' ] - ], - [ - 'APIModules' => [ 'foobar' => 'ApiFooBar' ], - 'AvailableRights' => [ 'foobar', 'unfoobar' ], - ], - [ - 'wgAPIModules' => [ 'barbaz' => 'ApiBarBaz', 'foobar' => 'ApiFooBar' ], - 'wgAvailableRights' => [ 'barbaz', 'foobar', 'unfoobar' ], - ], - ], - [ - [ 'wgGroupPermissions' ], - [ - 'wgGroupPermissions' => [ - 'sysop' => [ 'delete' ] - ], - ], - [ - 'GroupPermissions' => [ - 'sysop' => [ 'undelete' ], - 'user' => [ 'edit' ] - ], - ], - [ - 'wgGroupPermissions' => [ - 'sysop' => [ 'delete', 'undelete' ], - 'user' => [ 'edit' ] - ], - ] - ] - ]; - } - - /** - * Attributes under manifest_version 2 - */ - public function testExtractAttributes() { - $processor = new ExtensionProcessor(); - // Load FooBar extension - $processor->extractInfo( $this->dir, [ 'name' => 'FooBar' ], 2 ); - $processor->extractInfo( - $this->dir, - [ - 'name' => 'Baz', - 'attributes' => [ - // Loaded - 'FooBar' => [ - 'Plugins' => [ - 'ext.baz.foobar', - ], - ], - // Not loaded - 'FizzBuzz' => [ - 'MorePlugins' => [ - 'ext.baz.fizzbuzz', - ], - ], - ], - ], - 2 - ); - - $info = $processor->getExtractedInfo(); - $this->assertArrayHasKey( 'FooBarPlugins', $info['attributes'] ); - $this->assertSame( [ 'ext.baz.foobar' ], $info['attributes']['FooBarPlugins'] ); - $this->assertArrayNotHasKey( 'FizzBuzzMorePlugins', $info['attributes'] ); - } - - /** - * Attributes under manifest_version 1 - */ - public function testAttributes1() { - $processor = new ExtensionProcessor(); - $processor->extractInfo( - $this->dir, - [ - 'name' => 'FooBar', - 'FooBarPlugins' => [ - 'ext.baz.foobar', - ], - 'FizzBuzzMorePlugins' => [ - 'ext.baz.fizzbuzz', - ], - ], - 1 - ); - $processor->extractInfo( - $this->dir, - [ - 'name' => 'FooBar2', - 'FizzBuzzMorePlugins' => [ - 'ext.bar.fizzbuzz', - ] - ], - 1 - ); - - $info = $processor->getExtractedInfo(); - $this->assertArrayHasKey( 'FooBarPlugins', $info['attributes'] ); - $this->assertSame( [ 'ext.baz.foobar' ], $info['attributes']['FooBarPlugins'] ); - $this->assertArrayHasKey( 'FizzBuzzMorePlugins', $info['attributes'] ); - $this->assertSame( - [ 'ext.baz.fizzbuzz', 'ext.bar.fizzbuzz' ], - $info['attributes']['FizzBuzzMorePlugins'] - ); - } - - public function testAttributes1_notarray() { - $processor = new ExtensionProcessor(); - $this->setExpectedException( - InvalidArgumentException::class, - "The value for 'FooBarPlugins' should be an array (from {$this->dir})" - ); - $processor->extractInfo( - $this->dir, - [ - 'FooBarPlugins' => 'ext.baz.foobar', - ] + self::$default, - 1 - ); - } - - public function testExtractPathBasedGlobal() { - $processor = new ExtensionProcessor(); - $processor->extractInfo( - $this->dir, - [ - 'ParserTestFiles' => [ - 'tests/parserTests.txt', - 'tests/extraParserTests.txt', - ], - 'ServiceWiringFiles' => [ - 'includes/ServiceWiring.php' - ], - ] + self::$default, - 1 - ); - $globals = $processor->getExtractedInfo()['globals']; - $this->assertArrayHasKey( 'wgParserTestFiles', $globals ); - $this->assertSame( [ - "{$this->dirname}/tests/parserTests.txt", - "{$this->dirname}/tests/extraParserTests.txt" - ], $globals['wgParserTestFiles'] ); - $this->assertArrayHasKey( 'wgServiceWiringFiles', $globals ); - $this->assertSame( [ - "{$this->dirname}/includes/ServiceWiring.php" - ], $globals['wgServiceWiringFiles'] ); - } - - public function testGetRequirements() { - $info = self::$default + [ - 'requires' => [ - 'MediaWiki' => '>= 1.25.0', - 'platform' => [ - 'php' => '>= 5.5.9' - ], - 'extensions' => [ - 'Bar' => '*' - ] - ] - ]; - $processor = new ExtensionProcessor(); - $this->assertSame( - $info['requires'], - $processor->getRequirements( $info, false ) - ); - $this->assertSame( - [], - $processor->getRequirements( [], false ) - ); - } - - public function testGetDevRequirements() { - $info = self::$default + [ - 'dev-requires' => [ - 'MediaWiki' => '>= 1.31.0', - 'platform' => [ - 'ext-foo' => '*', - ], - 'skins' => [ - 'Baz' => '*', - ], - 'extensions' => [ - 'Biz' => '*', - ], - ], - ]; - $processor = new ExtensionProcessor(); - $this->assertSame( - $info['dev-requires'], - $processor->getRequirements( $info, true ) - ); - // Set some standard requirements, so we can test merging - $info['requires'] = [ - 'MediaWiki' => '>= 1.25.0', - 'platform' => [ - 'php' => '>= 5.5.9' - ], - 'extensions' => [ - 'Bar' => '*' - ] - ]; - $this->assertSame( - [ - 'MediaWiki' => '>= 1.25.0 >= 1.31.0', - 'platform' => [ - 'php' => '>= 5.5.9', - 'ext-foo' => '*', - ], - 'extensions' => [ - 'Bar' => '*', - 'Biz' => '*', - ], - 'skins' => [ - 'Baz' => '*', - ], - ], - $processor->getRequirements( $info, true ) - ); - - // If there's no dev-requires, it just returns requires - unset( $info['dev-requires'] ); - $this->assertSame( - $info['requires'], - $processor->getRequirements( $info, true ) - ); - } - - public function testGetExtraAutoloaderPaths() { - $processor = new ExtensionProcessor(); - $this->assertSame( - [ "{$this->dirname}/vendor/autoload.php" ], - $processor->getExtraAutoloaderPaths( $this->dirname, [ - 'load_composer_autoloader' => true, - ] ) - ); - } - - /** - * Verify that extension.schema.json is in sync with ExtensionProcessor - * - * @coversNothing - */ - public function testGlobalSettingsDocumentedInSchema() { - global $IP; - $globalSettings = TestingAccessWrapper::newFromClass( - ExtensionProcessor::class )->globalSettings; - - $version = ExtensionRegistry::MANIFEST_VERSION; - $schema = FormatJson::decode( - file_get_contents( "$IP/docs/extension.schema.v$version.json" ), - true - ); - $missing = []; - foreach ( $globalSettings as $global ) { - if ( !isset( $schema['properties'][$global] ) ) { - $missing[] = $global; - } - } - - $this->assertEquals( [], $missing, - "The following global settings are not documented in docs/extension.schema.json" ); - } -} - -/** - * Allow overriding the default value of $this->globals - * so we can test merging - */ -class MockExtensionProcessor extends ExtensionProcessor { - public function __construct( $globals = [] ) { - $this->globals = $globals + $this->globals; - } -} diff --git a/tests/phpunit/includes/search/SearchIndexFieldTest.php b/tests/phpunit/includes/search/SearchIndexFieldTest.php deleted file mode 100644 index 8b4119e0d3..0000000000 --- a/tests/phpunit/includes/search/SearchIndexFieldTest.php +++ /dev/null @@ -1,56 +0,0 @@ -getMockBuilder( SearchIndexFieldDefinition::class ) - ->setMethods( [ 'getMapping' ] ) - ->setConstructorArgs( [ $n1, $t1 ] ) - ->getMock(); - $field2 = - $this->getMockBuilder( SearchIndexFieldDefinition::class ) - ->setMethods( [ 'getMapping' ] ) - ->setConstructorArgs( [ $n2, $t2 ] ) - ->getMock(); - - if ( $result ) { - $this->assertNotFalse( $field1->merge( $field2 ) ); - } else { - $this->assertFalse( $field1->merge( $field2 ) ); - } - - $field1->setFlag( 0xFF ); - $this->assertFalse( $field1->merge( $field2 ) ); - - $field1->setMergeCallback( - function ( $a, $b ) { - return "test"; - } - ); - $this->assertEquals( "test", $field1->merge( $field2 ) ); - } - -} diff --git a/tests/phpunit/includes/session/MetadataMergeExceptionTest.php b/tests/phpunit/includes/session/MetadataMergeExceptionTest.php deleted file mode 100644 index 8cb4302a4e..0000000000 --- a/tests/phpunit/includes/session/MetadataMergeExceptionTest.php +++ /dev/null @@ -1,30 +0,0 @@ - 'bar' ]; - - $ex = new MetadataMergeException(); - $this->assertInstanceOf( \UnexpectedValueException::class, $ex ); - $this->assertSame( [], $ex->getContext() ); - - $ex2 = new MetadataMergeException( 'Message', 42, $ex, $data ); - $this->assertSame( 'Message', $ex2->getMessage() ); - $this->assertSame( 42, $ex2->getCode() ); - $this->assertSame( $ex, $ex2->getPrevious() ); - $this->assertSame( $data, $ex2->getContext() ); - - $ex->setContext( $data ); - $this->assertSame( $data, $ex->getContext() ); - } - -} diff --git a/tests/phpunit/includes/session/SessionIdTest.php b/tests/phpunit/includes/session/SessionIdTest.php deleted file mode 100644 index 2b06d971a6..0000000000 --- a/tests/phpunit/includes/session/SessionIdTest.php +++ /dev/null @@ -1,22 +0,0 @@ -assertSame( 'foo', $id->getId() ); - $this->assertSame( 'foo', (string)$id ); - $id->setId( 'bar' ); - $this->assertSame( 'bar', $id->getId() ); - $this->assertSame( 'bar', (string)$id ); - } - -} diff --git a/tests/phpunit/includes/skins/SkinFactoryTest.php b/tests/phpunit/includes/skins/SkinFactoryTest.php deleted file mode 100644 index 4289fd9188..0000000000 --- a/tests/phpunit/includes/skins/SkinFactoryTest.php +++ /dev/null @@ -1,82 +0,0 @@ -register( 'fallback', 'Fallback', function () { - return new SkinFallback(); - } ); - $this->assertTrue( true ); // No exception thrown - $this->setExpectedException( InvalidArgumentException::class ); - $factory->register( 'invalid', 'Invalid', 'Invalid callback' ); - } - - /** - * @covers SkinFactory::makeSkin - */ - public function testMakeSkinWithNoBuilders() { - $factory = new SkinFactory(); - $this->setExpectedException( SkinException::class ); - $factory->makeSkin( 'nobuilderregistered' ); - } - - /** - * @covers SkinFactory::makeSkin - */ - public function testMakeSkinWithInvalidCallback() { - $factory = new SkinFactory(); - $factory->register( 'unittest', 'Unittest', function () { - return true; // Not a Skin object - } ); - $this->setExpectedException( UnexpectedValueException::class ); - $factory->makeSkin( 'unittest' ); - } - - /** - * @covers SkinFactory::makeSkin - */ - public function testMakeSkinWithValidCallback() { - $factory = new SkinFactory(); - $factory->register( 'testfallback', 'TestFallback', function () { - return new SkinFallback(); - } ); - - $skin = $factory->makeSkin( 'testfallback' ); - $this->assertInstanceOf( Skin::class, $skin ); - $this->assertInstanceOf( SkinFallback::class, $skin ); - $this->assertEquals( 'fallback', $skin->getSkinName() ); - } - - /** - * @covers Skin::__construct - * @covers Skin::getSkinName - */ - public function testGetSkinName() { - $skin = new SkinFallback(); - $this->assertEquals( 'fallback', $skin->getSkinName(), 'Default' ); - $skin = new SkinFallback( 'testname' ); - $this->assertEquals( 'testname', $skin->getSkinName(), 'Constructor argument' ); - } - - /** - * @covers SkinFactory::getSkinNames - */ - public function testGetSkinNames() { - $factory = new SkinFactory(); - // A fake callback we can use that will never be called - $callback = function () { - // NOP - }; - $factory->register( 'skin1', 'Skin1', $callback ); - $factory->register( 'skin2', 'Skin2', $callback ); - $names = $factory->getSkinNames(); - $this->assertArrayHasKey( 'skin1', $names ); - $this->assertArrayHasKey( 'skin2', $names ); - $this->assertEquals( 'Skin1', $names['skin1'] ); - $this->assertEquals( 'Skin2', $names['skin2'] ); - } -} diff --git a/tests/phpunit/includes/title/ForeignTitleTest.php b/tests/phpunit/includes/title/ForeignTitleTest.php deleted file mode 100644 index f2fccc7577..0000000000 --- a/tests/phpunit/includes/title/ForeignTitleTest.php +++ /dev/null @@ -1,103 +0,0 @@ -assertEquals( true, $title->isNamespaceIdKnown() ); - $this->assertEquals( $expectedId, $title->getNamespaceId() ); - $this->assertEquals( $expectedName, $title->getNamespaceName() ); - $this->assertEquals( $expectedText, $title->getText() ); - } - - public function testUnknownNamespaceCheck() { - $title = new ForeignTitle( null, 'this', 'that' ); - - $this->assertEquals( false, $title->isNamespaceIdKnown() ); - $this->assertEquals( 'this', $title->getNamespaceName() ); - $this->assertEquals( 'that', $title->getText() ); - } - - public function testUnknownNamespaceError() { - $this->setExpectedException( MWException::class ); - $title = new ForeignTitle( null, 'this', 'that' ); - $title->getNamespaceId(); - } - - public function fullTextProvider() { - return [ - [ - new ForeignTitle( 20, 'Contributor', 'JohnDoe' ), - 'Contributor:JohnDoe' - ], - [ - new ForeignTitle( '1', 'Discussion', 'Capital' ), - 'Discussion:Capital' - ], - [ - new ForeignTitle( 0, '', 'MainNamespace' ), - 'MainNamespace' - ], - [ - new ForeignTitle( 4, 'Some ns', 'Article title with spaces' ), - 'Some_ns:Article_title_with_spaces' - ], - ]; - } - - /** - * @dataProvider fullTextProvider - */ - public function testFullText( ForeignTitle $title, $fullText ) { - $this->assertEquals( $fullText, $title->getFullText() ); - } -} diff --git a/tests/phpunit/includes/title/NamespaceAwareForeignTitleFactoryTest.php b/tests/phpunit/includes/title/NamespaceAwareForeignTitleFactoryTest.php deleted file mode 100644 index 9aa3578da7..0000000000 --- a/tests/phpunit/includes/title/NamespaceAwareForeignTitleFactoryTest.php +++ /dev/null @@ -1,101 +0,0 @@ - '', 1 => 'Talk', 100 => 'Portal', 9000 => 'Bogus' - ]; - - $factory = new NamespaceAwareForeignTitleFactory( $foreignNamespaces ); - $testTitle = $factory->createForeignTitle( $title, $ns ); - - $this->assertEquals( $testTitle->isNamespaceIdKnown(), - $foreignTitle->isNamespaceIdKnown() ); - - if ( - $testTitle->isNamespaceIdKnown() && - $foreignTitle->isNamespaceIdKnown() - ) { - $this->assertEquals( $testTitle->getNamespaceId(), - $foreignTitle->getNamespaceId() ); - } - - $this->assertEquals( $testTitle->getNamespaceName(), - $foreignTitle->getNamespaceName() ); - $this->assertEquals( $testTitle->getText(), $foreignTitle->getText() ); - } -} diff --git a/tests/phpunit/includes/title/TitleValueTest.php b/tests/phpunit/includes/title/TitleValueTest.php deleted file mode 100644 index bbeb068007..0000000000 --- a/tests/phpunit/includes/title/TitleValueTest.php +++ /dev/null @@ -1,149 +0,0 @@ -assertEquals( $ns, $title->getNamespace() ); - $this->assertTrue( $title->inNamespace( $ns ) ); - $this->assertEquals( $text, $title->getText() ); - $this->assertEquals( $fragment, $title->getFragment() ); - $this->assertEquals( $hasFragment, $title->hasFragment() ); - $this->assertEquals( $interwiki, $title->getInterwiki() ); - $this->assertEquals( $hasInterwiki, $title->isExternal() ); - } - - public function badConstructorProvider() { - return [ - [ 'foo', 'title', 'fragment', '' ], - [ null, 'title', 'fragment', '' ], - [ 2.3, 'title', 'fragment', '' ], - - [ NS_MAIN, 5, 'fragment', '' ], - [ NS_MAIN, null, 'fragment', '' ], - [ NS_USER, '', 'fragment', '' ], - [ NS_MAIN, 'foo bar', '', '' ], - [ NS_MAIN, 'bar_', '', '' ], - [ NS_MAIN, '_foo', '', '' ], - [ NS_MAIN, ' eek ', '', '' ], - - [ NS_MAIN, 'title', 5, '' ], - [ NS_MAIN, 'title', null, '' ], - [ NS_MAIN, 'title', [], '' ], - - [ NS_MAIN, 'title', '', 5 ], - [ NS_MAIN, 'title', null, 5 ], - [ NS_MAIN, 'title', [], 5 ], - ]; - } - - /** - * @dataProvider badConstructorProvider - */ - public function testConstructionErrors( $ns, $text, $fragment, $interwiki ) { - $this->setExpectedException( InvalidArgumentException::class ); - new TitleValue( $ns, $text, $fragment, $interwiki ); - } - - public function fragmentTitleProvider() { - return [ - [ new TitleValue( NS_MAIN, 'Test' ), 'foo' ], - [ new TitleValue( NS_TALK, 'Test', 'foo' ), '' ], - [ new TitleValue( NS_CATEGORY, 'Test', 'foo' ), 'bar' ], - ]; - } - - /** - * @dataProvider fragmentTitleProvider - */ - public function testCreateFragmentTitle( TitleValue $title, $fragment ) { - $fragmentTitle = $title->createFragmentTarget( $fragment ); - - $this->assertEquals( $title->getNamespace(), $fragmentTitle->getNamespace() ); - $this->assertEquals( $title->getText(), $fragmentTitle->getText() ); - $this->assertEquals( $fragment, $fragmentTitle->getFragment() ); - } - - public function getTextProvider() { - return [ - [ 'Foo', 'Foo' ], - [ 'Foo_Bar', 'Foo Bar' ], - ]; - } - - /** - * @dataProvider getTextProvider - */ - public function testGetText( $dbkey, $text ) { - $title = new TitleValue( NS_MAIN, $dbkey ); - - $this->assertEquals( $text, $title->getText() ); - } - - public function provideTestToString() { - yield [ - new TitleValue( 0, 'Foo' ), - '0:Foo' - ]; - yield [ - new TitleValue( 1, 'Bar_Baz' ), - '1:Bar_Baz' - ]; - yield [ - new TitleValue( 9, 'JoJo', 'Frag' ), - '9:JoJo#Frag' - ]; - yield [ - new TitleValue( 200, 'tea', 'Fragment', 'wikicode' ), - 'wikicode:200:tea#Fragment' - ]; - } - - /** - * @dataProvider provideTestToString - */ - public function testToString( TitleValue $value, $expected ) { - $this->assertSame( - $expected, - $value->__toString() - ); - } -} diff --git a/tests/phpunit/includes/user/UserArrayFromResultTest.php b/tests/phpunit/includes/user/UserArrayFromResultTest.php deleted file mode 100644 index 4cbfe462dd..0000000000 --- a/tests/phpunit/includes/user/UserArrayFromResultTest.php +++ /dev/null @@ -1,110 +0,0 @@ -getMockBuilder( Wikimedia\Rdbms\ResultWrapper::class ) - ->disableOriginalConstructor(); - - $resultWrapper = $resultWrapper->getMock(); - $resultWrapper->expects( $this->atLeastOnce() ) - ->method( 'current' ) - ->will( $this->returnValue( $row ) ); - $resultWrapper->expects( $this->any() ) - ->method( 'numRows' ) - ->will( $this->returnValue( $numRows ) ); - - return $resultWrapper; - } - - private function getRowWithUsername( $username = 'fooUser' ) { - $row = new stdClass(); - $row->user_name = $username; - return $row; - } - - /** - * @covers UserArrayFromResult::__construct - */ - public function testConstructionWithFalseRow() { - $row = false; - $resultWrapper = $this->getMockResultWrapper( $row ); - - $object = new UserArrayFromResult( $resultWrapper ); - - $this->assertEquals( $resultWrapper, $object->res ); - $this->assertSame( 0, $object->key ); - $this->assertEquals( $row, $object->current ); - } - - /** - * @covers UserArrayFromResult::__construct - */ - public function testConstructionWithRow() { - $username = 'addshore'; - $row = $this->getRowWithUsername( $username ); - $resultWrapper = $this->getMockResultWrapper( $row ); - - $object = new UserArrayFromResult( $resultWrapper ); - - $this->assertEquals( $resultWrapper, $object->res ); - $this->assertSame( 0, $object->key ); - $this->assertInstanceOf( User::class, $object->current ); - $this->assertEquals( $username, $object->current->mName ); - } - - public static function provideNumberOfRows() { - return [ - [ 0 ], - [ 1 ], - [ 122 ], - ]; - } - - /** - * @dataProvider provideNumberOfRows - * @covers UserArrayFromResult::count - */ - public function testCountWithVaryingValues( $numRows ) { - $object = new UserArrayFromResult( $this->getMockResultWrapper( - $this->getRowWithUsername(), - $numRows - ) ); - $this->assertEquals( $numRows, $object->count() ); - } - - /** - * @covers UserArrayFromResult::current - */ - public function testCurrentAfterConstruction() { - $username = 'addshore'; - $userRow = $this->getRowWithUsername( $username ); - $object = new UserArrayFromResult( $this->getMockResultWrapper( $userRow ) ); - $this->assertInstanceOf( User::class, $object->current() ); - $this->assertEquals( $username, $object->current()->mName ); - } - - public function provideTestValid() { - return [ - [ $this->getRowWithUsername(), true ], - [ false, false ], - ]; - } - - /** - * @dataProvider provideTestValid - * @covers UserArrayFromResult::valid - */ - public function testValid( $input, $expected ) { - $object = new UserArrayFromResult( $this->getMockResultWrapper( $input ) ); - $this->assertEquals( $expected, $object->valid() ); - } - - // @todo unit test for key() - // @todo unit test for next() - // @todo unit test for rewind() -} diff --git a/tests/phpunit/includes/watcheditem/NoWriteWatchedItemStoreUnitTest.php b/tests/phpunit/includes/watcheditem/NoWriteWatchedItemStoreUnitTest.php deleted file mode 100644 index f424b21b3e..0000000000 --- a/tests/phpunit/includes/watcheditem/NoWriteWatchedItemStoreUnitTest.php +++ /dev/null @@ -1,250 +0,0 @@ -getMockForAbstractClass( WatchedItemStoreInterface::class ); - $innerService->expects( $this->never() )->method( 'addWatch' ); - $noWriteService = new NoWriteWatchedItemStore( $innerService ); - - $this->setExpectedException( DBReadOnlyError::class ); - $noWriteService->addWatch( - new UserIdentityValue( 1, 'MockUser', 0 ), new TitleValue( 0, 'Foo' ) ); - } - - public function testAddWatchBatchForUser() { - /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ - $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); - $innerService->expects( $this->never() )->method( 'addWatchBatchForUser' ); - $noWriteService = new NoWriteWatchedItemStore( $innerService ); - - $this->setExpectedException( DBReadOnlyError::class ); - $noWriteService->addWatchBatchForUser( new UserIdentityValue( 1, 'MockUser', 0 ), [] ); - } - - public function testRemoveWatch() { - /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ - $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); - $innerService->expects( $this->never() )->method( 'removeWatch' ); - $noWriteService = new NoWriteWatchedItemStore( $innerService ); - - $this->setExpectedException( DBReadOnlyError::class ); - $noWriteService->removeWatch( - new UserIdentityValue( 1, 'MockUser', 0 ), new TitleValue( 0, 'Foo' ) ); - } - - public function testSetNotificationTimestampsForUser() { - /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ - $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); - $innerService->expects( $this->never() )->method( 'setNotificationTimestampsForUser' ); - $noWriteService = new NoWriteWatchedItemStore( $innerService ); - - $this->setExpectedException( DBReadOnlyError::class ); - $noWriteService->setNotificationTimestampsForUser( - new UserIdentityValue( 1, 'MockUser', 0 ), - 'timestamp', - [] - ); - } - - public function testUpdateNotificationTimestamp() { - /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ - $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); - $innerService->expects( $this->never() )->method( 'updateNotificationTimestamp' ); - $noWriteService = new NoWriteWatchedItemStore( $innerService ); - - $this->setExpectedException( DBReadOnlyError::class ); - $noWriteService->updateNotificationTimestamp( - new UserIdentityValue( 1, 'MockUser', 0 ), - new TitleValue( 0, 'Foo' ), - 'timestamp' - ); - } - - public function testResetNotificationTimestamp() { - /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ - $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); - $innerService->expects( $this->never() )->method( 'resetNotificationTimestamp' ); - $noWriteService = new NoWriteWatchedItemStore( $innerService ); - - $this->setExpectedException( DBReadOnlyError::class ); - $noWriteService->resetNotificationTimestamp( - new UserIdentityValue( 1, 'MockUser', 0 ), - new TitleValue( 0, 'Foo' ) - ); - } - - public function testCountWatchedItems() { - /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ - $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); - $innerService->expects( $this->once() )->method( 'countWatchedItems' )->willReturn( __METHOD__ ); - $noWriteService = new NoWriteWatchedItemStore( $innerService ); - - $return = $noWriteService->countWatchedItems( - new UserIdentityValue( 1, 'MockUser', 0 ) - ); - $this->assertEquals( __METHOD__, $return ); - } - - public function testCountWatchers() { - /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ - $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); - $innerService->expects( $this->once() )->method( 'countWatchers' )->willReturn( __METHOD__ ); - $noWriteService = new NoWriteWatchedItemStore( $innerService ); - - $return = $noWriteService->countWatchers( - new TitleValue( 0, 'Foo' ) - ); - $this->assertEquals( __METHOD__, $return ); - } - - public function testCountVisitingWatchers() { - /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ - $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); - $innerService->expects( $this->once() ) - ->method( 'countVisitingWatchers' ) - ->willReturn( __METHOD__ ); - $noWriteService = new NoWriteWatchedItemStore( $innerService ); - - $return = $noWriteService->countVisitingWatchers( - new TitleValue( 0, 'Foo' ), - 9 - ); - $this->assertEquals( __METHOD__, $return ); - } - - public function testCountWatchersMultiple() { - /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ - $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); - $innerService->expects( $this->once() ) - ->method( 'countVisitingWatchersMultiple' ) - ->willReturn( __METHOD__ ); - $noWriteService = new NoWriteWatchedItemStore( $innerService ); - - $return = $noWriteService->countWatchersMultiple( - [ new TitleValue( 0, 'Foo' ) ], - [] - ); - $this->assertEquals( __METHOD__, $return ); - } - - public function testCountVisitingWatchersMultiple() { - /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ - $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); - $innerService->expects( $this->once() ) - ->method( 'countVisitingWatchersMultiple' ) - ->willReturn( __METHOD__ ); - $noWriteService = new NoWriteWatchedItemStore( $innerService ); - - $return = $noWriteService->countVisitingWatchersMultiple( - [ [ new TitleValue( 0, 'Foo' ), 99 ] ], - 11 - ); - $this->assertEquals( __METHOD__, $return ); - } - - public function testGetWatchedItem() { - /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ - $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); - $innerService->expects( $this->once() )->method( 'getWatchedItem' )->willReturn( __METHOD__ ); - $noWriteService = new NoWriteWatchedItemStore( $innerService ); - - $return = $noWriteService->getWatchedItem( - new UserIdentityValue( 1, 'MockUser', 0 ), - new TitleValue( 0, 'Foo' ) - ); - $this->assertEquals( __METHOD__, $return ); - } - - public function testLoadWatchedItem() { - /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ - $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); - $innerService->expects( $this->once() )->method( 'loadWatchedItem' )->willReturn( __METHOD__ ); - $noWriteService = new NoWriteWatchedItemStore( $innerService ); - - $return = $noWriteService->loadWatchedItem( - new UserIdentityValue( 1, 'MockUser', 0 ), - new TitleValue( 0, 'Foo' ) - ); - $this->assertEquals( __METHOD__, $return ); - } - - public function testGetWatchedItemsForUser() { - /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ - $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); - $innerService->expects( $this->once() ) - ->method( 'getWatchedItemsForUser' ) - ->willReturn( __METHOD__ ); - $noWriteService = new NoWriteWatchedItemStore( $innerService ); - - $return = $noWriteService->getWatchedItemsForUser( - new UserIdentityValue( 1, 'MockUser', 0 ), - [] - ); - $this->assertEquals( __METHOD__, $return ); - } - - public function testIsWatched() { - /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ - $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); - $innerService->expects( $this->once() )->method( 'isWatched' )->willReturn( __METHOD__ ); - $noWriteService = new NoWriteWatchedItemStore( $innerService ); - - $return = $noWriteService->isWatched( - new UserIdentityValue( 1, 'MockUser', 0 ), - new TitleValue( 0, 'Foo' ) - ); - $this->assertEquals( __METHOD__, $return ); - } - - public function testGetNotificationTimestampsBatch() { - /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ - $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); - $innerService->expects( $this->once() ) - ->method( 'getNotificationTimestampsBatch' ) - ->willReturn( __METHOD__ ); - $noWriteService = new NoWriteWatchedItemStore( $innerService ); - - $return = $noWriteService->getNotificationTimestampsBatch( - new UserIdentityValue( 1, 'MockUser', 0 ), - [ new TitleValue( 0, 'Foo' ) ] - ); - $this->assertEquals( __METHOD__, $return ); - } - - public function testCountUnreadNotifications() { - /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ - $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); - $innerService->expects( $this->once() ) - ->method( 'countUnreadNotifications' ) - ->willReturn( __METHOD__ ); - $noWriteService = new NoWriteWatchedItemStore( $innerService ); - - $return = $noWriteService->countUnreadNotifications( - new UserIdentityValue( 1, 'MockUser', 0 ), - 88 - ); - $this->assertEquals( __METHOD__, $return ); - } - - public function testDuplicateAllAssociatedEntries() { - /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ - $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); - $noWriteService = new NoWriteWatchedItemStore( $innerService ); - - $this->setExpectedException( DBReadOnlyError::class ); - $noWriteService->duplicateAllAssociatedEntries( - new TitleValue( 0, 'Foo' ), - new TitleValue( 0, 'Bar' ) - ); - } - -} diff --git a/tests/phpunit/languages/SpecialPageAliasTest.php b/tests/phpunit/languages/SpecialPageAliasTest.php index d406c88933..cce9d0eb0f 100644 --- a/tests/phpunit/languages/SpecialPageAliasTest.php +++ b/tests/phpunit/languages/SpecialPageAliasTest.php @@ -11,7 +11,7 @@ * * @author Katie Filbert < aude.wiki@gmail.com > */ -class SpecialPageAliasTest extends MediaWikiTestCase { +class SpecialPageAliasTest extends \MediaWikiUnitTestCase { /** * @coversNothing diff --git a/tests/phpunit/unit/includes/FauxResponseTest.php b/tests/phpunit/unit/includes/FauxResponseTest.php new file mode 100644 index 0000000000..5e208aceda --- /dev/null +++ b/tests/phpunit/unit/includes/FauxResponseTest.php @@ -0,0 +1,146 @@ +response = new FauxResponse; + } + + /** + * @covers FauxResponse::setCookie + * @covers FauxResponse::getCookie + * @covers FauxResponse::getCookieData + * @covers FauxResponse::getCookies + */ + public function testCookie() { + $expire = time() + 100; + $cookie = [ + 'value' => 'val', + 'path' => '/path', + 'domain' => 'domain', + 'secure' => true, + 'httpOnly' => false, + 'raw' => false, + 'expire' => $expire, + ]; + + $this->assertEquals( null, $this->response->getCookie( 'xkey' ), 'Non-existing cookie' ); + $this->response->setCookie( 'key', 'val', $expire, [ + 'prefix' => 'x', + 'path' => '/path', + 'domain' => 'domain', + 'secure' => 1, + 'httpOnly' => 0, + ] ); + $this->assertEquals( 'val', $this->response->getCookie( 'xkey' ), 'Existing cookie' ); + $this->assertEquals( $cookie, $this->response->getCookieData( 'xkey' ), + 'Existing cookie (data)' ); + $this->assertEquals( [ 'xkey' => $cookie ], $this->response->getCookies(), + 'Existing cookies' ); + } + + /** + * @covers FauxResponse::getheader + * @covers FauxResponse::header + */ + public function testHeader() { + $this->assertEquals( null, $this->response->getHeader( 'Location' ), 'Non-existing header' ); + + $this->response->header( 'Location: http://localhost/' ); + $this->assertEquals( + 'http://localhost/', + $this->response->getHeader( 'Location' ), + 'Set header' + ); + + $this->response->header( 'Location: http://127.0.0.1/' ); + $this->assertEquals( + 'http://127.0.0.1/', + $this->response->getHeader( 'Location' ), + 'Same header' + ); + + $this->response->header( 'Location: http://127.0.0.2/', false ); + $this->assertEquals( + 'http://127.0.0.1/', + $this->response->getHeader( 'Location' ), + 'Same header with override disabled' + ); + + $this->response->header( 'Location: http://localhost/' ); + $this->assertEquals( + 'http://localhost/', + $this->response->getHeader( 'LOCATION' ), + 'Get header case insensitive' + ); + } + + /** + * @covers FauxResponse::getStatusCode + */ + public function testResponseCode() { + $this->response->header( 'HTTP/1.1 200' ); + $this->assertEquals( 200, $this->response->getStatusCode(), 'Header with no message' ); + + $this->response->header( 'HTTP/1.x 201' ); + $this->assertEquals( + 201, + $this->response->getStatusCode(), + 'Header with no message and protocol 1.x' + ); + + $this->response->header( 'HTTP/1.1 202 OK' ); + $this->assertEquals( 202, $this->response->getStatusCode(), 'Normal header' ); + + $this->response->header( 'HTTP/1.x 203 OK' ); + $this->assertEquals( + 203, + $this->response->getStatusCode(), + 'Normal header with no message and protocol 1.x' + ); + + $this->response->header( 'HTTP/1.x 204 OK', false, 205 ); + $this->assertEquals( + 205, + $this->response->getStatusCode(), + '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, + $this->response->getStatusCode(), + 'Third parameter with another header' + ); + } +} diff --git a/tests/phpunit/unit/includes/FormOptionsInitializationTest.php b/tests/phpunit/unit/includes/FormOptionsInitializationTest.php new file mode 100644 index 0000000000..708956d260 --- /dev/null +++ b/tests/phpunit/unit/includes/FormOptionsInitializationTest.php @@ -0,0 +1,70 @@ +object = TestingAccessWrapper::newFromObject( new FormOptions() ); + } + + /** + * @covers FormOptions::add + */ + public function testAddStringOption() { + $this->object->add( 'foo', 'string value' ); + $this->assertEquals( + [ + 'foo' => [ + 'default' => 'string value', + 'consumed' => false, + 'type' => FormOptions::STRING, + 'value' => null, + ] + ], + $this->object->options + ); + } + + /** + * @covers FormOptions::add + */ + public function testAddIntegers() { + $this->object->add( 'one', 1 ); + $this->object->add( 'negone', -1 ); + $this->assertEquals( + [ + 'negone' => [ + 'default' => -1, + 'value' => null, + 'consumed' => false, + 'type' => FormOptions::INT, + ], + 'one' => [ + 'default' => 1, + 'value' => null, + 'consumed' => false, + 'type' => FormOptions::INT, + ] + ], + $this->object->options + ); + } +} diff --git a/tests/phpunit/unit/includes/FormOptionsTest.php b/tests/phpunit/unit/includes/FormOptionsTest.php new file mode 100644 index 0000000000..c14595bc4a --- /dev/null +++ b/tests/phpunit/unit/includes/FormOptionsTest.php @@ -0,0 +1,105 @@ +object = new FormOptions; + $this->object->add( 'string1', 'string one' ); + $this->object->add( 'string2', 'string two' ); + $this->object->add( 'integer', 0 ); + $this->object->add( 'float', 0.0 ); + $this->object->add( 'intnull', 0, FormOptions::INTNULL ); + } + + /** Helpers for testGuessType() */ + /* @{ */ + private function assertGuessBoolean( $data ) { + $this->guess( FormOptions::BOOL, $data ); + } + + private function assertGuessInt( $data ) { + $this->guess( FormOptions::INT, $data ); + } + + private function assertGuessFloat( $data ) { + $this->guess( FormOptions::FLOAT, $data ); + } + + private function assertGuessString( $data ) { + $this->guess( FormOptions::STRING, $data ); + } + + private function assertGuessArray( $data ) { + $this->guess( FormOptions::ARR, $data ); + } + + /** Generic helper */ + private function guess( $expected, $data ) { + $this->assertEquals( + $expected, + FormOptions::guessType( $data ) + ); + } + + /* @} */ + + /** + * Reuse helpers above assertGuessBoolean assertGuessInt assertGuessString + * @covers FormOptions::guessType + */ + public function testGuessTypeDetection() { + $this->assertGuessBoolean( true ); + $this->assertGuessBoolean( false ); + + $this->assertGuessInt( 0 ); + $this->assertGuessInt( -5 ); + $this->assertGuessInt( 5 ); + $this->assertGuessInt( 0x0F ); + + $this->assertGuessFloat( 0.0 ); + $this->assertGuessFloat( 1.5 ); + $this->assertGuessFloat( 1e3 ); + + $this->assertGuessString( 'true' ); + $this->assertGuessString( 'false' ); + $this->assertGuessString( '5' ); + $this->assertGuessString( '0' ); + $this->assertGuessString( '1.5' ); + + $this->assertGuessArray( [ 'foo' ] ); + } + + /** + * @expectedException MWException + * @covers FormOptions::guessType + */ + public function testGuessTypeOnNullThrowException() { + $this->object->guessType( null ); + } +} diff --git a/tests/phpunit/unit/includes/LicensesTest.php b/tests/phpunit/unit/includes/LicensesTest.php new file mode 100644 index 0000000000..e5a6baeff6 --- /dev/null +++ b/tests/phpunit/unit/includes/LicensesTest.php @@ -0,0 +1,25 @@ + 'FooField', + 'type' => 'select', + 'section' => 'description', + 'id' => 'wpLicense', + 'label' => 'A label text', # Note can't test label-message because $wgOut is not defined + 'name' => 'AnotherName', + 'licenses' => $str, + ] ); + $this->assertThat( $lc, $this->isInstanceOf( Licenses::class ) ); + } +} diff --git a/tests/phpunit/unit/includes/Rest/HeaderContainerTest.php b/tests/phpunit/unit/includes/Rest/HeaderContainerTest.php new file mode 100644 index 0000000000..e65251e350 --- /dev/null +++ b/tests/phpunit/unit/includes/Rest/HeaderContainerTest.php @@ -0,0 +1,171 @@ + [ + [ + [ 'Test', 'foo' ] + ], + [ 'Test' => [ 'foo' ] ], + [ 'Test' => 'foo' ] + ], + 'replace' => [ + [ + [ 'Test', 'foo' ], + [ 'Test', 'bar' ], + ], + [ 'Test' => [ 'bar' ] ], + [ 'Test' => 'bar' ], + ], + 'array value' => [ + [ + [ 'Test', [ '1', '2' ] ], + [ 'Test', [ '3', '4' ] ], + ], + [ 'Test' => [ '3', '4' ] ], + [ 'Test' => '3, 4' ] + ], + 'preserve most recent case' => [ + [ + [ 'test', 'foo' ], + [ 'tesT', 'bar' ], + ], + [ 'tesT' => [ 'bar' ] ], + [ 'tesT' => 'bar' ] + ], + 'empty' => [ [], [], [] ], + ]; + } + + /** @dataProvider provideSetHeader */ + public function testSetHeader( $setOps, $headers, $lines ) { + $hc = new HeaderContainer; + foreach ( $setOps as list( $name, $value ) ) { + $hc->setHeader( $name, $value ); + } + $this->assertSame( $headers, $hc->getHeaders() ); + $this->assertSame( $lines, $hc->getHeaderLines() ); + } + + public static function provideAddHeader() { + return [ + 'simple' => [ + [ + [ 'Test', 'foo' ] + ], + [ 'Test' => [ 'foo' ] ], + [ 'Test' => 'foo' ] + ], + 'add' => [ + [ + [ 'Test', 'foo' ], + [ 'Test', 'bar' ], + ], + [ 'Test' => [ 'foo', 'bar' ] ], + [ 'Test' => 'foo, bar' ], + ], + 'array value' => [ + [ + [ 'Test', [ '1', '2' ] ], + [ 'Test', [ '3', '4' ] ], + ], + [ 'Test' => [ '1', '2', '3', '4' ] ], + [ 'Test' => '1, 2, 3, 4' ] + ], + 'preserve original case' => [ + [ + [ 'Test', 'foo' ], + [ 'tesT', 'bar' ], + ], + [ 'Test' => [ 'foo', 'bar' ] ], + [ 'Test' => 'foo, bar' ] + ], + ]; + } + + /** @dataProvider provideAddHeader */ + public function testAddHeader( $addOps, $headers, $lines ) { + $hc = new HeaderContainer; + foreach ( $addOps as list( $name, $value ) ) { + $hc->addHeader( $name, $value ); + } + $this->assertSame( $headers, $hc->getHeaders() ); + $this->assertSame( $lines, $hc->getHeaderLines() ); + } + + public static function provideRemoveHeader() { + return [ + 'simple' => [ + [ [ 'Test', 'foo' ] ], + [ 'Test' ], + [], + [] + ], + 'case mismatch' => [ + [ [ 'Test', 'foo' ] ], + [ 'tesT' ], + [], + [] + ], + 'remove nonexistent' => [ + [ [ 'A', '1' ] ], + [ 'B' ], + [ 'A' => [ '1' ] ], + [ 'A' => '1' ] + ], + ]; + } + + /** @dataProvider provideRemoveHeader */ + public function testRemoveHeader( $addOps, $removeOps, $headers, $lines ) { + $hc = new HeaderContainer; + foreach ( $addOps as list( $name, $value ) ) { + $hc->addHeader( $name, $value ); + } + foreach ( $removeOps as $name ) { + $hc->removeHeader( $name ); + } + $this->assertSame( $headers, $hc->getHeaders() ); + $this->assertSame( $lines, $hc->getHeaderLines() ); + } + + public function testHasHeader() { + $hc = new HeaderContainer; + $hc->addHeader( 'A', '1' ); + $hc->addHeader( 'B', '2' ); + $hc->addHeader( 'C', '3' ); + $hc->removeHeader( 'B' ); + $hc->removeHeader( 'c' ); + $this->assertTrue( $hc->hasHeader( 'A' ) ); + $this->assertTrue( $hc->hasHeader( 'a' ) ); + $this->assertFalse( $hc->hasHeader( 'B' ) ); + $this->assertFalse( $hc->hasHeader( 'c' ) ); + $this->assertFalse( $hc->hasHeader( 'C' ) ); + } + + public function testGetRawHeaderLines() { + $hc = new HeaderContainer; + $hc->addHeader( 'A', '1' ); + $hc->addHeader( 'a', '2' ); + $hc->addHeader( 'b', '3' ); + $hc->addHeader( 'Set-Cookie', 'x' ); + $hc->addHeader( 'SET-cookie', 'y' ); + $this->assertSame( + [ + 'A: 1, 2', + 'b: 3', + 'Set-Cookie: x', + 'Set-Cookie: y', + ], + $hc->getRawHeaderLines() + ); + } +} diff --git a/tests/phpunit/unit/includes/Rest/PathTemplateMatcher/PathMatcherTest.php b/tests/phpunit/unit/includes/Rest/PathTemplateMatcher/PathMatcherTest.php new file mode 100644 index 0000000000..f56024cfdf --- /dev/null +++ b/tests/phpunit/unit/includes/Rest/PathTemplateMatcher/PathMatcherTest.php @@ -0,0 +1,76 @@ + [], 'userData' => 0 ] ], + [ '/b', false ], + [ '/b/1', [ 'params' => [ 'x' => '1' ], 'userData' => 1 ] ], + [ '/c/1/d', [ 'params' => [ 'x' => '1' ], 'userData' => 2 ] ], + [ '/c/1/e', [ 'params' => [ 'x' => '1' ], 'userData' => 3 ] ], + [ '/c/000/e', [ 'params' => [ 'x' => '000' ], 'userData' => 3 ] ], + [ '/c/1/f', false ], + [ '/c//e', [ 'params' => [ 'x' => '' ], 'userData' => 3 ] ], + [ '/c///e', false ], + ]; + } + + public function createNormalRouter() { + $pm = new PathMatcher; + foreach ( self::$normalRoutes as $i => $route ) { + $pm->add( $route, $i ); + } + return $pm; + } + + /** @dataProvider provideConflictingRoutes */ + public function testAddConflict( $attempt, $expectedUserData, $expectedTemplate ) { + $pm = $this->createNormalRouter(); + $actualTemplate = null; + $actualUserData = null; + try { + $pm->add( $attempt, 'conflict' ); + } catch ( PathConflict $pc ) { + $actualTemplate = $pc->existingTemplate; + $actualUserData = $pc->existingUserData; + } + $this->assertSame( $expectedUserData, $actualUserData ); + $this->assertSame( $expectedTemplate, $actualTemplate ); + } + + /** @dataProvider provideMatch */ + public function testMatch( $path, $expectedResult ) { + $pm = $this->createNormalRouter(); + $result = $pm->match( $path ); + $this->assertSame( $expectedResult, $result ); + } +} diff --git a/tests/phpunit/unit/includes/Rest/StringStreamTest.php b/tests/phpunit/unit/includes/Rest/StringStreamTest.php new file mode 100644 index 0000000000..1e72239d1d --- /dev/null +++ b/tests/phpunit/unit/includes/Rest/StringStreamTest.php @@ -0,0 +1,130 @@ +write( $input ); + $ss->seek( 1 ); + $ss->seek( $offset, $whence ); + $destStream = fopen( 'php://memory', 'w+' ); + $ss->copyToStream( $destStream ); + fseek( $destStream, 0 ); + $result = stream_get_contents( $destStream ); + $this->assertSame( $expected, $result ); + } + + public function testGetSize() { + $ss = new StringStream; + $this->assertSame( 0, $ss->getSize() ); + $ss->write( "hello" ); + $this->assertSame( 5, $ss->getSize() ); + $ss->rewind(); + $this->assertSame( 5, $ss->getSize() ); + } + + public function testTell() { + $ss = new StringStream; + $this->assertSame( $ss->tell(), 0 ); + $ss->write( "abc" ); + $this->assertSame( $ss->tell(), 3 ); + $ss->seek( 0 ); + $ss->read( 1 ); + $this->assertSame( $ss->tell(), 1 ); + } + + public function testEof() { + $ss = new StringStream( 'abc' ); + $this->assertFalse( $ss->eof() ); + $ss->read( 1 ); + $this->assertFalse( $ss->eof() ); + $ss->read( 1 ); + $this->assertFalse( $ss->eof() ); + $ss->read( 1 ); + $this->assertTrue( $ss->eof() ); + $ss->rewind(); + $this->assertFalse( $ss->eof() ); + } + + public function testIsSeekable() { + $ss = new StringStream; + $this->assertTrue( $ss->isSeekable() ); + } + + public function testIsReadable() { + $ss = new StringStream; + $this->assertTrue( $ss->isReadable() ); + } + + public function testIsWritable() { + $ss = new StringStream; + $this->assertTrue( $ss->isWritable() ); + } + + public function testSeekWrite() { + $ss = new StringStream; + $this->assertSame( '', (string)$ss ); + $ss->write( 'a' ); + $this->assertSame( 'a', (string)$ss ); + $ss->write( 'b' ); + $this->assertSame( 'ab', (string)$ss ); + $ss->seek( 1 ); + $ss->write( 'c' ); + $this->assertSame( 'ac', (string)$ss ); + } + + /** @dataProvider provideSeekGetContents */ + public function testSeekGetContents( $input, $offset, $whence, $expected ) { + $ss = new StringStream( $input ); + $ss->seek( 1 ); + $ss->seek( $offset, $whence ); + $this->assertSame( $expected, $ss->getContents() ); + } + + public static function provideSeekRead() { + return [ + [ 'abcde', 0, SEEK_SET, 1, 'a' ], + [ 'abcde', 0, SEEK_SET, 2, 'ab' ], + [ 'abcde', 4, SEEK_SET, 2, 'e' ], + [ 'abcde', 5, SEEK_SET, 1, '' ], + [ 'abcde', 1, SEEK_CUR, 1, 'c' ], + [ 'abcde', 0, SEEK_END, 1, '' ], + [ 'abcde', -1, SEEK_END, 1, 'e' ], + ]; + } + + /** @dataProvider provideSeekRead */ + public function testSeekRead( $input, $offset, $whence, $length, $expected ) { + $ss = new StringStream( $input ); + $ss->seek( 1 ); + $ss->seek( $offset, $whence ); + $this->assertSame( $expected, $ss->read( $length ) ); + } + + /** @expectedException \InvalidArgumentException */ + public function testReadBeyondEnd() { + $ss = new StringStream( 'abc' ); + $ss->seek( 1, SEEK_END ); + } + + /** @expectedException \InvalidArgumentException */ + public function testReadBeforeStart() { + $ss = new StringStream( 'abc' ); + $ss->seek( -1 ); + } +} diff --git a/tests/phpunit/unit/includes/Revision/FallbackSlotRoleHandlerTest.php b/tests/phpunit/unit/includes/Revision/FallbackSlotRoleHandlerTest.php new file mode 100644 index 0000000000..17b3504952 --- /dev/null +++ b/tests/phpunit/unit/includes/Revision/FallbackSlotRoleHandlerTest.php @@ -0,0 +1,72 @@ +createMock( Title::class ); + } + + /** + * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::__construct + * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getRole() + * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getNameMessageKey() + * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getDefaultModel() + * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getOutputLayoutHints() + */ + public function testConstruction() { + $handler = new FallbackSlotRoleHandler( 'foo' ); + $this->assertSame( 'foo', $handler->getRole() ); + $this->assertSame( 'slot-name-foo', $handler->getNameMessageKey() ); + + $title = $this->makeBlankTitleObject(); + $this->assertSame( CONTENT_MODEL_TEXT, $handler->getDefaultModel( $title ) ); + + $hints = $handler->getOutputLayoutHints(); + $this->assertArrayHasKey( 'display', $hints ); + $this->assertArrayHasKey( 'region', $hints ); + $this->assertArrayHasKey( 'placement', $hints ); + } + + /** + * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::isAllowedModel() + */ + public function testIsAllowedModel() { + $handler = new FallbackSlotRoleHandler( 'foo', 'FooModel' ); + + // For the fallback handler, no models are allowed + $title = $this->makeBlankTitleObject(); + $this->assertFalse( $handler->isAllowedModel( 'FooModel', $title ) ); + $this->assertFalse( $handler->isAllowedModel( 'QuaxModel', $title ) ); + } + + /** + * @covers \MediaWiki\Revision\SlotRoleHandler::isAllowedModel() + */ + public function testIsAllowedOn() { + $handler = new FallbackSlotRoleHandler( 'foo', 'FooModel' ); + + $title = $this->makeBlankTitleObject(); + $this->assertFalse( $handler->isAllowedOn( $title ) ); + } + + /** + * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::supportsArticleCount() + */ + public function testSupportsArticleCount() { + $handler = new FallbackSlotRoleHandler( 'foo', 'FooModel' ); + + $this->assertFalse( $handler->supportsArticleCount() ); + } + +} diff --git a/tests/phpunit/unit/includes/Revision/SlotRoleHandlerTest.php b/tests/phpunit/unit/includes/Revision/SlotRoleHandlerTest.php new file mode 100644 index 0000000000..39217c2597 --- /dev/null +++ b/tests/phpunit/unit/includes/Revision/SlotRoleHandlerTest.php @@ -0,0 +1,64 @@ +createMock( Title::class ); + } + + /** + * @covers \MediaWiki\Revision\SlotRoleHandler::__construct + * @covers \MediaWiki\Revision\SlotRoleHandler::getRole() + * @covers \MediaWiki\Revision\SlotRoleHandler::getNameMessageKey() + * @covers \MediaWiki\Revision\SlotRoleHandler::getDefaultModel() + * @covers \MediaWiki\Revision\SlotRoleHandler::getOutputLayoutHints() + */ + public function testConstruction() { + $handler = new SlotRoleHandler( 'foo', 'FooModel', [ 'frob' => 'niz' ] ); + $this->assertSame( 'foo', $handler->getRole() ); + $this->assertSame( 'slot-name-foo', $handler->getNameMessageKey() ); + + $title = $this->makeBlankTitleObject(); + $this->assertSame( 'FooModel', $handler->getDefaultModel( $title ) ); + + $hints = $handler->getOutputLayoutHints(); + $this->assertArrayHasKey( 'frob', $hints ); + $this->assertSame( 'niz', $hints['frob'] ); + + $this->assertArrayHasKey( 'display', $hints ); + $this->assertArrayHasKey( 'region', $hints ); + $this->assertArrayHasKey( 'placement', $hints ); + } + + /** + * @covers \MediaWiki\Revision\SlotRoleHandler::isAllowedModel() + */ + public function testIsAllowedModel() { + $handler = new SlotRoleHandler( 'foo', 'FooModel' ); + + $title = $this->makeBlankTitleObject(); + $this->assertTrue( $handler->isAllowedModel( 'FooModel', $title ) ); + $this->assertFalse( $handler->isAllowedModel( 'QuaxModel', $title ) ); + } + + /** + * @covers \MediaWiki\Revision\SlotRoleHandler::supportsArticleCount() + */ + public function testSupportsArticleCount() { + $handler = new SlotRoleHandler( 'foo', 'FooModel' ); + + $this->assertFalse( $handler->supportsArticleCount() ); + } + +} diff --git a/tests/phpunit/unit/includes/ServiceWiringTest.php b/tests/phpunit/unit/includes/ServiceWiringTest.php new file mode 100644 index 0000000000..25b0214db1 --- /dev/null +++ b/tests/phpunit/unit/includes/ServiceWiringTest.php @@ -0,0 +1,16 @@ +assertSame( $sortedServices, $services, + 'Please keep services sorted alphabetically' ); + } +} diff --git a/tests/phpunit/unit/includes/SiteConfigurationTest.php b/tests/phpunit/unit/includes/SiteConfigurationTest.php new file mode 100644 index 0000000000..b992a86471 --- /dev/null +++ b/tests/phpunit/unit/includes/SiteConfigurationTest.php @@ -0,0 +1,379 @@ +mConf = new SiteConfiguration; + + $this->mConf->suffixes = [ 'wikipedia' => 'wiki' ]; + $this->mConf->wikis = [ 'enwiki', 'dewiki', 'frwiki' ]; + $this->mConf->settings = [ + 'SimpleKey' => [ + 'wiki' => 'wiki', + 'tag' => 'tag', + 'enwiki' => 'enwiki', + 'dewiki' => 'dewiki', + 'frwiki' => 'frwiki', + ], + + 'Fallback' => [ + 'default' => 'default', + 'wiki' => 'wiki', + 'tag' => 'tag', + 'frwiki' => 'frwiki', + 'null_wiki' => null, + ], + + 'WithParams' => [ + 'default' => '$lang $site $wiki', + ], + + '+SomeGlobal' => [ + 'wiki' => [ + 'wiki' => 'wiki', + ], + 'tag' => [ + 'tag' => 'tag', + ], + 'enwiki' => [ + 'enwiki' => 'enwiki', + ], + 'dewiki' => [ + 'dewiki' => 'dewiki', + ], + 'frwiki' => [ + 'frwiki' => 'frwiki', + ], + ], + + 'MergeIt' => [ + '+wiki' => [ + 'wiki' => 'wiki', + ], + '+tag' => [ + 'tag' => 'tag', + ], + 'default' => [ + 'default' => 'default', + ], + '+enwiki' => [ + 'enwiki' => 'enwiki', + ], + '+dewiki' => [ + 'dewiki' => 'dewiki', + ], + '+frwiki' => [ + 'frwiki' => 'frwiki', + ], + ], + ]; + + $GLOBALS['SomeGlobal'] = [ 'SomeGlobal' => 'SomeGlobal' ]; + } + + /** + * This function is used as a callback within the tests below + */ + public static function getSiteParamsCallback( $conf, $wiki ) { + $site = null; + $lang = null; + foreach ( $conf->suffixes as $suffix ) { + if ( substr( $wiki, -strlen( $suffix ) ) == $suffix ) { + $site = $suffix; + $lang = substr( $wiki, 0, -strlen( $suffix ) ); + break; + } + } + + return [ + 'suffix' => $site, + 'lang' => $lang, + 'params' => [ + 'lang' => $lang, + 'site' => $site, + 'wiki' => $wiki, + ], + 'tags' => [ 'tag' ], + ]; + } + + /** + * @covers SiteConfiguration::siteFromDB + */ + public function testSiteFromDb() { + $this->assertEquals( + [ 'wikipedia', 'en' ], + $this->mConf->siteFromDB( 'enwiki' ), + 'siteFromDB()' + ); + $this->assertEquals( + [ 'wikipedia', '' ], + $this->mConf->siteFromDB( 'wiki' ), + 'siteFromDB() on a suffix' + ); + $this->assertEquals( + [ null, null ], + $this->mConf->siteFromDB( 'wikien' ), + 'siteFromDB() on a non-existing wiki' + ); + + $this->mConf->suffixes = [ 'wiki', '' ]; + $this->assertEquals( + [ '', 'wikien' ], + $this->mConf->siteFromDB( 'wikien' ), + 'siteFromDB() on a non-existing wiki (2)' + ); + } + + /** + * @covers SiteConfiguration::getLocalDatabases + */ + public function testGetLocalDatabases() { + $this->assertEquals( + [ 'enwiki', 'dewiki', 'frwiki' ], + $this->mConf->getLocalDatabases(), + 'getLocalDatabases()' + ); + } + + /** + * @covers SiteConfiguration::get + */ + public function testGetConfVariables() { + // Simple + $this->assertEquals( + 'enwiki', + $this->mConf->get( 'SimpleKey', 'enwiki', 'wiki' ), + 'get(): simple setting on an existing wiki' + ); + $this->assertEquals( + 'dewiki', + $this->mConf->get( 'SimpleKey', 'dewiki', 'wiki' ), + 'get(): simple setting on an existing wiki (2)' + ); + $this->assertEquals( + 'frwiki', + $this->mConf->get( 'SimpleKey', 'frwiki', 'wiki' ), + 'get(): simple setting on an existing wiki (3)' + ); + $this->assertEquals( + 'wiki', + $this->mConf->get( 'SimpleKey', 'wiki', 'wiki' ), + 'get(): simple setting on an suffix' + ); + $this->assertEquals( + 'wiki', + $this->mConf->get( 'SimpleKey', 'eswiki', 'wiki' ), + 'get(): simple setting on an non-existing wiki' + ); + + // Fallback + $this->assertEquals( + 'wiki', + $this->mConf->get( 'Fallback', 'enwiki', 'wiki' ), + 'get(): fallback setting on an existing wiki' + ); + $this->assertEquals( + 'tag', + $this->mConf->get( 'Fallback', 'dewiki', 'wiki', [], [ 'tag' ] ), + 'get(): fallback setting on an existing wiki (with wiki tag)' + ); + $this->assertEquals( + 'frwiki', + $this->mConf->get( 'Fallback', 'frwiki', 'wiki', [], [ 'tag' ] ), + 'get(): no fallback if wiki has its own setting (matching tag)' + ); + $this->assertSame( + // Potential regression test for T192855 + null, + $this->mConf->get( 'Fallback', 'null_wiki', 'wiki', [], [ 'tag' ] ), + 'get(): no fallback if wiki has its own setting (matching tag and uses null)' + ); + $this->assertEquals( + 'wiki', + $this->mConf->get( 'Fallback', 'wiki', 'wiki' ), + 'get(): fallback setting on an suffix' + ); + $this->assertEquals( + 'wiki', + $this->mConf->get( 'Fallback', 'wiki', 'wiki', [], [ 'tag' ] ), + 'get(): fallback setting on an suffix (with wiki tag)' + ); + $this->assertEquals( + 'wiki', + $this->mConf->get( 'Fallback', 'eswiki', 'wiki' ), + 'get(): fallback setting on an non-existing wiki' + ); + $this->assertEquals( + 'tag', + $this->mConf->get( 'Fallback', 'eswiki', 'wiki', [], [ 'tag' ] ), + 'get(): fallback setting on an non-existing wiki (with wiki tag)' + ); + + // Merging + $common = [ 'wiki' => 'wiki', 'default' => 'default' ]; + $commonTag = [ 'tag' => 'tag', 'wiki' => 'wiki', 'default' => 'default' ]; + $this->assertEquals( + [ 'enwiki' => 'enwiki' ] + $common, + $this->mConf->get( 'MergeIt', 'enwiki', 'wiki' ), + 'get(): merging setting on an existing wiki' + ); + $this->assertEquals( + [ 'enwiki' => 'enwiki' ] + $commonTag, + $this->mConf->get( 'MergeIt', 'enwiki', 'wiki', [], [ 'tag' ] ), + 'get(): merging setting on an existing wiki (with tag)' + ); + $this->assertEquals( + [ 'dewiki' => 'dewiki' ] + $common, + $this->mConf->get( 'MergeIt', 'dewiki', 'wiki' ), + 'get(): merging setting on an existing wiki (2)' + ); + $this->assertEquals( + [ 'dewiki' => 'dewiki' ] + $commonTag, + $this->mConf->get( 'MergeIt', 'dewiki', 'wiki', [], [ 'tag' ] ), + 'get(): merging setting on an existing wiki (2) (with tag)' + ); + $this->assertEquals( + [ 'frwiki' => 'frwiki' ] + $common, + $this->mConf->get( 'MergeIt', 'frwiki', 'wiki' ), + 'get(): merging setting on an existing wiki (3)' + ); + $this->assertEquals( + [ 'frwiki' => 'frwiki' ] + $commonTag, + $this->mConf->get( 'MergeIt', 'frwiki', 'wiki', [], [ 'tag' ] ), + 'get(): merging setting on an existing wiki (3) (with tag)' + ); + $this->assertEquals( + [ 'wiki' => 'wiki' ] + $common, + $this->mConf->get( 'MergeIt', 'wiki', 'wiki' ), + 'get(): merging setting on an suffix' + ); + $this->assertEquals( + [ 'wiki' => 'wiki' ] + $commonTag, + $this->mConf->get( 'MergeIt', 'wiki', 'wiki', [], [ 'tag' ] ), + 'get(): merging setting on an suffix (with tag)' + ); + $this->assertEquals( + $common, + $this->mConf->get( 'MergeIt', 'eswiki', 'wiki' ), + 'get(): merging setting on an non-existing wiki' + ); + $this->assertEquals( + $commonTag, + $this->mConf->get( 'MergeIt', 'eswiki', 'wiki', [], [ 'tag' ] ), + 'get(): merging setting on an non-existing wiki (with tag)' + ); + } + + /** + * @covers SiteConfiguration::siteFromDB + */ + public function testSiteFromDbWithCallback() { + $this->mConf->siteParamsCallback = 'SiteConfigurationTest::getSiteParamsCallback'; + + $this->assertEquals( + [ 'wiki', 'en' ], + $this->mConf->siteFromDB( 'enwiki' ), + 'siteFromDB() with callback' + ); + $this->assertEquals( + [ 'wiki', '' ], + $this->mConf->siteFromDB( 'wiki' ), + 'siteFromDB() with callback on a suffix' + ); + $this->assertEquals( + [ null, null ], + $this->mConf->siteFromDB( 'wikien' ), + 'siteFromDB() with callback on a non-existing wiki' + ); + } + + /** + * @covers SiteConfiguration::get + */ + public function testParameterReplacement() { + $this->mConf->siteParamsCallback = 'SiteConfigurationTest::getSiteParamsCallback'; + + $this->assertEquals( + 'en wiki enwiki', + $this->mConf->get( 'WithParams', 'enwiki', 'wiki' ), + 'get(): parameter replacement on an existing wiki' + ); + $this->assertEquals( + 'de wiki dewiki', + $this->mConf->get( 'WithParams', 'dewiki', 'wiki' ), + 'get(): parameter replacement on an existing wiki (2)' + ); + $this->assertEquals( + 'fr wiki frwiki', + $this->mConf->get( 'WithParams', 'frwiki', 'wiki' ), + 'get(): parameter replacement on an existing wiki (3)' + ); + $this->assertEquals( + ' wiki wiki', + $this->mConf->get( 'WithParams', 'wiki', 'wiki' ), + 'get(): parameter replacement on an suffix' + ); + $this->assertEquals( + 'es wiki eswiki', + $this->mConf->get( 'WithParams', 'eswiki', 'wiki' ), + 'get(): parameter replacement on an non-existing wiki' + ); + } + + /** + * @covers SiteConfiguration::getAll + */ + public function testGetAllGlobals() { + $this->mConf->siteParamsCallback = 'SiteConfigurationTest::getSiteParamsCallback'; + + $getall = [ + 'SimpleKey' => 'enwiki', + 'Fallback' => 'tag', + 'WithParams' => 'en wiki enwiki', + 'SomeGlobal' => [ 'enwiki' => 'enwiki' ] + $GLOBALS['SomeGlobal'], + 'MergeIt' => [ + 'enwiki' => 'enwiki', + 'tag' => 'tag', + 'wiki' => 'wiki', + 'default' => 'default' + ], + ]; + $this->assertEquals( $getall, $this->mConf->getAll( 'enwiki' ), 'getAll()' ); + + $this->mConf->extractAllGlobals( 'enwiki', 'wiki' ); + + $this->assertEquals( + $getall['SimpleKey'], + $GLOBALS['SimpleKey'], + 'extractAllGlobals(): simple setting' + ); + $this->assertEquals( + $getall['Fallback'], + $GLOBALS['Fallback'], + 'extractAllGlobals(): fallback setting' + ); + $this->assertEquals( + $getall['WithParams'], + $GLOBALS['WithParams'], + 'extractAllGlobals(): parameter replacement' + ); + $this->assertEquals( + $getall['SomeGlobal'], + $GLOBALS['SomeGlobal'], + 'extractAllGlobals(): merging with global' + ); + $this->assertEquals( + $getall['MergeIt'], + $GLOBALS['MergeIt'], + 'extractAllGlobals(): merging setting' + ); + } +} diff --git a/tests/phpunit/unit/includes/Storage/PreparedEditTest.php b/tests/phpunit/unit/includes/Storage/PreparedEditTest.php new file mode 100644 index 0000000000..e3249e74bc --- /dev/null +++ b/tests/phpunit/unit/includes/Storage/PreparedEditTest.php @@ -0,0 +1,21 @@ +parserOutputCallback = function () { + return new ParserOutput(); + }; + + $this->assertEquals( $output, $edit->getOutput() ); + $this->assertEquals( $output, $edit->output ); + } +} diff --git a/tests/phpunit/unit/includes/XmlSelectTest.php b/tests/phpunit/unit/includes/XmlSelectTest.php new file mode 100644 index 0000000000..54d269e0ce --- /dev/null +++ b/tests/phpunit/unit/includes/XmlSelectTest.php @@ -0,0 +1,182 @@ +select = new XmlSelect(); + } + + protected function tearDown() { + parent::tearDown(); + $this->select = null; + } + + /** + * @covers XmlSelect::__construct + */ + public function testConstructWithoutParameters() { + $this->assertEquals( '', $this->select->getHTML() ); + } + + /** + * Parameters are $name (false), $id (false), $default (false) + * @dataProvider provideConstructionParameters + * @covers XmlSelect::__construct + */ + public function testConstructParameters( $name, $id, $default, $expected ) { + $this->select = new XmlSelect( $name, $id, $default ); + $this->assertEquals( $expected, $this->select->getHTML() ); + } + + /** + * Provide parameters for testConstructParameters() which use three + * parameters: + * - $name (default: false) + * - $id (default: false) + * - $default (default: false) + * Provides a fourth parameters representing the expected HTML output + */ + public static function provideConstructionParameters() { + return [ + /** + * Values are set following a 3-bit Gray code where two successive + * values differ by only one value. + * See https://en.wikipedia.org/wiki/Gray_code + */ + # $name $id $default + [ false, false, false, '' ], + [ false, false, 'foo', '' ], + [ false, 'id', 'foo', '' ], + [ false, 'id', false, '' ], + [ 'name', 'id', false, '' ], + [ 'name', 'id', 'foo', '' ], + [ 'name', false, 'foo', '' ], + [ 'name', false, false, '' ], + ]; + } + + /** + * @covers XmlSelect::addOption + */ + public function testAddOption() { + $this->select->addOption( 'foo' ); + $this->assertEquals( + '', + $this->select->getHTML() + ); + } + + /** + * @covers XmlSelect::addOption + */ + public function testAddOptionWithDefault() { + $this->select->addOption( 'foo', true ); + $this->assertEquals( + '', + $this->select->getHTML() + ); + } + + /** + * @covers XmlSelect::addOption + */ + public function testAddOptionWithFalse() { + $this->select->addOption( 'foo', false ); + $this->assertEquals( + '', + $this->select->getHTML() + ); + } + + /** + * @covers XmlSelect::addOption + */ + public function testAddOptionWithValueZero() { + $this->select->addOption( 'foo', 0 ); + $this->assertEquals( + '', + $this->select->getHTML() + ); + } + + /** + * @covers XmlSelect::setDefault + */ + public function testSetDefault() { + $this->select->setDefault( 'bar1' ); + $this->select->addOption( 'foo1' ); + $this->select->addOption( 'bar1' ); + $this->select->addOption( 'foo2' ); + $this->assertEquals( + '', $this->select->getHTML() ); + } + + /** + * Adding default later on should set the correct selection or + * raise an exception. + * To handle this, we need to render the options in getHtml() + * @covers XmlSelect::setDefault + */ + public function testSetDefaultAfterAddingOptions() { + $this->select->addOption( 'foo1' ); + $this->select->addOption( 'bar1' ); + $this->select->addOption( 'foo2' ); + $this->select->setDefault( 'bar1' ); # setting default after adding options + $this->assertEquals( + '', $this->select->getHTML() ); + } + + /** + * @covers XmlSelect::setAttribute + * @covers XmlSelect::getAttribute + */ + public function testGetAttributes() { + # create some attributes + $this->select->setAttribute( 'dummy', 0x777 ); + $this->select->setAttribute( 'string', 'euro €' ); + $this->select->setAttribute( 1911, 'razor' ); + + # verify we can retrieve them + $this->assertEquals( + $this->select->getAttribute( 'dummy' ), + 0x777 + ); + $this->assertEquals( + $this->select->getAttribute( 'string' ), + 'euro €' + ); + $this->assertEquals( + $this->select->getAttribute( 1911 ), + 'razor' + ); + + # inexistent keys should give us 'null' + $this->assertEquals( + $this->select->getAttribute( 'I DO NOT EXIT' ), + null + ); + + # verify string / integer + $this->assertEquals( + $this->select->getAttribute( '1911' ), + 'razor' + ); + $this->assertEquals( + $this->select->getAttribute( 'dummy' ), + 0x777 + ); + } +} diff --git a/tests/phpunit/unit/includes/auth/AuthenticationResponseTest.php b/tests/phpunit/unit/includes/auth/AuthenticationResponseTest.php new file mode 100644 index 0000000000..44b063153b --- /dev/null +++ b/tests/phpunit/unit/includes/auth/AuthenticationResponseTest.php @@ -0,0 +1,112 @@ +messageType = 'warning'; + foreach ( $expect as $field => $value ) { + $res->$field = $value; + } + $ret = call_user_func_array( "MediaWiki\\Auth\\AuthenticationResponse::$constructor", $args ); + $this->assertEquals( $res, $ret ); + } else { + try { + call_user_func_array( "MediaWiki\\Auth\\AuthenticationResponse::$constructor", $args ); + $this->fail( 'Expected exception not thrown' ); + } catch ( \Exception $ex ) { + $this->assertEquals( $expect, $ex ); + } + } + } + + public function provideConstructors() { + $req = $this->getMockForAbstractClass( AuthenticationRequest::class ); + $msg = new \Message( 'mainpage' ); + + return [ + [ 'newPass', [], [ + 'status' => AuthenticationResponse::PASS, + ] ], + [ 'newPass', [ 'name' ], [ + 'status' => AuthenticationResponse::PASS, + 'username' => 'name', + ] ], + [ 'newPass', [ 'name', null ], [ + 'status' => AuthenticationResponse::PASS, + 'username' => 'name', + ] ], + + [ 'newFail', [ $msg ], [ + 'status' => AuthenticationResponse::FAIL, + 'message' => $msg, + 'messageType' => 'error', + ] ], + + [ 'newRestart', [ $msg ], [ + 'status' => AuthenticationResponse::RESTART, + 'message' => $msg, + ] ], + + [ 'newAbstain', [], [ + 'status' => AuthenticationResponse::ABSTAIN, + ] ], + + [ 'newUI', [ [ $req ], $msg ], [ + 'status' => AuthenticationResponse::UI, + 'neededRequests' => [ $req ], + 'message' => $msg, + 'messageType' => 'warning', + ] ], + + [ 'newUI', [ [ $req ], $msg, 'warning' ], [ + 'status' => AuthenticationResponse::UI, + 'neededRequests' => [ $req ], + 'message' => $msg, + 'messageType' => 'warning', + ] ], + + [ 'newUI', [ [ $req ], $msg, 'error' ], [ + 'status' => AuthenticationResponse::UI, + 'neededRequests' => [ $req ], + 'message' => $msg, + 'messageType' => 'error', + ] ], + [ 'newUI', [ [], $msg ], + new \InvalidArgumentException( '$reqs may not be empty' ) + ], + + [ 'newRedirect', [ [ $req ], 'http://example.org/redir' ], [ + 'status' => AuthenticationResponse::REDIRECT, + 'neededRequests' => [ $req ], + 'redirectTarget' => 'http://example.org/redir', + ] ], + [ + 'newRedirect', + [ [ $req ], 'http://example.org/redir', [ 'foo' => 'bar' ] ], + [ + 'status' => AuthenticationResponse::REDIRECT, + 'neededRequests' => [ $req ], + 'redirectTarget' => 'http://example.org/redir', + 'redirectApiData' => [ 'foo' => 'bar' ], + ] + ], + [ 'newRedirect', [ [], 'http://example.org/redir' ], + new \InvalidArgumentException( '$reqs may not be empty' ) + ], + ]; + } + +} diff --git a/tests/phpunit/unit/includes/changes/ChangesListFilterGroupTest.php b/tests/phpunit/unit/includes/changes/ChangesListFilterGroupTest.php new file mode 100644 index 0000000000..bd54d508ba --- /dev/null +++ b/tests/phpunit/unit/includes/changes/ChangesListFilterGroupTest.php @@ -0,0 +1,79 @@ + 'some_type', + 'name' => 'group_name', + 'priority' => 1, + 'filters' => [], + ] + ); + } + + public function testAutoPriorities() { + $group = new MockChangesListFilterGroup( + [ + 'type' => 'some_type', + 'name' => 'groupName', + 'isFullCoverage' => true, + 'priority' => 1, + 'filters' => [ + [ 'name' => 'hidefoo' ], + [ 'name' => 'hidebar' ], + [ 'name' => 'hidebaz' ], + ], + ] + ); + + $filters = $group->getFilters(); + $this->assertEquals( + [ + -2, + -3, + -4, + ], + array_map( + function ( $f ) { + return $f->getPriority(); + }, + array_values( $filters ) + ) + ); + } + + // Get without warnings + public function testGetFilter() { + $group = new MockChangesListFilterGroup( + [ + 'type' => 'some_type', + 'name' => 'groupName', + 'isFullCoverage' => true, + 'priority' => 1, + 'filters' => [ + [ 'name' => 'foo' ], + ], + ] + ); + + $this->assertEquals( + 'foo', + $group->getFilter( 'foo' )->getName() + ); + + $this->assertEquals( + null, + $group->getFilter( 'bar' ) + ); + } +} diff --git a/tests/phpunit/unit/includes/config/HashConfigTest.php b/tests/phpunit/unit/includes/config/HashConfigTest.php new file mode 100644 index 0000000000..d46ee09d5b --- /dev/null +++ b/tests/phpunit/unit/includes/config/HashConfigTest.php @@ -0,0 +1,63 @@ +assertInstanceOf( HashConfig::class, $conf ); + } + + /** + * @covers HashConfig::__construct + */ + public function testConstructor() { + $conf = new HashConfig(); + $this->assertInstanceOf( HashConfig::class, $conf ); + + // Test passing arguments to the constructor + $conf2 = new HashConfig( [ + 'one' => '1', + ] ); + $this->assertEquals( '1', $conf2->get( 'one' ) ); + } + + /** + * @covers HashConfig::get + */ + public function testGet() { + $conf = new HashConfig( [ + 'one' => '1', + ] ); + $this->assertEquals( '1', $conf->get( 'one' ) ); + $this->setExpectedException( ConfigException::class, 'HashConfig::get: undefined option' ); + $conf->get( 'two' ); + } + + /** + * @covers HashConfig::has + */ + public function testHas() { + $conf = new HashConfig( [ + 'one' => '1', + ] ); + $this->assertTrue( $conf->has( 'one' ) ); + $this->assertFalse( $conf->has( 'two' ) ); + } + + /** + * @covers HashConfig::set + */ + public function testSet() { + $conf = new HashConfig( [ + 'one' => '1', + ] ); + $conf->set( 'two', '2' ); + $this->assertEquals( '2', $conf->get( 'two' ) ); + // Check that set overwrites + $conf->set( 'one', '3' ); + $this->assertEquals( '3', $conf->get( 'one' ) ); + } +} diff --git a/tests/phpunit/unit/includes/config/MultiConfigTest.php b/tests/phpunit/unit/includes/config/MultiConfigTest.php new file mode 100644 index 0000000000..4351151302 --- /dev/null +++ b/tests/phpunit/unit/includes/config/MultiConfigTest.php @@ -0,0 +1,39 @@ + 'bar' ] ), + new HashConfig( [ 'foo' => 'baz', 'bar' => 'foo' ] ), + new HashConfig( [ 'bar' => 'baz' ] ), + ] ); + + $this->assertEquals( 'bar', $multi->get( 'foo' ) ); + $this->assertEquals( 'foo', $multi->get( 'bar' ) ); + $this->setExpectedException( ConfigException::class, 'MultiConfig::get: undefined option:' ); + $multi->get( 'notset' ); + } + + /** + * @covers MultiConfig::has + */ + public function testHas() { + $conf = new MultiConfig( [ + new HashConfig( [ 'foo' => 'foo' ] ), + new HashConfig( [ 'something' => 'bleh' ] ), + new HashConfig( [ 'meh' => 'eh' ] ), + ] ); + + $this->assertTrue( $conf->has( 'foo' ) ); + $this->assertTrue( $conf->has( 'something' ) ); + $this->assertTrue( $conf->has( 'meh' ) ); + $this->assertFalse( $conf->has( 'what' ) ); + } +} diff --git a/tests/phpunit/unit/includes/config/ServiceOptionsTest.php b/tests/phpunit/unit/includes/config/ServiceOptionsTest.php new file mode 100644 index 0000000000..c58c6f579c --- /dev/null +++ b/tests/phpunit/unit/includes/config/ServiceOptionsTest.php @@ -0,0 +1,149 @@ + $val ) { + $this->assertSame( $val, $options->get( $key ) ); + } + + // This is lumped in the same test because there's no support for depending on a test that + // has a data provider. + $options->assertRequiredOptions( array_keys( $expected ) ); + + // Suppress warning if no assertions were run. This is expected for empty arguments. + $this->assertTrue( true ); + } + + public function provideConstructor() { + return [ + 'No keys' => [ [], [], [ 'a' => 'aval' ] ], + 'Simple array source' => [ + [ 'a' => 'aval', 'b' => 'bval' ], + [ 'a', 'b' ], + [ 'a' => 'aval', 'b' => 'bval', 'c' => 'cval' ], + ], + 'Simple HashConfig source' => [ + [ 'a' => 'aval', 'b' => 'bval' ], + [ 'a', 'b' ], + new HashConfig( [ 'a' => 'aval', 'b' => 'bval', 'c' => 'cval' ] ), + ], + 'Three different sources' => [ + [ 'a' => 'aval', 'b' => 'bval' ], + [ 'a', 'b' ], + [ 'z' => 'zval' ], + new HashConfig( [ 'a' => 'aval', 'c' => 'cval' ] ), + [ 'b' => 'bval', 'd' => 'dval' ], + ], + 'null key' => [ + [ 'a' => null ], + [ 'a' ], + [ 'a' => null ], + ], + 'Numeric option name' => [ + [ '0' => 'nothing' ], + [ '0' ], + [ '0' => 'nothing' ], + ], + 'Multiple sources for one key' => [ + [ 'a' => 'winner' ], + [ 'a' ], + [ 'a' => 'winner' ], + [ 'a' => 'second place' ], + ], + 'Object value is passed by reference' => [ + [ 'a' => self::$testObj ], + [ 'a' ], + [ 'a' => self::$testObj ], + ], + ]; + } + + /** + * @covers ::__construct + */ + public function testKeyNotFound() { + $this->setExpectedException( InvalidArgumentException::class, + 'Key "a" not found in input sources' ); + + new ServiceOptions( [ 'a' ], [ 'b' => 'bval' ], [ 'c' => 'cval' ] ); + } + + /** + * @covers ::__construct + * @covers ::assertRequiredOptions + */ + public function testOutOfOrderAssertRequiredOptions() { + $options = new ServiceOptions( [ 'a', 'b' ], [ 'a' => '', 'b' => '' ] ); + $options->assertRequiredOptions( [ 'b', 'a' ] ); + $this->assertTrue( true, 'No exception thrown' ); + } + + /** + * @covers ::__construct + * @covers ::get + */ + public function testGetUnrecognized() { + $this->setExpectedException( InvalidArgumentException::class, + 'Unrecognized option "b"' ); + + $options = new ServiceOptions( [ 'a' ], [ 'a' => '' ] ); + $options->get( 'b' ); + } + + /** + * @covers ::__construct + * @covers ::assertRequiredOptions + */ + public function testExtraKeys() { + $this->setExpectedException( Wikimedia\Assert\PreconditionException::class, + 'Precondition failed: Unsupported options passed: b, c!' ); + + $options = new ServiceOptions( [ 'a', 'b', 'c' ], [ 'a' => '', 'b' => '', 'c' => '' ] ); + $options->assertRequiredOptions( [ 'a' ] ); + } + + /** + * @covers ::__construct + * @covers ::assertRequiredOptions + */ + public function testMissingKeys() { + $this->setExpectedException( Wikimedia\Assert\PreconditionException::class, + 'Precondition failed: Required options missing: a, b!' ); + + $options = new ServiceOptions( [ 'c' ], [ 'c' => '' ] ); + $options->assertRequiredOptions( [ 'a', 'b', 'c' ] ); + } + + /** + * @covers ::__construct + * @covers ::assertRequiredOptions + */ + public function testExtraAndMissingKeys() { + $this->setExpectedException( Wikimedia\Assert\PreconditionException::class, + 'Precondition failed: Unsupported options passed: b! Required options missing: c!' ); + + $options = new ServiceOptions( [ 'a', 'b' ], [ 'a' => '', 'b' => '' ] ); + $options->assertRequiredOptions( [ 'a', 'c' ] ); + } +} diff --git a/tests/phpunit/unit/includes/content/JsonContentHandlerTest.php b/tests/phpunit/unit/includes/content/JsonContentHandlerTest.php new file mode 100644 index 0000000000..70db73c810 --- /dev/null +++ b/tests/phpunit/unit/includes/content/JsonContentHandlerTest.php @@ -0,0 +1,14 @@ +makeEmptyContent(); + $this->assertInstanceOf( JsonContent::class, $content ); + $this->assertTrue( $content->isValid() ); + } +} diff --git a/tests/phpunit/unit/includes/debug/logger/MonologSpiTest.php b/tests/phpunit/unit/includes/debug/logger/MonologSpiTest.php new file mode 100644 index 0000000000..ecb5d17c2a --- /dev/null +++ b/tests/phpunit/unit/includes/debug/logger/MonologSpiTest.php @@ -0,0 +1,135 @@ + [ + '@default' => [ + 'processors' => [ 'constructor' ], + 'handlers' => [ 'constructor' ], + ], + ], + 'processors' => [ + 'constructor' => [ + 'class' => 'constructor', + ], + ], + 'handlers' => [ + 'constructor' => [ + 'class' => 'constructor', + 'formatter' => 'constructor', + ], + ], + 'formatters' => [ + 'constructor' => [ + 'class' => 'constructor', + ], + ], + ]; + + $fixture = new MonologSpi( $base ); + $this->assertSame( + $base, + TestingAccessWrapper::newFromObject( $fixture )->config + ); + + $fixture->mergeConfig( [ + 'loggers' => [ + 'merged' => [ + 'processors' => [ 'merged' ], + 'handlers' => [ 'merged' ], + ], + ], + 'processors' => [ + 'merged' => [ + 'class' => 'merged', + ], + ], + 'magic' => [ + 'idkfa' => [ 'xyzzy' ], + ], + 'handlers' => [ + 'merged' => [ + 'class' => 'merged', + 'formatter' => 'merged', + ], + ], + 'formatters' => [ + 'merged' => [ + 'class' => 'merged', + ], + ], + ] ); + $this->assertSame( + [ + 'loggers' => [ + '@default' => [ + 'processors' => [ 'constructor' ], + 'handlers' => [ 'constructor' ], + ], + 'merged' => [ + 'processors' => [ 'merged' ], + 'handlers' => [ 'merged' ], + ], + ], + 'processors' => [ + 'constructor' => [ + 'class' => 'constructor', + ], + 'merged' => [ + 'class' => 'merged', + ], + ], + 'handlers' => [ + 'constructor' => [ + 'class' => 'constructor', + 'formatter' => 'constructor', + ], + 'merged' => [ + 'class' => 'merged', + 'formatter' => 'merged', + ], + ], + 'formatters' => [ + 'constructor' => [ + 'class' => 'constructor', + ], + 'merged' => [ + 'class' => 'merged', + ], + ], + 'magic' => [ + 'idkfa' => [ 'xyzzy' ], + ], + ], + TestingAccessWrapper::newFromObject( $fixture )->config + ); + } + +} diff --git a/tests/phpunit/unit/includes/debug/logger/monolog/AvroFormatterTest.php b/tests/phpunit/unit/includes/debug/logger/monolog/AvroFormatterTest.php new file mode 100644 index 0000000000..e091561923 --- /dev/null +++ b/tests/phpunit/unit/includes/debug/logger/monolog/AvroFormatterTest.php @@ -0,0 +1,75 @@ +markTestSkipped( 'Avro is required for the AvroFormatterTest' ); + } + parent::setUp(); + } + + public function testSchemaNotAvailable() { + $formatter = new AvroFormatter( [] ); + $this->setExpectedException( + 'PHPUnit_Framework_Error_Notice', + "The schema for channel 'marty' is not available" + ); + $formatter->format( [ 'channel' => 'marty' ] ); + } + + public function testSchemaNotAvailableReturnValue() { + $formatter = new AvroFormatter( [] ); + $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 + \Wikimedia\suppressWarnings(); + $res = $formatter->format( [ 'channel' => 'marty' ] ); + \Wikimedia\restoreWarnings(); + PHPUnit_Framework_Error_Notice::$enabled = $noticeEnabled; + $this->assertNull( $res ); + } + + public function testDoesSomethingWhenSchemaAvailable() { + $formatter = new AvroFormatter( [ + 'string' => [ + 'schema' => [ 'type' => 'string' ], + 'revision' => 1010101, + ] + ] ); + $res = $formatter->format( [ + 'channel' => 'string', + 'context' => 'better to be', + ] ); + $this->assertNotNull( $res ); + // basically just tell us if avro changes its string encoding, or if + // we completely fail to generate a log message. + $this->assertEquals( 'AAAAAAAAD2m1GGJldHRlciB0byBiZQ==', base64_encode( $res ) ); + } +} diff --git a/tests/phpunit/unit/includes/debug/logger/monolog/KafkaHandlerTest.php b/tests/phpunit/unit/includes/debug/logger/monolog/KafkaHandlerTest.php new file mode 100644 index 0000000000..bbac17f1cc --- /dev/null +++ b/tests/phpunit/unit/includes/debug/logger/monolog/KafkaHandlerTest.php @@ -0,0 +1,226 @@ +markTestSkipped( 'Monolog and Kafka are required for the KafkaHandlerTest' ); + } + + parent::setUp(); + } + + public function topicNamingProvider() { + return [ + [ [], 'monolog_foo' ], + [ [ 'alias' => [ '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( [ 'A' ] ) ); + $produce->expects( $this->once() ) + ->method( 'setMessages' ) + ->with( $expect, $this->anything(), $this->anything() ); + $produce->expects( $this->any() ) + ->method( 'send' ) + ->will( $this->returnValue( true ) ); + + $handler = new KafkaHandler( $produce, $options ); + $handler->handle( [ + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => [], + 'context' => [], + ] ); + } + + public function swallowsExceptionsWhenRequested() { + return [ + // defaults to false + [ [], true ], + // also try false explicitly + [ [ 'swallowExceptions' => false ], true ], + // turn it on + [ [ '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 ) ); + $produce->expects( $this->any() ) + ->method( 'send' ) + ->will( $this->returnValue( true ) ); + + if ( $expectException ) { + $this->setExpectedException( 'Kafka\Exception' ); + } + + $handler = new KafkaHandler( $produce, $options ); + $handler->handle( [ + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => [], + 'context' => [], + ] ); + + 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( [ '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( [ + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => [], + 'context' => [], + ] ); + + 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( [ 'A' ] ) ); + $mockMethod = $produce->expects( $this->exactly( 2 ) ) + ->method( 'setMessages' ); + $produce->expects( $this->any() ) + ->method( 'send' ) + ->will( $this->returnValue( true ) ); + // evil hax + $matcher = TestingAccessWrapper::newFromObject( $mockMethod )->matcher; + TestingAccessWrapper::newFromObject( $matcher )->parametersMatcher = + new \PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters( [ + [ $this->anything(), $this->anything(), [ 'words' ] ], + [ $this->anything(), $this->anything(), [ 'lines' ] ] + ] ); + + $formatter = $this->createMock( \Monolog\Formatter\FormatterInterface::class ); + $formatter->expects( $this->any() ) + ->method( 'format' ) + ->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) ); + + $handler = new KafkaHandler( $produce, [] ); + $handler->setFormatter( $formatter ); + for ( $i = 0; $i < 3; ++$i ) { + $handler->handle( [ + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => [], + 'context' => [], + ] ); + } + } + + public function testBatchHandlesNullFormatterResult() { + $produce = $this->getMockBuilder( 'Kafka\Produce' ) + ->disableOriginalConstructor() + ->getMock(); + $produce->expects( $this->any() ) + ->method( 'getAvailablePartitions' ) + ->will( $this->returnValue( [ 'A' ] ) ); + $produce->expects( $this->once() ) + ->method( 'setMessages' ) + ->with( $this->anything(), $this->anything(), [ 'words', 'lines' ] ); + $produce->expects( $this->any() ) + ->method( 'send' ) + ->will( $this->returnValue( true ) ); + + $formatter = $this->createMock( \Monolog\Formatter\FormatterInterface::class ); + $formatter->expects( $this->any() ) + ->method( 'format' ) + ->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) ); + + $handler = new KafkaHandler( $produce, [] ); + $handler->setFormatter( $formatter ); + $handler->handleBatch( [ + [ + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => [], + 'context' => [], + ], + [ + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => [], + 'context' => [], + ], + [ + 'channel' => 'foo', + 'level' => Logger::EMERGENCY, + 'extra' => [], + 'context' => [], + ], + ] ); + } +} diff --git a/tests/phpunit/unit/includes/debug/logger/monolog/LineFormatterTest.php b/tests/phpunit/unit/includes/debug/logger/monolog/LineFormatterTest.php new file mode 100644 index 0000000000..8da3d9304e --- /dev/null +++ b/tests/phpunit/unit/includes/debug/logger/monolog/LineFormatterTest.php @@ -0,0 +1,121 @@ +markTestSkipped( 'This test requires monolog to be installed' ); + } + parent::setUp(); + } + + /** + * @covers MediaWiki\Logger\Monolog\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 MediaWiki\Logger\Monolog\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 ); + } + + /** + * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException + */ + public function testNormalizeExceptionErrorNoTrace() { + if ( !class_exists( AssertionError::class ) ) { + $this->markTestSkipped( 'AssertionError class does not exist' ); + } + + $fixture = new LineFormatter(); + $fixture->includeStacktraces( false ); + $fixture = TestingAccessWrapper::newFromObject( $fixture ); + $boom = new InvalidArgumentException( 'boom', 0, + new LengthException( 'too long', 0, + new AssertionError( 'Spock wuz here' ) + ) + ); + $out = $fixture->normalizeException( $boom ); + $this->assertContains( "\n[Exception InvalidArgumentException]", $out ); + $this->assertContains( "\nCaused by: [Exception LengthException]", $out ); + $this->assertContains( "\nCaused by: [Error AssertionError]", $out ); + $this->assertNotContains( "\n #0", $out ); + } + + /** + * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException + */ + public function testNormalizeExceptionErrorTrace() { + if ( !class_exists( AssertionError::class ) ) { + $this->markTestSkipped( 'AssertionError class does not exist' ); + } + + $fixture = new LineFormatter(); + $fixture->includeStacktraces( true ); + $fixture = TestingAccessWrapper::newFromObject( $fixture ); + $boom = new InvalidArgumentException( 'boom', 0, + new LengthException( 'too long', 0, + new AssertionError( 'Spock wuz here' ) + ) + ); + $out = $fixture->normalizeException( $boom ); + $this->assertContains( "\n[Exception InvalidArgumentException]", $out ); + $this->assertContains( "\nCaused by: [Exception LengthException]", $out ); + $this->assertContains( "\nCaused by: [Error AssertionError]", $out ); + $this->assertContains( "\n #0", $out ); + } +} diff --git a/tests/phpunit/unit/includes/diff/ArrayDiffFormatterTest.php b/tests/phpunit/unit/includes/diff/ArrayDiffFormatterTest.php new file mode 100644 index 0000000000..d436991fe9 --- /dev/null +++ b/tests/phpunit/unit/includes/diff/ArrayDiffFormatterTest.php @@ -0,0 +1,134 @@ +format( $input ); + $this->assertEquals( $expectedOutput, $output ); + } + + private function getMockDiff( $edits ) { + $diff = $this->getMockBuilder( Diff::class ) + ->disableOriginalConstructor() + ->getMock(); + $diff->expects( $this->any() ) + ->method( 'getEdits' ) + ->will( $this->returnValue( $edits ) ); + return $diff; + } + + private function getMockDiffOp( $type = null, $orig = [], $closing = [] ) { + $diffOp = $this->getMockBuilder( DiffOp::class ) + ->disableOriginalConstructor() + ->getMock(); + $diffOp->expects( $this->any() ) + ->method( 'getType' ) + ->will( $this->returnValue( $type ) ); + $diffOp->expects( $this->any() ) + ->method( 'getOrig' ) + ->will( $this->returnValue( $orig ) ); + if ( $type === 'change' ) { + $diffOp->expects( $this->any() ) + ->method( 'getClosing' ) + ->with( $this->isType( 'integer' ) ) + ->will( $this->returnCallback( function () { + return 'mockLine'; + } ) ); + } else { + $diffOp->expects( $this->any() ) + ->method( 'getClosing' ) + ->will( $this->returnValue( $closing ) ); + } + return $diffOp; + } + + public function provideTestFormat() { + $emptyArrayTestCases = [ + $this->getMockDiff( [] ), + $this->getMockDiff( [ $this->getMockDiffOp( 'add' ) ] ), + $this->getMockDiff( [ $this->getMockDiffOp( 'delete' ) ] ), + $this->getMockDiff( [ $this->getMockDiffOp( 'change' ) ] ), + $this->getMockDiff( [ $this->getMockDiffOp( 'copy' ) ] ), + $this->getMockDiff( [ $this->getMockDiffOp( 'FOOBARBAZ' ) ] ), + $this->getMockDiff( [ $this->getMockDiffOp( 'add', 'line' ) ] ), + $this->getMockDiff( [ $this->getMockDiffOp( 'delete', [], [ 'line' ] ) ] ), + $this->getMockDiff( [ $this->getMockDiffOp( 'copy', [], [ 'line' ] ) ] ), + ]; + + $otherTestCases = []; + $otherTestCases[] = [ + $this->getMockDiff( [ $this->getMockDiffOp( 'add', [], [ 'a1' ] ) ] ), + [ [ 'action' => 'add', 'new' => 'a1', 'newline' => 1 ] ], + ]; + $otherTestCases[] = [ + $this->getMockDiff( [ $this->getMockDiffOp( 'add', [], [ 'a1', 'a2' ] ) ] ), + [ + [ 'action' => 'add', 'new' => 'a1', 'newline' => 1 ], + [ 'action' => 'add', 'new' => 'a2', 'newline' => 2 ], + ], + ]; + $otherTestCases[] = [ + $this->getMockDiff( [ $this->getMockDiffOp( 'delete', [ 'd1' ] ) ] ), + [ [ 'action' => 'delete', 'old' => 'd1', 'oldline' => 1 ] ], + ]; + $otherTestCases[] = [ + $this->getMockDiff( [ $this->getMockDiffOp( 'delete', [ 'd1', 'd2' ] ) ] ), + [ + [ 'action' => 'delete', 'old' => 'd1', 'oldline' => 1 ], + [ 'action' => 'delete', 'old' => 'd2', 'oldline' => 2 ], + ], + ]; + $otherTestCases[] = [ + $this->getMockDiff( [ $this->getMockDiffOp( 'change', [ 'd1' ], [ 'a1' ] ) ] ), + [ [ + 'action' => 'change', + 'old' => 'd1', + 'new' => 'mockLine', + 'newline' => 1, 'oldline' => 1 + ] ], + ]; + $otherTestCases[] = [ + $this->getMockDiff( [ $this->getMockDiffOp( + 'change', + [ 'd1', 'd2' ], + [ 'a1', 'a2' ] + ) ] ), + [ + [ + 'action' => 'change', + 'old' => 'd1', + 'new' => 'mockLine', + 'newline' => 1, 'oldline' => 1 + ], + [ + 'action' => 'change', + 'old' => 'd2', + 'new' => 'mockLine', + 'newline' => 2, 'oldline' => 2 + ], + ], + ]; + + $testCases = []; + foreach ( $emptyArrayTestCases as $testCase ) { + $testCases[] = [ $testCase, [] ]; + } + foreach ( $otherTestCases as $testCase ) { + $testCases[] = [ $testCase[0], $testCase[1] ]; + } + return $testCases; + } + +} diff --git a/tests/phpunit/unit/includes/diff/DiffOpTest.php b/tests/phpunit/unit/includes/diff/DiffOpTest.php new file mode 100644 index 0000000000..4e1aced7a6 --- /dev/null +++ b/tests/phpunit/unit/includes/diff/DiffOpTest.php @@ -0,0 +1,68 @@ +type = 'foo'; + $this->assertEquals( 'foo', $obj->getType() ); + } + + /** + * @covers DiffOp::getOrig + */ + public function testGetOrig() { + $obj = new FakeDiffOp(); + $obj->orig = [ 'foo' ]; + $this->assertEquals( [ 'foo' ], $obj->getOrig() ); + } + + /** + * @covers DiffOp::getClosing + */ + public function testGetClosing() { + $obj = new FakeDiffOp(); + $obj->closing = [ 'foo' ]; + $this->assertEquals( [ 'foo' ], $obj->getClosing() ); + } + + /** + * @covers DiffOp::getClosing + */ + public function testGetClosingWithParameter() { + $obj = new FakeDiffOp(); + $obj->closing = [ 'foo', 'bar', 'baz' ]; + $this->assertEquals( 'foo', $obj->getClosing( 0 ) ); + $this->assertEquals( 'bar', $obj->getClosing( 1 ) ); + $this->assertEquals( 'baz', $obj->getClosing( 2 ) ); + $this->assertEquals( null, $obj->getClosing( 3 ) ); + } + + /** + * @covers DiffOp::norig + */ + public function testNorig() { + $obj = new FakeDiffOp(); + $this->assertEquals( 0, $obj->norig() ); + $obj->orig = [ 'foo' ]; + $this->assertEquals( 1, $obj->norig() ); + } + + /** + * @covers DiffOp::nclosing + */ + public function testNclosing() { + $obj = new FakeDiffOp(); + $this->assertEquals( 0, $obj->nclosing() ); + $obj->closing = [ 'foo' ]; + $this->assertEquals( 1, $obj->nclosing() ); + } + +} diff --git a/tests/phpunit/unit/includes/diff/DiffTest.php b/tests/phpunit/unit/includes/diff/DiffTest.php new file mode 100644 index 0000000000..f0a8490f72 --- /dev/null +++ b/tests/phpunit/unit/includes/diff/DiffTest.php @@ -0,0 +1,19 @@ +edits = 'FooBarBaz'; + $this->assertEquals( 'FooBarBaz', $obj->getEdits() ); + } + +} diff --git a/tests/phpunit/unit/includes/exception/MWExceptionHandlerTest.php b/tests/phpunit/unit/includes/exception/MWExceptionHandlerTest.php new file mode 100644 index 0000000000..2b021c4f77 --- /dev/null +++ b/tests/phpunit/unit/includes/exception/MWExceptionHandlerTest.php @@ -0,0 +1,74 @@ +getTrace(); + $hasObject = false; + $hasArray = false; + foreach ( $trace as $frame ) { + if ( !isset( $frame['args'] ) ) { + continue; + } + foreach ( $frame['args'] as $arg ) { + $hasObject = $hasObject || is_object( $arg ); + $hasArray = $hasArray || is_array( $arg ); + } + + if ( $hasObject && $hasArray ) { + break; + } + } + $this->assertTrue( $hasObject, + "The stacktrace must have a function having an object has parameter" ); + $this->assertTrue( $hasArray, + "The stacktrace must have a function having an array has parameter" ); + + # Now we redact the trace.. and make sure no function arguments are + # arrays or objects. + $redacted = MWExceptionHandler::getRedactedTrace( $e ); + + foreach ( $redacted as $frame ) { + if ( !isset( $frame['args'] ) ) { + continue; + } + foreach ( $frame['args'] as $arg ) { + $this->assertNotInternalType( 'array', $arg ); + $this->assertNotInternalType( 'object', $arg ); + } + } + + $this->assertEquals( 'value', $refvar, 'Ensuring reference variable wasn\'t changed' ); + } + + /** + * Helper function for testExpandArgumentsInCall + * + * Pass it an object and an array, and something by reference :-) + * + * @throws Exception + */ + protected static function helperThrowAnException( $a, $b, &$c ) { + throw new Exception(); + } +} diff --git a/tests/phpunit/unit/includes/installer/InstallDocFormatterTest.php b/tests/phpunit/unit/includes/installer/InstallDocFormatterTest.php new file mode 100644 index 0000000000..fddc3b8680 --- /dev/null +++ b/tests/phpunit/unit/includes/installer/InstallDocFormatterTest.php @@ -0,0 +1,83 @@ +assertEquals( + $expected, + InstallDocFormatter::format( $unformattedText ), + $message + ); + } + + /** + * Provider for testFormat() + */ + public static function provideDocFormattingTests() { + # Format: (expected string, unformattedText string, optional message) + return [ + # Escape some wikitext + [ 'Install <tag>', 'Install ', 'Escaping <' ], + [ 'Install {{template}}', 'Install {{template}}', 'Escaping [[' ], + [ 'Install [[page]]', 'Install [[page]]', 'Escaping {{' ], + [ 'Install __TOC__', 'Install __TOC__', 'Escaping __' ], + [ 'Install ', "Install \r", 'Removing \r' ], + + # Transform \t{1,2} into :{1,2} + [ ':One indentation', "\tOne indentation", 'Replacing a single \t' ], + [ '::Two indentations', "\t\tTwo indentations", 'Replacing 2 x \t' ], + + # Transform 'T123' links + [ + '[https://phabricator.wikimedia.org/T123 T123]', + 'T123', 'Testing T123 links' ], + [ + 'bug [https://phabricator.wikimedia.org/T123 T123]', + 'bug T123', 'Testing bug T123 links' ], + [ + '([https://phabricator.wikimedia.org/T987654 T987654])', + '(T987654)', 'Testing (T987654) links' ], + + # "Tabc" shouldn't work + [ 'Tfoobar', 'Tfoobar', "Don't match T followed by non-digits" ], + [ 'T!!fakefake!!', 'T!!fakefake!!', "Don't match T followed by non-digits" ], + + # Transform 'bug 123' links + [ + '[https://bugzilla.wikimedia.org/123 bug 123]', + 'bug 123', 'Testing bug 123 links' ], + [ + '([https://bugzilla.wikimedia.org/987654 bug 987654])', + '(bug 987654)', 'Testing (bug 987654) links' ], + + # "bug abc" shouldn't work + [ 'bug foobar', 'bug foobar', "Don't match bug followed by non-digits" ], + [ 'bug !!fakefake!!', 'bug !!fakefake!!', "Don't match bug followed by non-digits" ], + + # Transform '$wgFooBar' links + [ + '' + . '[https://www.mediawiki.org/wiki/Manual:$wgFooBar $wgFooBar]', + '$wgFooBar', 'Testing basic $wgFooBar' ], + [ + '' + . '[https://www.mediawiki.org/wiki/Manual:$wgFooBar45 $wgFooBar45]', + '$wgFooBar45', 'Testing $wgFooBar45 (with numbers)' ], + [ + '' + . '[https://www.mediawiki.org/wiki/Manual:$wgFoo_Bar $wgFoo_Bar]', + '$wgFoo_Bar', 'Testing $wgFoo_Bar (with underscore)' ], + + # Icky variables that shouldn't link + [ + '$myAwesomeVariable', + '$myAwesomeVariable', + 'Testing $myAwesomeVariable (not starting with $wg)' + ], + [ '$()not!a&Var', '$()not!a&Var', 'Testing $()not!a&Var (obviously not a variable)' ], + ]; + } +} diff --git a/tests/phpunit/unit/includes/installer/OracleInstallerTest.php b/tests/phpunit/unit/includes/installer/OracleInstallerTest.php new file mode 100644 index 0000000000..69b5552a56 --- /dev/null +++ b/tests/phpunit/unit/includes/installer/OracleInstallerTest.php @@ -0,0 +1,48 @@ +assertEquals( $expected, + OracleInstaller::checkConnectStringFormat( $connectString ), + $msg + ); + } + + /** + * Provider to test OracleInstaller::checkConnectStringFormat() + */ + function provideOracleConnectStrings() { + // expected result, connectString[, message] + return [ + [ true, 'simple_01', 'Simple TNS name' ], + [ true, 'simple_01.world', 'TNS name with domain' ], + [ true, 'simple_01.domain.net', 'TNS name with domain' ], + [ true, 'host123', 'Host only' ], + [ true, 'host123.domain.net', 'FQDN only' ], + [ true, '//host123.domain.net', 'FQDN URL only' ], + [ true, '123.223.213.132', 'Host IP only' ], + [ true, 'host:1521', 'Host and port' ], + [ true, 'host:1521/service', 'Host, port and service' ], + [ true, 'host:1521/service:shared', 'Host, port, service and shared server type' ], + [ true, 'host:1521/service:dedicated', 'Host, port, service and dedicated server type' ], + [ true, 'host:1521/service:pooled', 'Host, port, service and pooled server type' ], + [ + true, + 'host:1521/service:shared/instance1', + 'Host, port, service, server type and instance' + ], + [ true, 'host:1521//instance1', 'Host, port and instance' ], + ]; + } + +} diff --git a/tests/phpunit/unit/includes/interwiki/InterwikiLookupAdapterTest.php b/tests/phpunit/unit/includes/interwiki/InterwikiLookupAdapterTest.php new file mode 100644 index 0000000000..abbd2d778e --- /dev/null +++ b/tests/phpunit/unit/includes/interwiki/InterwikiLookupAdapterTest.php @@ -0,0 +1,133 @@ +interwikiLookup = new InterwikiLookupAdapter( + $this->getSiteLookup( $this->getSites() ) + ); + } + + public function testIsValidInterwiki() { + $this->assertTrue( + $this->interwikiLookup->isValidInterwiki( 'enwt' ), + 'enwt known prefix is valid' + ); + $this->assertTrue( + $this->interwikiLookup->isValidInterwiki( 'foo' ), + 'foo site known prefix is valid' + ); + $this->assertFalse( + $this->interwikiLookup->isValidInterwiki( 'xyz' ), + 'unknown prefix is not valid' + ); + } + + public function testFetch() { + $interwiki = $this->interwikiLookup->fetch( '' ); + $this->assertNull( $interwiki ); + + $interwiki = $this->interwikiLookup->fetch( 'xyz' ); + $this->assertFalse( $interwiki ); + + $interwiki = $this->interwikiLookup->fetch( 'foo' ); + $this->assertInstanceOf( Interwiki::class, $interwiki ); + $this->assertSame( 'foobar', $interwiki->getWikiID() ); + + $interwiki = $this->interwikiLookup->fetch( 'enwt' ); + $this->assertInstanceOf( Interwiki::class, $interwiki ); + + $this->assertSame( 'https://en.wiktionary.org/wiki/$1', $interwiki->getURL(), 'getURL' ); + $this->assertSame( 'https://en.wiktionary.org/w/api.php', $interwiki->getAPI(), 'getAPI' ); + $this->assertSame( 'enwiktionary', $interwiki->getWikiID(), 'getWikiID' ); + $this->assertTrue( $interwiki->isLocal(), 'isLocal' ); + } + + public function testGetAllPrefixes() { + $foo = [ + 'iw_prefix' => 'foo', + 'iw_url' => '', + 'iw_api' => '', + 'iw_wikiid' => 'foobar', + 'iw_local' => false, + 'iw_trans' => false, + ]; + $enwt = [ + 'iw_prefix' => 'enwt', + 'iw_url' => 'https://en.wiktionary.org/wiki/$1', + 'iw_api' => 'https://en.wiktionary.org/w/api.php', + 'iw_wikiid' => 'enwiktionary', + 'iw_local' => true, + 'iw_trans' => false, + ]; + + $this->assertEquals( + [ $foo, $enwt ], + $this->interwikiLookup->getAllPrefixes(), + 'getAllPrefixes()' + ); + + $this->assertEquals( + [ $foo ], + $this->interwikiLookup->getAllPrefixes( false ), + 'get external prefixes' + ); + + $this->assertEquals( + [ $enwt ], + $this->interwikiLookup->getAllPrefixes( true ), + 'get local prefixes' + ); + } + + private function getSiteLookup( SiteList $sites ) { + $siteLookup = $this->getMockBuilder( SiteLookup::class ) + ->disableOriginalConstructor() + ->getMock(); + + $siteLookup->expects( $this->any() ) + ->method( 'getSites' ) + ->will( $this->returnValue( $sites ) ); + + return $siteLookup; + } + + private function getSites() { + $sites = []; + + $site = new Site(); + $site->setGlobalId( 'foobar' ); + $site->addInterwikiId( 'foo' ); + $site->setSource( 'external' ); + $sites[] = $site; + + $site = new MediaWikiSite(); + $site->setGlobalId( 'enwiktionary' ); + $site->setGroup( 'wiktionary' ); + $site->setLanguageCode( 'en' ); + $site->addNavigationId( 'enwiktionary' ); + $site->addInterwikiId( 'enwt' ); + $site->setSource( 'local' ); + $site->setPath( MediaWikiSite::PATH_PAGE, "https://en.wiktionary.org/wiki/$1" ); + $site->setPath( MediaWikiSite::PATH_FILE, "https://en.wiktionary.org/w/$1" ); + $sites[] = $site; + + return new SiteList( $sites ); + } + +} diff --git a/tests/phpunit/unit/includes/libs/objectcache/ReplicatedBagOStuffTest.php b/tests/phpunit/unit/includes/libs/objectcache/ReplicatedBagOStuffTest.php new file mode 100644 index 0000000000..64d282f487 --- /dev/null +++ b/tests/phpunit/unit/includes/libs/objectcache/ReplicatedBagOStuffTest.php @@ -0,0 +1,62 @@ +writeCache = new HashBagOStuff(); + $this->readCache = new HashBagOStuff(); + $this->cache = new ReplicatedBagOStuff( [ + 'writeFactory' => $this->writeCache, + 'readFactory' => $this->readCache, + ] ); + } + + /** + * @covers ReplicatedBagOStuff::set + */ + public function testSet() { + $key = 'a key'; + $value = 'a value'; + $this->cache->set( $key, $value ); + + // Write to master. + $this->assertEquals( $value, $this->writeCache->get( $key ) ); + // Don't write to replica. Replication is deferred to backend. + $this->assertFalse( $this->readCache->get( $key ) ); + } + + /** + * @covers ReplicatedBagOStuff::get + */ + public function testGet() { + $key = 'a key'; + + $write = 'one value'; + $this->writeCache->set( $key, $write ); + $read = 'another value'; + $this->readCache->set( $key, $read ); + + // Read from replica. + $this->assertEquals( $read, $this->cache->get( $key ) ); + } + + /** + * @covers ReplicatedBagOStuff::get + */ + public function testGetAbsent() { + $key = 'a key'; + $value = 'a value'; + $this->writeCache->set( $key, $value ); + + // Don't read from master. No failover if value is absent. + $this->assertFalse( $this->cache->get( $key ) ); + } +} diff --git a/tests/phpunit/unit/includes/media/IPTCTest.php b/tests/phpunit/unit/includes/media/IPTCTest.php new file mode 100644 index 0000000000..430493cd20 --- /dev/null +++ b/tests/phpunit/unit/includes/media/IPTCTest.php @@ -0,0 +1,85 @@ +assertEquals( 'UTF-8', $res ); + } + + /** + * @covers IPTC::parse + */ + public function testIPTCParseNoCharset88591() { + // basically IPTC for keyword with value of 0xBC which is 1/4 in iso-8859-1 + // This data doesn't specify a charset. We're supposed to guess + // (which basically means utf-8 if valid, windows 1252 (iso 8859-1) if not) + $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x06\x1c\x02\x19\x00\x01\xBC"; + $res = IPTC::parse( $iptcData ); + $this->assertEquals( [ '¼' ], $res['Keywords'] ); + } + + /** + * @covers IPTC::parse + */ + public function testIPTCParseNoCharset88591b() { + /* This one contains a sequence that's valid iso 8859-1 but not valid utf8 */ + /* \xC3 = Ã, \xB8 = ¸ */ + $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x09\x1c\x02\x19\x00\x04\xC3\xC3\xC3\xB8"; + $res = IPTC::parse( $iptcData ); + $this->assertEquals( [ 'ÃÃø' ], $res['Keywords'] ); + } + + /** + * Same as testIPTCParseNoCharset88591b, but forcing the charset to utf-8. + * What should happen is the first "\xC3\xC3" should be dropped as invalid, + * leaving \xC3\xB8, which is ø + * @covers IPTC::parse + */ + public function testIPTCParseForcedUTFButInvalid() { + $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x11\x1c\x02\x19\x00\x04\xC3\xC3\xC3\xB8" + . "\x1c\x01\x5A\x00\x03\x1B\x25\x47"; + $res = IPTC::parse( $iptcData ); + $this->assertEquals( [ 'ø' ], $res['Keywords'] ); + } + + /** + * @covers IPTC::parse + */ + public function testIPTCParseNoCharsetUTF8() { + $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x07\x1c\x02\x19\x00\x02¼"; + $res = IPTC::parse( $iptcData ); + $this->assertEquals( [ '¼' ], $res['Keywords'] ); + } + + /** + * Testing something that has 2 values for keyword + * @covers IPTC::parse + */ + public function testIPTCParseMulti() { + $iptcData = /* identifier */ "Photoshop 3.0\08BIM\4\4" + /* length */ . "\0\0\0\0\0\x0D" + . "\x1c\x02\x19" . "\x00\x01" . "\xBC" + . "\x1c\x02\x19" . "\x00\x02" . "\xBC\xBD"; + $res = IPTC::parse( $iptcData ); + $this->assertEquals( [ '¼', '¼½' ], $res['Keywords'] ); + } + + /** + * @covers IPTC::parse + */ + public function testIPTCParseUTF8() { + // This has the magic "\x1c\x01\x5A\x00\x03\x1B\x25\x47" which marks content as UTF8. + $iptcData = + "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x0F\x1c\x02\x19\x00\x02¼\x1c\x01\x5A\x00\x03\x1B\x25\x47"; + $res = IPTC::parse( $iptcData ); + $this->assertEquals( [ '¼' ], $res['Keywords'] ); + } +} diff --git a/tests/phpunit/unit/includes/media/MediaHandlerTest.php b/tests/phpunit/unit/includes/media/MediaHandlerTest.php new file mode 100644 index 0000000000..eb4ece88c4 --- /dev/null +++ b/tests/phpunit/unit/includes/media/MediaHandlerTest.php @@ -0,0 +1,68 @@ +assertEquals( $expected, + $result, + "($width, $height, $max) wanted: {$expected}x$y, got: {z$result}x$y2" ); + } + + public static function provideTestFitBoxWidth() { + return array_merge( + static::generateTestFitBoxWidthData( 50, 50, [ + 50 => 50, + 17 => 17, + 18 => 18 ] + ), + static::generateTestFitBoxWidthData( 366, 300, [ + 50 => 61, + 17 => 21, + 18 => 22 ] + ), + static::generateTestFitBoxWidthData( 300, 366, [ + 50 => 41, + 17 => 14, + 18 => 15 ] + ), + static::generateTestFitBoxWidthData( 100, 400, [ + 50 => 12, + 17 => 4, + 18 => 4 ] + ) + ); + } + + /** + * Generate single test cases by combining the dimensions and tests contents + * + * It creates: + * [$width, $height, $max, $expected], + * [$width, $height, $max2, $expected2], ... + * out of parameters: + * $width, $height, { $max => $expected, $max2 => $expected2, ... } + * + * @param int $width + * @param int $height + * @param array $tests associative array of $max => $expected values + * @return array + */ + private static function generateTestFitBoxWidthData( $width, $height, $tests ) { + $result = []; + foreach ( $tests as $max => $expected ) { + $result[] = [ $width, $height, $max, $expected ]; + } + return $result; + } +} diff --git a/tests/phpunit/unit/includes/objectcache/MemcachedBagOStuffTest.php b/tests/phpunit/unit/includes/objectcache/MemcachedBagOStuffTest.php new file mode 100644 index 0000000000..eb040b4501 --- /dev/null +++ b/tests/phpunit/unit/includes/objectcache/MemcachedBagOStuffTest.php @@ -0,0 +1,107 @@ +cache = new MemcachedPhpBagOStuff( [ 'keyspace' => 'test', 'servers' => [] ] ); + } + + /** + * @covers MemcachedBagOStuff::makeKey + */ + public function testKeyNormalization() { + $this->assertEquals( + 'test:vanilla', + $this->cache->makeKey( 'vanilla' ) + ); + + $this->assertEquals( + 'test:punctuation_marks_are_ok:!@$^&*()', + $this->cache->makeKey( 'punctuation_marks_are_ok', '!@$^&*()' ) + ); + + $this->assertEquals( + 'test:but_spaces:hashes%23:and%0Anewlines:are_not', + $this->cache->makeKey( 'but spaces', 'hashes#', "and\nnewlines", 'are_not' ) + ); + + $this->assertEquals( + 'test:this:key:contains:%F0%9D%95%9E%F0%9D%95%A6%F0%9D%95%9D%F0%9D%95%A5%F0%9' . + 'D%95%9A%F0%9D%95%93%F0%9D%95%AA%F0%9D%95%A5%F0%9D%95%96:characters', + $this->cache->makeKey( 'this', 'key', 'contains', '𝕞𝕦𝕝𝕥𝕚𝕓𝕪𝕥𝕖', 'characters' ) + ); + + $this->assertEquals( + 'test:this:key:contains:#c118f92685a635cb843039de50014c9c', + $this->cache->makeKey( 'this', 'key', 'contains', '𝕥𝕠𝕠 𝕞𝕒𝕟𝕪 𝕞𝕦𝕝𝕥𝕚𝕓𝕪𝕥𝕖 𝕔𝕙𝕒𝕣𝕒𝕔𝕥𝕖𝕣𝕤' ) + ); + + $this->assertEquals( + 'test:BagOStuff-long-key:##dc89dcb43b28614da27660240af478b5', + $this->cache->makeKey( '𝕖𝕧𝕖𝕟', '𝕚𝕗', '𝕨𝕖', '𝕄𝔻𝟝', '𝕖𝕒𝕔𝕙', + '𝕒𝕣𝕘𝕦𝕞𝕖𝕟𝕥', '𝕥𝕙𝕚𝕤', '𝕜𝕖𝕪', '𝕨𝕠𝕦𝕝𝕕', '𝕤𝕥𝕚𝕝𝕝', '𝕓𝕖', '𝕥𝕠𝕠', '𝕝𝕠𝕟𝕘' ) + ); + + $this->assertEquals( + 'test:%23%235820ad1d105aa4dc698585c39df73e19', + $this->cache->makeKey( '##5820ad1d105aa4dc698585c39df73e19' ) + ); + + $this->assertEquals( + 'test:percent_is_escaped:!@$%25^&*()', + $this->cache->makeKey( 'percent_is_escaped', '!@$%^&*()' ) + ); + + $this->assertEquals( + 'test:colon_is_escaped:!@$%3A^&*()', + $this->cache->makeKey( 'colon_is_escaped', '!@$:^&*()' ) + ); + + $this->assertEquals( + 'test:long_key_part_hashed:#0244f7b1811d982dd932dd7de01465ac', + $this->cache->makeKey( 'long_key_part_hashed', str_repeat( 'y', 500 ) ) + ); + } + + /** + * @dataProvider validKeyProvider + * @covers MemcachedBagOStuff::validateKeyEncoding + */ + public function testValidateKeyEncoding( $key ) { + $this->assertSame( $key, $this->cache->validateKeyEncoding( $key ) ); + } + + public function validKeyProvider() { + return [ + 'empty' => [ '' ], + 'digits' => [ '09' ], + 'letters' => [ 'AZaz' ], + 'ASCII special characters' => [ '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' ], + ]; + } + + /** + * @dataProvider invalidKeyProvider + * @covers MemcachedBagOStuff::validateKeyEncoding + */ + public function testValidateKeyEncodingThrowsException( $key ) { + $this->setExpectedException( Exception::class ); + $this->cache->validateKeyEncoding( $key ); + } + + public function invalidKeyProvider() { + return [ + [ "\x00" ], + [ ' ' ], + [ "\x1F" ], + [ "\x7F" ], + [ "\x80" ], + [ "\xFF" ], + ]; + } +} diff --git a/tests/phpunit/unit/includes/objectcache/RESTBagOStuffTest.php b/tests/phpunit/unit/includes/objectcache/RESTBagOStuffTest.php new file mode 100644 index 0000000000..459e3eebc2 --- /dev/null +++ b/tests/phpunit/unit/includes/objectcache/RESTBagOStuffTest.php @@ -0,0 +1,96 @@ +client = + $this->getMockBuilder( MultiHttpClient::class ) + ->setConstructorArgs( [ [] ] ) + ->setMethods( [ 'run' ] ) + ->getMock(); + $this->bag = new RESTBagOStuff( [ 'client' => $this->client, 'url' => 'http://test/rest/' ] ); + } + + public function testGet() { + $this->client->expects( $this->once() )->method( 'run' )->with( [ + 'method' => 'GET', + 'url' => 'http://test/rest/42xyz42', + 'headers' => [] + // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) + ] )->willReturn( [ 200, 'OK', [], '"somedata"', 0 ] ); + $result = $this->bag->get( '42xyz42' ); + $this->assertEquals( 'somedata', $result ); + } + + public function testGetNotExist() { + $this->client->expects( $this->once() )->method( 'run' )->with( [ + 'method' => 'GET', + 'url' => 'http://test/rest/42xyz42', + 'headers' => [] + // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) + ] )->willReturn( [ 404, 'Not found', [], 'Nothing to see here', 0 ] ); + $result = $this->bag->get( '42xyz42' ); + $this->assertFalse( $result ); + } + + public function testGetBadClient() { + $this->client->expects( $this->once() )->method( 'run' )->with( [ + 'method' => 'GET', + 'url' => 'http://test/rest/42xyz42', + 'headers' => [] + // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) + ] )->willReturn( [ 0, '', [], '', 'cURL has failed you today' ] ); + $result = $this->bag->get( '42xyz42' ); + $this->assertFalse( $result ); + $this->assertEquals( BagOStuff::ERR_UNREACHABLE, $this->bag->getLastError() ); + } + + public function testGetBadServer() { + $this->client->expects( $this->once() )->method( 'run' )->with( [ + 'method' => 'GET', + 'url' => 'http://test/rest/42xyz42', + 'headers' => [] + // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) + ] )->willReturn( [ 500, 'Too busy', [], 'Server is too busy', '' ] ); + $result = $this->bag->get( '42xyz42' ); + $this->assertFalse( $result ); + $this->assertEquals( BagOStuff::ERR_UNEXPECTED, $this->bag->getLastError() ); + } + + public function testPut() { + $this->client->expects( $this->once() )->method( 'run' )->with( [ + 'method' => 'PUT', + 'url' => 'http://test/rest/42xyz42', + 'body' => '"postdata"', + 'headers' => [] + // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) + ] )->willReturn( [ 200, 'OK', [], 'Done', 0 ] ); + $result = $this->bag->set( '42xyz42', 'postdata' ); + $this->assertTrue( $result ); + } + + public function testDelete() { + $this->client->expects( $this->once() )->method( 'run' )->with( [ + 'method' => 'DELETE', + 'url' => 'http://test/rest/42xyz42', + 'headers' => [] + // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) + ] )->willReturn( [ 200, 'OK', [], 'Done', 0 ] ); + $result = $this->bag->delete( '42xyz42' ); + $this->assertTrue( $result ); + } +} diff --git a/tests/phpunit/unit/includes/parser/TidyTest.php b/tests/phpunit/unit/includes/parser/TidyTest.php new file mode 100644 index 0000000000..1adb6a6444 --- /dev/null +++ b/tests/phpunit/unit/includes/parser/TidyTest.php @@ -0,0 +1,64 @@ +markTestSkipped( 'Tidy not found' ); + } + } + + /** + * @dataProvider provideTestWrapping + */ + public function testTidyWrapping( $expected, $text, $msg = '' ) { + $text = MWTidy::tidy( $text ); + // We don't care about where Tidy wants to stick is

s + $text = trim( preg_replace( '##', '', $text ) ); + // Windows, we love you! + $text = str_replace( "\r", '', $text ); + $this->assertEquals( $expected, $text, $msg ); + } + + public static function provideTestWrapping() { + $testMathML = <<<'MathML' + + + a + + + x + 2 + + + + b + + x + + + c + + +MathML; + return [ + [ + 'foo', + 'foo', + ' should survive tidy' + ], + [ + 'foo', + 'foo', + ' should survive tidy' + ], + [ 'foo', 'foo', ' should survive tidy' ], + [ "foo", 'foo', ' should survive tidy' ], + [ "foo", 'foo', ' should survive tidy' ], + [ $testMathML, $testMathML, ' should survive tidy' ], + ]; + } +} diff --git a/tests/phpunit/unit/includes/password/PasswordTest.php b/tests/phpunit/unit/includes/password/PasswordTest.php new file mode 100644 index 0000000000..b41c0f4150 --- /dev/null +++ b/tests/phpunit/unit/includes/password/PasswordTest.php @@ -0,0 +1,33 @@ +newFromPlaintext( null ); + + $this->assertInstanceOf( InvalidPassword::class, $invalid ); + } +} diff --git a/tests/phpunit/unit/includes/preferences/FiltersTest.php b/tests/phpunit/unit/includes/preferences/FiltersTest.php new file mode 100644 index 0000000000..d2b5d05bb3 --- /dev/null +++ b/tests/phpunit/unit/includes/preferences/FiltersTest.php @@ -0,0 +1,141 @@ +filterFromForm( '0' ) ); + self::assertSame( 3, $filter->filterFromForm( '3' ) ); + self::assertSame( '123', $filter->filterForForm( '123' ) ); + } + + /** + * @covers MediaWiki\Preferences\TimezoneFilter::filterFromForm() + * @dataProvider provideTimezoneFilter + * + * @param string $input + * @param string $expected + */ + public function testTimezoneFilter( $input, $expected ) { + $filter = new TimezoneFilter(); + $result = $filter->filterFromForm( $input ); + self::assertEquals( $expected, $result ); + } + + public function provideTimezoneFilter() { + return [ + [ 'ZoneInfo', 'Offset|0' ], + [ 'ZoneInfo|bogus', 'Offset|0' ], + [ 'System', 'System' ], + [ '2:30', 'Offset|150' ], + ]; + } + + /** + * @covers MediaWiki\Preferences\MultiUsernameFilter::filterFromForm() + * @dataProvider provideMultiUsernameFilterFrom + * + * @param string $input + * @param string|null $expected + */ + public function testMultiUsernameFilterFrom( $input, $expected ) { + $filter = $this->makeMultiUsernameFilter(); + $result = $filter->filterFromForm( $input ); + self::assertSame( $expected, $result ); + } + + public function provideMultiUsernameFilterFrom() { + return [ + [ '', null ], + [ "\n\n\n", null ], + [ 'Foo', '1' ], + [ "\n\n\nFoo\nBar\n", "1\n2" ], + [ "Baz\nInvalid\nFoo", "3\n1" ], + [ "Invalid", null ], + [ "Invalid\n\n\nInvalid\n", null ], + ]; + } + + /** + * @covers MediaWiki\Preferences\MultiUsernameFilter::filterForForm() + * @dataProvider provideMultiUsernameFilterFor + * + * @param string $input + * @param string $expected + */ + public function testMultiUsernameFilterFor( $input, $expected ) { + $filter = $this->makeMultiUsernameFilter(); + $result = $filter->filterForForm( $input ); + self::assertSame( $expected, $result ); + } + + public function provideMultiUsernameFilterFor() { + return [ + [ '', '' ], + [ "\n", '' ], + [ '1', 'Foo' ], + [ "\n1\n\n2\377\n", "Foo\nBar" ], + [ "666\n667", '' ], + ]; + } + + private function makeMultiUsernameFilter() { + $userMapping = [ + 'Foo' => 1, + 'Bar' => 2, + 'Baz' => 3, + ]; + $flipped = array_flip( $userMapping ); + $idLookup = self::getMockBuilder( CentralIdLookup::class ) + ->disableOriginalConstructor() + ->setMethods( [ 'centralIdsFromNames', 'namesFromCentralIds' ] ) + ->getMockForAbstractClass(); + + $idLookup->method( 'centralIdsFromNames' ) + ->will( self::returnCallback( function ( $names ) use ( $userMapping ) { + $ids = []; + foreach ( $names as $name ) { + $ids[] = $userMapping[$name] ?? null; + } + return array_filter( $ids, 'is_numeric' ); + } ) ); + $idLookup->method( 'namesFromCentralIds' ) + ->will( self::returnCallback( function ( $ids ) use ( $flipped ) { + $names = []; + foreach ( $ids as $id ) { + $names[] = $flipped[$id] ?? null; + } + return array_filter( $names, 'is_string' ); + } ) ); + + return new MultiUsernameFilter( $idLookup ); + } +} diff --git a/tests/phpunit/unit/includes/registration/ExtensionProcessorTest.php b/tests/phpunit/unit/includes/registration/ExtensionProcessorTest.php new file mode 100644 index 0000000000..13de142ae6 --- /dev/null +++ b/tests/phpunit/unit/includes/registration/ExtensionProcessorTest.php @@ -0,0 +1,829 @@ +dir = __DIR__ . '/FooBar/extension.json'; + $this->dirname = dirname( $this->dir ); + } + + /** + * 'name' is absolutely required + * + * @var array + */ + public static $default = [ + 'name' => 'FooBar', + ]; + + public function testExtractInfo() { + // Test that attributes that begin with @ are ignored + $processor = new ExtensionProcessor(); + $processor->extractInfo( $this->dir, self::$default + [ + '@metadata' => [ 'foobarbaz' ], + 'AnAttribute' => [ 'omg' ], + 'AutoloadClasses' => [ 'FooBar' => 'includes/FooBar.php' ], + 'SpecialPages' => [ 'Foo' => 'SpecialFoo' ], + 'callback' => 'FooBar::onRegistration', + ], 1 ); + + $extracted = $processor->getExtractedInfo(); + $attributes = $extracted['attributes']; + $this->assertArrayHasKey( 'AnAttribute', $attributes ); + $this->assertArrayNotHasKey( '@metadata', $attributes ); + $this->assertArrayNotHasKey( 'AutoloadClasses', $attributes ); + $this->assertSame( + [ 'FooBar' => 'FooBar::onRegistration' ], + $extracted['callbacks'] + ); + $this->assertSame( + [ 'Foo' => 'SpecialFoo' ], + $extracted['globals']['wgSpecialPages'] + ); + } + + public function testExtractNamespaces() { + // Test that namespace IDs can be overwritten + if ( !defined( 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X' ) ) { + define( 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X', 123456 ); + } + + $processor = new ExtensionProcessor(); + $processor->extractInfo( $this->dir, self::$default + [ + 'namespaces' => [ + [ + 'id' => 332200, + 'constant' => 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A', + 'name' => 'Test_A', + 'defaultcontentmodel' => 'TestModel', + 'gender' => [ + 'male' => 'Male test', + 'female' => 'Female test', + ], + 'subpages' => true, + 'content' => true, + 'protection' => 'userright', + ], + [ // Test_X will use ID 123456 not 334400 + 'id' => 334400, + 'constant' => 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X', + 'name' => 'Test_X', + 'defaultcontentmodel' => 'TestModel' + ], + ] + ], 1 ); + + $extracted = $processor->getExtractedInfo(); + + $this->assertArrayHasKey( + 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A', + $extracted['defines'] + ); + $this->assertArrayNotHasKey( + 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X', + $extracted['defines'] + ); + + $this->assertSame( + $extracted['defines']['MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A'], + 332200 + ); + + $this->assertArrayHasKey( 'ExtensionNamespaces', $extracted['attributes'] ); + $this->assertArrayHasKey( 123456, $extracted['attributes']['ExtensionNamespaces'] ); + $this->assertArrayHasKey( 332200, $extracted['attributes']['ExtensionNamespaces'] ); + $this->assertArrayNotHasKey( 334400, $extracted['attributes']['ExtensionNamespaces'] ); + + $this->assertSame( 'Test_X', $extracted['attributes']['ExtensionNamespaces'][123456] ); + $this->assertSame( 'Test_A', $extracted['attributes']['ExtensionNamespaces'][332200] ); + $this->assertSame( + [ 'male' => 'Male test', 'female' => 'Female test' ], + $extracted['globals']['wgExtraGenderNamespaces'][332200] + ); + // A has subpages, X does not + $this->assertTrue( $extracted['globals']['wgNamespacesWithSubpages'][332200] ); + $this->assertArrayNotHasKey( 123456, $extracted['globals']['wgNamespacesWithSubpages'] ); + } + + public static function provideRegisterHooks() { + $merge = [ ExtensionRegistry::MERGE_STRATEGY => 'array_merge_recursive' ]; + // Format: + // Current $wgHooks + // Content in extension.json + // Expected value of $wgHooks + return [ + // No hooks + [ + [], + self::$default, + $merge, + ], + // No current hooks, adding one for "FooBaz" in string format + [ + [], + [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default, + [ 'FooBaz' => [ 'FooBazCallback' ] ] + $merge, + ], + // Hook for "FooBaz", adding another one + [ + [ 'FooBaz' => [ 'PriorCallback' ] ], + [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default, + [ 'FooBaz' => [ 'PriorCallback', 'FooBazCallback' ] ] + $merge, + ], + // No current hooks, adding one for "FooBaz" in verbose array format + [ + [], + [ 'Hooks' => [ 'FooBaz' => [ 'FooBazCallback' ] ] ] + self::$default, + [ 'FooBaz' => [ 'FooBazCallback' ] ] + $merge, + ], + // Hook for "BarBaz", adding one for "FooBaz" + [ + [ 'BarBaz' => [ 'BarBazCallback' ] ], + [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default, + [ + 'BarBaz' => [ 'BarBazCallback' ], + 'FooBaz' => [ 'FooBazCallback' ], + ] + $merge, + ], + // Callbacks for FooBaz wrapped in an array + [ + [], + [ 'Hooks' => [ 'FooBaz' => [ 'Callback1' ] ] ] + self::$default, + [ + 'FooBaz' => [ 'Callback1' ], + ] + $merge, + ], + // Multiple callbacks for FooBaz hook + [ + [], + [ 'Hooks' => [ 'FooBaz' => [ 'Callback1', 'Callback2' ] ] ] + self::$default, + [ + 'FooBaz' => [ 'Callback1', 'Callback2' ], + ] + $merge, + ], + ]; + } + + /** + * @dataProvider provideRegisterHooks + */ + public function testRegisterHooks( $pre, $info, $expected ) { + $processor = new MockExtensionProcessor( [ 'wgHooks' => $pre ] ); + $processor->extractInfo( $this->dir, $info, 1 ); + $extracted = $processor->getExtractedInfo(); + $this->assertEquals( $expected, $extracted['globals']['wgHooks'] ); + } + + public function testExtractConfig1() { + $processor = new ExtensionProcessor; + $info = [ + 'config' => [ + 'Bar' => 'somevalue', + 'Foo' => 10, + '@IGNORED' => 'yes', + ], + ] + self::$default; + $info2 = [ + 'config' => [ + '_prefix' => 'eg', + 'Bar' => 'somevalue' + ], + 'name' => 'FooBar2', + ]; + $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'] ); + $this->assertArrayNotHasKey( 'wg@IGNORED', $extracted['globals'] ); + // Custom prefix: + $this->assertEquals( 'somevalue', $extracted['globals']['egBar'] ); + } + + public function testExtractConfig2() { + $processor = new ExtensionProcessor; + $info = [ + 'config' => [ + 'Bar' => [ 'value' => 'somevalue' ], + 'Foo' => [ 'value' => 10 ], + 'Path' => [ 'value' => 'foo.txt', 'path' => true ], + 'Namespaces' => [ + 'value' => [ + '10' => true, + '12' => false, + ], + 'merge_strategy' => 'array_plus', + ], + ], + ] + self::$default; + $info2 = [ + 'config' => [ + 'Bar' => [ 'value' => 'somevalue' ], + ], + 'config_prefix' => 'eg', + 'name' => 'FooBar2', + ]; + $processor->extractInfo( $this->dir, $info, 2 ); + $processor->extractInfo( $this->dir, $info2, 2 ); + $extracted = $processor->getExtractedInfo(); + $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] ); + $this->assertEquals( 10, $extracted['globals']['wgFoo'] ); + $this->assertEquals( "{$this->dirname}/foo.txt", $extracted['globals']['wgPath'] ); + // Custom prefix: + $this->assertEquals( 'somevalue', $extracted['globals']['egBar'] ); + $this->assertSame( + [ 10 => true, 12 => false, ExtensionRegistry::MERGE_STRATEGY => 'array_plus' ], + $extracted['globals']['wgNamespaces'] + ); + } + + /** + * @expectedException RuntimeException + */ + public function testDuplicateConfigKey1() { + $processor = new ExtensionProcessor; + $info = [ + 'config' => [ + 'Bar' => '', + ] + ] + self::$default; + $info2 = [ + 'config' => [ + 'Bar' => 'g', + ], + 'name' => 'FooBar2', + ]; + $processor->extractInfo( $this->dir, $info, 1 ); + $processor->extractInfo( $this->dir, $info2, 1 ); + } + + /** + * @expectedException RuntimeException + */ + public function testDuplicateConfigKey2() { + $processor = new ExtensionProcessor; + $info = [ + 'config' => [ + 'Bar' => [ 'value' => 'somevalue' ], + ] + ] + self::$default; + $info2 = [ + 'config' => [ + 'Bar' => [ 'value' => 'somevalue' ], + ], + 'name' => 'FooBar2', + ]; + $processor->extractInfo( $this->dir, $info, 2 ); + $processor->extractInfo( $this->dir, $info2, 2 ); + } + + public static function provideExtractExtensionMessagesFiles() { + $dir = __DIR__ . '/FooBar/'; + return [ + [ + [ 'ExtensionMessagesFiles' => [ 'FooBarAlias' => 'FooBar.alias.php' ] ], + [ 'wgExtensionMessagesFiles' => [ 'FooBarAlias' => $dir . 'FooBar.alias.php' ] ] + ], + [ + [ + 'ExtensionMessagesFiles' => [ + 'FooBarAlias' => 'FooBar.alias.php', + 'FooBarMagic' => 'FooBar.magic.i18n.php', + ], + ], + [ + 'wgExtensionMessagesFiles' => [ + 'FooBarAlias' => $dir . 'FooBar.alias.php', + 'FooBarMagic' => $dir . 'FooBar.magic.i18n.php', + ], + ], + ], + ]; + } + + /** + * @dataProvider provideExtractExtensionMessagesFiles + */ + public function testExtractExtensionMessagesFiles( $input, $expected ) { + $processor = new ExtensionProcessor(); + $processor->extractInfo( $this->dir, $input + self::$default, 1 ); + $out = $processor->getExtractedInfo(); + foreach ( $expected as $key => $value ) { + $this->assertEquals( $value, $out['globals'][$key] ); + } + } + + public static function provideExtractMessagesDirs() { + $dir = __DIR__ . '/FooBar/'; + return [ + [ + [ 'MessagesDirs' => [ 'VisualEditor' => 'i18n' ] ], + [ 'wgMessagesDirs' => [ 'VisualEditor' => [ $dir . 'i18n' ] ] ] + ], + [ + [ 'MessagesDirs' => [ 'VisualEditor' => [ 'i18n', 'foobar' ] ] ], + [ 'wgMessagesDirs' => [ 'VisualEditor' => [ $dir . 'i18n', $dir . 'foobar' ] ] ] + ], + ]; + } + + /** + * @dataProvider provideExtractMessagesDirs + */ + public function testExtractMessagesDirs( $input, $expected ) { + $processor = new ExtensionProcessor(); + $processor->extractInfo( $this->dir, $input + self::$default, 1 ); + $out = $processor->getExtractedInfo(); + foreach ( $expected as $key => $value ) { + $this->assertEquals( $value, $out['globals'][$key] ); + } + } + + public function testExtractCredits() { + $processor = new ExtensionProcessor(); + $processor->extractInfo( $this->dir, self::$default, 1 ); + $this->setExpectedException( Exception::class ); + $processor->extractInfo( $this->dir, self::$default, 1 ); + } + + /** + * @dataProvider provideExtractResourceLoaderModules + */ + public function testExtractResourceLoaderModules( + $input, + array $expectedGlobals, + array $expectedAttribs = [] + ) { + $processor = new ExtensionProcessor(); + $processor->extractInfo( $this->dir, $input + self::$default, 1 ); + $out = $processor->getExtractedInfo(); + foreach ( $expectedGlobals as $key => $value ) { + $this->assertEquals( $value, $out['globals'][$key] ); + } + foreach ( $expectedAttribs as $key => $value ) { + $this->assertEquals( $value, $out['attributes'][$key] ); + } + } + + public static function provideExtractResourceLoaderModules() { + $dir = __DIR__ . '/FooBar'; + return [ + // Generic module with localBasePath/remoteExtPath specified + [ + // Input + [ + 'ResourceModules' => [ + 'test.foo' => [ + 'styles' => 'foobar.js', + 'localBasePath' => '', + 'remoteExtPath' => 'FooBar', + ], + ], + ], + // Expected + [ + 'wgResourceModules' => [ + 'test.foo' => [ + 'styles' => 'foobar.js', + 'localBasePath' => $dir, + 'remoteExtPath' => 'FooBar', + ], + ], + ], + ], + // ResourceFileModulePaths specified: + [ + // Input + [ + 'ResourceFileModulePaths' => [ + 'localBasePath' => 'modules', + 'remoteExtPath' => 'FooBar/modules', + ], + 'ResourceModules' => [ + // No paths + 'test.foo' => [ + 'styles' => 'foo.js', + ], + // Different paths set + 'test.bar' => [ + 'styles' => 'bar.js', + 'localBasePath' => 'subdir', + 'remoteExtPath' => 'FooBar/subdir', + ], + // Custom class with no paths set + 'test.class' => [ + 'class' => 'FooBarModule', + 'extra' => 'argument', + ], + // Custom class with a localBasePath + 'test.class.with.path' => [ + 'class' => 'FooBarPathModule', + 'extra' => 'argument', + 'localBasePath' => '', + ] + ], + ], + // Expected + [ + 'wgResourceModules' => [ + 'test.foo' => [ + 'styles' => 'foo.js', + 'localBasePath' => "$dir/modules", + 'remoteExtPath' => 'FooBar/modules', + ], + 'test.bar' => [ + 'styles' => 'bar.js', + 'localBasePath' => "$dir/subdir", + 'remoteExtPath' => 'FooBar/subdir', + ], + 'test.class' => [ + 'class' => 'FooBarModule', + 'extra' => 'argument', + 'localBasePath' => "$dir/modules", + 'remoteExtPath' => 'FooBar/modules', + ], + 'test.class.with.path' => [ + 'class' => 'FooBarPathModule', + 'extra' => 'argument', + 'localBasePath' => $dir, + 'remoteExtPath' => 'FooBar/modules', + ] + ], + ], + ], + // ResourceModuleSkinStyles with file module paths + [ + // Input + [ + 'ResourceFileModulePaths' => [ + 'localBasePath' => '', + 'remoteSkinPath' => 'FooBar', + ], + 'ResourceModuleSkinStyles' => [ + 'foobar' => [ + 'test.foo' => 'foo.css', + ] + ], + ], + // Expected + [ + 'wgResourceModuleSkinStyles' => [ + 'foobar' => [ + 'test.foo' => 'foo.css', + 'localBasePath' => $dir, + 'remoteSkinPath' => 'FooBar', + ], + ], + ], + ], + // ResourceModuleSkinStyles with file module paths and an override + [ + // Input + [ + 'ResourceFileModulePaths' => [ + 'localBasePath' => '', + 'remoteSkinPath' => 'FooBar', + ], + 'ResourceModuleSkinStyles' => [ + 'foobar' => [ + 'test.foo' => 'foo.css', + 'remoteSkinPath' => 'BarFoo' + ], + ], + ], + // Expected + [ + 'wgResourceModuleSkinStyles' => [ + 'foobar' => [ + 'test.foo' => 'foo.css', + 'localBasePath' => $dir, + 'remoteSkinPath' => 'BarFoo', + ], + ], + ], + ], + 'QUnit test module' => [ + // Input + [ + 'QUnitTestModule' => [ + 'localBasePath' => '', + 'remoteExtPath' => 'Foo', + 'scripts' => 'bar.js', + ], + ], + // Expected + [], + [ + 'QUnitTestModules' => [ + 'test.FooBar' => [ + 'localBasePath' => $dir, + 'remoteExtPath' => 'Foo', + 'scripts' => 'bar.js', + ], + ], + ], + ], + ]; + } + + public static function provideSetToGlobal() { + return [ + [ + [ 'wgAPIModules', 'wgAvailableRights' ], + [], + [ + 'APIModules' => [ 'foobar' => 'ApiFooBar' ], + 'AvailableRights' => [ 'foobar', 'unfoobar' ], + ], + [ + 'wgAPIModules' => [ 'foobar' => 'ApiFooBar' ], + 'wgAvailableRights' => [ 'foobar', 'unfoobar' ], + ], + ], + [ + [ 'wgAPIModules', 'wgAvailableRights' ], + [ + 'wgAPIModules' => [ 'barbaz' => 'ApiBarBaz' ], + 'wgAvailableRights' => [ 'barbaz' ] + ], + [ + 'APIModules' => [ 'foobar' => 'ApiFooBar' ], + 'AvailableRights' => [ 'foobar', 'unfoobar' ], + ], + [ + 'wgAPIModules' => [ 'barbaz' => 'ApiBarBaz', 'foobar' => 'ApiFooBar' ], + 'wgAvailableRights' => [ 'barbaz', 'foobar', 'unfoobar' ], + ], + ], + [ + [ 'wgGroupPermissions' ], + [ + 'wgGroupPermissions' => [ + 'sysop' => [ 'delete' ] + ], + ], + [ + 'GroupPermissions' => [ + 'sysop' => [ 'undelete' ], + 'user' => [ 'edit' ] + ], + ], + [ + 'wgGroupPermissions' => [ + 'sysop' => [ 'delete', 'undelete' ], + 'user' => [ 'edit' ] + ], + ] + ] + ]; + } + + /** + * Attributes under manifest_version 2 + */ + public function testExtractAttributes() { + $processor = new ExtensionProcessor(); + // Load FooBar extension + $processor->extractInfo( $this->dir, [ 'name' => 'FooBar' ], 2 ); + $processor->extractInfo( + $this->dir, + [ + 'name' => 'Baz', + 'attributes' => [ + // Loaded + 'FooBar' => [ + 'Plugins' => [ + 'ext.baz.foobar', + ], + ], + // Not loaded + 'FizzBuzz' => [ + 'MorePlugins' => [ + 'ext.baz.fizzbuzz', + ], + ], + ], + ], + 2 + ); + + $info = $processor->getExtractedInfo(); + $this->assertArrayHasKey( 'FooBarPlugins', $info['attributes'] ); + $this->assertSame( [ 'ext.baz.foobar' ], $info['attributes']['FooBarPlugins'] ); + $this->assertArrayNotHasKey( 'FizzBuzzMorePlugins', $info['attributes'] ); + } + + /** + * Attributes under manifest_version 1 + */ + public function testAttributes1() { + $processor = new ExtensionProcessor(); + $processor->extractInfo( + $this->dir, + [ + 'name' => 'FooBar', + 'FooBarPlugins' => [ + 'ext.baz.foobar', + ], + 'FizzBuzzMorePlugins' => [ + 'ext.baz.fizzbuzz', + ], + ], + 1 + ); + $processor->extractInfo( + $this->dir, + [ + 'name' => 'FooBar2', + 'FizzBuzzMorePlugins' => [ + 'ext.bar.fizzbuzz', + ] + ], + 1 + ); + + $info = $processor->getExtractedInfo(); + $this->assertArrayHasKey( 'FooBarPlugins', $info['attributes'] ); + $this->assertSame( [ 'ext.baz.foobar' ], $info['attributes']['FooBarPlugins'] ); + $this->assertArrayHasKey( 'FizzBuzzMorePlugins', $info['attributes'] ); + $this->assertSame( + [ 'ext.baz.fizzbuzz', 'ext.bar.fizzbuzz' ], + $info['attributes']['FizzBuzzMorePlugins'] + ); + } + + public function testAttributes1_notarray() { + $processor = new ExtensionProcessor(); + $this->setExpectedException( + InvalidArgumentException::class, + "The value for 'FooBarPlugins' should be an array (from {$this->dir})" + ); + $processor->extractInfo( + $this->dir, + [ + 'FooBarPlugins' => 'ext.baz.foobar', + ] + self::$default, + 1 + ); + } + + public function testExtractPathBasedGlobal() { + $processor = new ExtensionProcessor(); + $processor->extractInfo( + $this->dir, + [ + 'ParserTestFiles' => [ + 'tests/parserTests.txt', + 'tests/extraParserTests.txt', + ], + 'ServiceWiringFiles' => [ + 'includes/ServiceWiring.php' + ], + ] + self::$default, + 1 + ); + $globals = $processor->getExtractedInfo()['globals']; + $this->assertArrayHasKey( 'wgParserTestFiles', $globals ); + $this->assertSame( [ + "{$this->dirname}/tests/parserTests.txt", + "{$this->dirname}/tests/extraParserTests.txt" + ], $globals['wgParserTestFiles'] ); + $this->assertArrayHasKey( 'wgServiceWiringFiles', $globals ); + $this->assertSame( [ + "{$this->dirname}/includes/ServiceWiring.php" + ], $globals['wgServiceWiringFiles'] ); + } + + public function testGetRequirements() { + $info = self::$default + [ + 'requires' => [ + 'MediaWiki' => '>= 1.25.0', + 'platform' => [ + 'php' => '>= 5.5.9' + ], + 'extensions' => [ + 'Bar' => '*' + ] + ] + ]; + $processor = new ExtensionProcessor(); + $this->assertSame( + $info['requires'], + $processor->getRequirements( $info, false ) + ); + $this->assertSame( + [], + $processor->getRequirements( [], false ) + ); + } + + public function testGetDevRequirements() { + $info = self::$default + [ + 'dev-requires' => [ + 'MediaWiki' => '>= 1.31.0', + 'platform' => [ + 'ext-foo' => '*', + ], + 'skins' => [ + 'Baz' => '*', + ], + 'extensions' => [ + 'Biz' => '*', + ], + ], + ]; + $processor = new ExtensionProcessor(); + $this->assertSame( + $info['dev-requires'], + $processor->getRequirements( $info, true ) + ); + // Set some standard requirements, so we can test merging + $info['requires'] = [ + 'MediaWiki' => '>= 1.25.0', + 'platform' => [ + 'php' => '>= 5.5.9' + ], + 'extensions' => [ + 'Bar' => '*' + ] + ]; + $this->assertSame( + [ + 'MediaWiki' => '>= 1.25.0 >= 1.31.0', + 'platform' => [ + 'php' => '>= 5.5.9', + 'ext-foo' => '*', + ], + 'extensions' => [ + 'Bar' => '*', + 'Biz' => '*', + ], + 'skins' => [ + 'Baz' => '*', + ], + ], + $processor->getRequirements( $info, true ) + ); + + // If there's no dev-requires, it just returns requires + unset( $info['dev-requires'] ); + $this->assertSame( + $info['requires'], + $processor->getRequirements( $info, true ) + ); + } + + public function testGetExtraAutoloaderPaths() { + $processor = new ExtensionProcessor(); + $this->assertSame( + [ "{$this->dirname}/vendor/autoload.php" ], + $processor->getExtraAutoloaderPaths( $this->dirname, [ + 'load_composer_autoloader' => true, + ] ) + ); + } + + /** + * Verify that extension.schema.json is in sync with ExtensionProcessor + * + * @coversNothing + */ + public function testGlobalSettingsDocumentedInSchema() { + global $IP; + $globalSettings = TestingAccessWrapper::newFromClass( + ExtensionProcessor::class )->globalSettings; + + $version = ExtensionRegistry::MANIFEST_VERSION; + $schema = FormatJson::decode( + file_get_contents( "$IP/docs/extension.schema.v$version.json" ), + true + ); + $missing = []; + foreach ( $globalSettings as $global ) { + if ( !isset( $schema['properties'][$global] ) ) { + $missing[] = $global; + } + } + + $this->assertEquals( [], $missing, + "The following global settings are not documented in docs/extension.schema.json" ); + } +} + +/** + * Allow overriding the default value of $this->globals + * so we can test merging + */ +class MockExtensionProcessor extends ExtensionProcessor { + public function __construct( $globals = [] ) { + $this->globals = $globals + $this->globals; + } +} diff --git a/tests/phpunit/unit/includes/search/SearchIndexFieldTest.php b/tests/phpunit/unit/includes/search/SearchIndexFieldTest.php new file mode 100644 index 0000000000..a640c967cc --- /dev/null +++ b/tests/phpunit/unit/includes/search/SearchIndexFieldTest.php @@ -0,0 +1,56 @@ +getMockBuilder( SearchIndexFieldDefinition::class ) + ->setMethods( [ 'getMapping' ] ) + ->setConstructorArgs( [ $n1, $t1 ] ) + ->getMock(); + $field2 = + $this->getMockBuilder( SearchIndexFieldDefinition::class ) + ->setMethods( [ 'getMapping' ] ) + ->setConstructorArgs( [ $n2, $t2 ] ) + ->getMock(); + + if ( $result ) { + $this->assertNotFalse( $field1->merge( $field2 ) ); + } else { + $this->assertFalse( $field1->merge( $field2 ) ); + } + + $field1->setFlag( 0xFF ); + $this->assertFalse( $field1->merge( $field2 ) ); + + $field1->setMergeCallback( + function ( $a, $b ) { + return "test"; + } + ); + $this->assertEquals( "test", $field1->merge( $field2 ) ); + } + +} diff --git a/tests/phpunit/unit/includes/session/MetadataMergeExceptionTest.php b/tests/phpunit/unit/includes/session/MetadataMergeExceptionTest.php new file mode 100644 index 0000000000..707adfe5b0 --- /dev/null +++ b/tests/phpunit/unit/includes/session/MetadataMergeExceptionTest.php @@ -0,0 +1,28 @@ + 'bar' ]; + + $ex = new MetadataMergeException(); + $this->assertInstanceOf( \UnexpectedValueException::class, $ex ); + $this->assertSame( [], $ex->getContext() ); + + $ex2 = new MetadataMergeException( 'Message', 42, $ex, $data ); + $this->assertSame( 'Message', $ex2->getMessage() ); + $this->assertSame( 42, $ex2->getCode() ); + $this->assertSame( $ex, $ex2->getPrevious() ); + $this->assertSame( $data, $ex2->getContext() ); + + $ex->setContext( $data ); + $this->assertSame( $data, $ex->getContext() ); + } + +} diff --git a/tests/phpunit/unit/includes/session/SessionIdTest.php b/tests/phpunit/unit/includes/session/SessionIdTest.php new file mode 100644 index 0000000000..3c7f8cbfdf --- /dev/null +++ b/tests/phpunit/unit/includes/session/SessionIdTest.php @@ -0,0 +1,20 @@ +assertSame( 'foo', $id->getId() ); + $this->assertSame( 'foo', (string)$id ); + $id->setId( 'bar' ); + $this->assertSame( 'bar', $id->getId() ); + $this->assertSame( 'bar', (string)$id ); + } + +} diff --git a/tests/phpunit/unit/includes/skins/SkinFactoryTest.php b/tests/phpunit/unit/includes/skins/SkinFactoryTest.php new file mode 100644 index 0000000000..8443c8d1f6 --- /dev/null +++ b/tests/phpunit/unit/includes/skins/SkinFactoryTest.php @@ -0,0 +1,82 @@ +register( 'fallback', 'Fallback', function () { + return new SkinFallback(); + } ); + $this->assertTrue( true ); // No exception thrown + $this->setExpectedException( InvalidArgumentException::class ); + $factory->register( 'invalid', 'Invalid', 'Invalid callback' ); + } + + /** + * @covers SkinFactory::makeSkin + */ + public function testMakeSkinWithNoBuilders() { + $factory = new SkinFactory(); + $this->setExpectedException( SkinException::class ); + $factory->makeSkin( 'nobuilderregistered' ); + } + + /** + * @covers SkinFactory::makeSkin + */ + public function testMakeSkinWithInvalidCallback() { + $factory = new SkinFactory(); + $factory->register( 'unittest', 'Unittest', function () { + return true; // Not a Skin object + } ); + $this->setExpectedException( UnexpectedValueException::class ); + $factory->makeSkin( 'unittest' ); + } + + /** + * @covers SkinFactory::makeSkin + */ + public function testMakeSkinWithValidCallback() { + $factory = new SkinFactory(); + $factory->register( 'testfallback', 'TestFallback', function () { + return new SkinFallback(); + } ); + + $skin = $factory->makeSkin( 'testfallback' ); + $this->assertInstanceOf( Skin::class, $skin ); + $this->assertInstanceOf( SkinFallback::class, $skin ); + $this->assertEquals( 'fallback', $skin->getSkinName() ); + } + + /** + * @covers Skin::__construct + * @covers Skin::getSkinName + */ + public function testGetSkinName() { + $skin = new SkinFallback(); + $this->assertEquals( 'fallback', $skin->getSkinName(), 'Default' ); + $skin = new SkinFallback( 'testname' ); + $this->assertEquals( 'testname', $skin->getSkinName(), 'Constructor argument' ); + } + + /** + * @covers SkinFactory::getSkinNames + */ + public function testGetSkinNames() { + $factory = new SkinFactory(); + // A fake callback we can use that will never be called + $callback = function () { + // NOP + }; + $factory->register( 'skin1', 'Skin1', $callback ); + $factory->register( 'skin2', 'Skin2', $callback ); + $names = $factory->getSkinNames(); + $this->assertArrayHasKey( 'skin1', $names ); + $this->assertArrayHasKey( 'skin2', $names ); + $this->assertEquals( 'Skin1', $names['skin1'] ); + $this->assertEquals( 'Skin2', $names['skin2'] ); + } +} diff --git a/tests/phpunit/unit/includes/title/ForeignTitleTest.php b/tests/phpunit/unit/includes/title/ForeignTitleTest.php new file mode 100644 index 0000000000..ec093cf83b --- /dev/null +++ b/tests/phpunit/unit/includes/title/ForeignTitleTest.php @@ -0,0 +1,103 @@ +assertEquals( true, $title->isNamespaceIdKnown() ); + $this->assertEquals( $expectedId, $title->getNamespaceId() ); + $this->assertEquals( $expectedName, $title->getNamespaceName() ); + $this->assertEquals( $expectedText, $title->getText() ); + } + + public function testUnknownNamespaceCheck() { + $title = new ForeignTitle( null, 'this', 'that' ); + + $this->assertEquals( false, $title->isNamespaceIdKnown() ); + $this->assertEquals( 'this', $title->getNamespaceName() ); + $this->assertEquals( 'that', $title->getText() ); + } + + public function testUnknownNamespaceError() { + $this->setExpectedException( MWException::class ); + $title = new ForeignTitle( null, 'this', 'that' ); + $title->getNamespaceId(); + } + + public function fullTextProvider() { + return [ + [ + new ForeignTitle( 20, 'Contributor', 'JohnDoe' ), + 'Contributor:JohnDoe' + ], + [ + new ForeignTitle( '1', 'Discussion', 'Capital' ), + 'Discussion:Capital' + ], + [ + new ForeignTitle( 0, '', 'MainNamespace' ), + 'MainNamespace' + ], + [ + new ForeignTitle( 4, 'Some ns', 'Article title with spaces' ), + 'Some_ns:Article_title_with_spaces' + ], + ]; + } + + /** + * @dataProvider fullTextProvider + */ + public function testFullText( ForeignTitle $title, $fullText ) { + $this->assertEquals( $fullText, $title->getFullText() ); + } +} diff --git a/tests/phpunit/unit/includes/title/NamespaceAwareForeignTitleFactoryTest.php b/tests/phpunit/unit/includes/title/NamespaceAwareForeignTitleFactoryTest.php new file mode 100644 index 0000000000..d77797314b --- /dev/null +++ b/tests/phpunit/unit/includes/title/NamespaceAwareForeignTitleFactoryTest.php @@ -0,0 +1,101 @@ + '', 1 => 'Talk', 100 => 'Portal', 9000 => 'Bogus' + ]; + + $factory = new NamespaceAwareForeignTitleFactory( $foreignNamespaces ); + $testTitle = $factory->createForeignTitle( $title, $ns ); + + $this->assertEquals( $testTitle->isNamespaceIdKnown(), + $foreignTitle->isNamespaceIdKnown() ); + + if ( + $testTitle->isNamespaceIdKnown() && + $foreignTitle->isNamespaceIdKnown() + ) { + $this->assertEquals( $testTitle->getNamespaceId(), + $foreignTitle->getNamespaceId() ); + } + + $this->assertEquals( $testTitle->getNamespaceName(), + $foreignTitle->getNamespaceName() ); + $this->assertEquals( $testTitle->getText(), $foreignTitle->getText() ); + } +} diff --git a/tests/phpunit/unit/includes/title/TitleValueTest.php b/tests/phpunit/unit/includes/title/TitleValueTest.php new file mode 100644 index 0000000000..cd67a9329d --- /dev/null +++ b/tests/phpunit/unit/includes/title/TitleValueTest.php @@ -0,0 +1,149 @@ +assertEquals( $ns, $title->getNamespace() ); + $this->assertTrue( $title->inNamespace( $ns ) ); + $this->assertEquals( $text, $title->getText() ); + $this->assertEquals( $fragment, $title->getFragment() ); + $this->assertEquals( $hasFragment, $title->hasFragment() ); + $this->assertEquals( $interwiki, $title->getInterwiki() ); + $this->assertEquals( $hasInterwiki, $title->isExternal() ); + } + + public function badConstructorProvider() { + return [ + [ 'foo', 'title', 'fragment', '' ], + [ null, 'title', 'fragment', '' ], + [ 2.3, 'title', 'fragment', '' ], + + [ NS_MAIN, 5, 'fragment', '' ], + [ NS_MAIN, null, 'fragment', '' ], + [ NS_USER, '', 'fragment', '' ], + [ NS_MAIN, 'foo bar', '', '' ], + [ NS_MAIN, 'bar_', '', '' ], + [ NS_MAIN, '_foo', '', '' ], + [ NS_MAIN, ' eek ', '', '' ], + + [ NS_MAIN, 'title', 5, '' ], + [ NS_MAIN, 'title', null, '' ], + [ NS_MAIN, 'title', [], '' ], + + [ NS_MAIN, 'title', '', 5 ], + [ NS_MAIN, 'title', null, 5 ], + [ NS_MAIN, 'title', [], 5 ], + ]; + } + + /** + * @dataProvider badConstructorProvider + */ + public function testConstructionErrors( $ns, $text, $fragment, $interwiki ) { + $this->setExpectedException( InvalidArgumentException::class ); + new TitleValue( $ns, $text, $fragment, $interwiki ); + } + + public function fragmentTitleProvider() { + return [ + [ new TitleValue( NS_MAIN, 'Test' ), 'foo' ], + [ new TitleValue( NS_TALK, 'Test', 'foo' ), '' ], + [ new TitleValue( NS_CATEGORY, 'Test', 'foo' ), 'bar' ], + ]; + } + + /** + * @dataProvider fragmentTitleProvider + */ + public function testCreateFragmentTitle( TitleValue $title, $fragment ) { + $fragmentTitle = $title->createFragmentTarget( $fragment ); + + $this->assertEquals( $title->getNamespace(), $fragmentTitle->getNamespace() ); + $this->assertEquals( $title->getText(), $fragmentTitle->getText() ); + $this->assertEquals( $fragment, $fragmentTitle->getFragment() ); + } + + public function getTextProvider() { + return [ + [ 'Foo', 'Foo' ], + [ 'Foo_Bar', 'Foo Bar' ], + ]; + } + + /** + * @dataProvider getTextProvider + */ + public function testGetText( $dbkey, $text ) { + $title = new TitleValue( NS_MAIN, $dbkey ); + + $this->assertEquals( $text, $title->getText() ); + } + + public function provideTestToString() { + yield [ + new TitleValue( 0, 'Foo' ), + '0:Foo' + ]; + yield [ + new TitleValue( 1, 'Bar_Baz' ), + '1:Bar_Baz' + ]; + yield [ + new TitleValue( 9, 'JoJo', 'Frag' ), + '9:JoJo#Frag' + ]; + yield [ + new TitleValue( 200, 'tea', 'Fragment', 'wikicode' ), + 'wikicode:200:tea#Fragment' + ]; + } + + /** + * @dataProvider provideTestToString + */ + public function testToString( TitleValue $value, $expected ) { + $this->assertSame( + $expected, + $value->__toString() + ); + } +} diff --git a/tests/phpunit/unit/includes/user/UserArrayFromResultTest.php b/tests/phpunit/unit/includes/user/UserArrayFromResultTest.php new file mode 100644 index 0000000000..0b2ce17d68 --- /dev/null +++ b/tests/phpunit/unit/includes/user/UserArrayFromResultTest.php @@ -0,0 +1,110 @@ +getMockBuilder( Wikimedia\Rdbms\ResultWrapper::class ) + ->disableOriginalConstructor(); + + $resultWrapper = $resultWrapper->getMock(); + $resultWrapper->expects( $this->atLeastOnce() ) + ->method( 'current' ) + ->will( $this->returnValue( $row ) ); + $resultWrapper->expects( $this->any() ) + ->method( 'numRows' ) + ->will( $this->returnValue( $numRows ) ); + + return $resultWrapper; + } + + private function getRowWithUsername( $username = 'fooUser' ) { + $row = new stdClass(); + $row->user_name = $username; + return $row; + } + + /** + * @covers UserArrayFromResult::__construct + */ + public function testConstructionWithFalseRow() { + $row = false; + $resultWrapper = $this->getMockResultWrapper( $row ); + + $object = new UserArrayFromResult( $resultWrapper ); + + $this->assertEquals( $resultWrapper, $object->res ); + $this->assertSame( 0, $object->key ); + $this->assertEquals( $row, $object->current ); + } + + /** + * @covers UserArrayFromResult::__construct + */ + public function testConstructionWithRow() { + $username = 'addshore'; + $row = $this->getRowWithUsername( $username ); + $resultWrapper = $this->getMockResultWrapper( $row ); + + $object = new UserArrayFromResult( $resultWrapper ); + + $this->assertEquals( $resultWrapper, $object->res ); + $this->assertSame( 0, $object->key ); + $this->assertInstanceOf( User::class, $object->current ); + $this->assertEquals( $username, $object->current->mName ); + } + + public static function provideNumberOfRows() { + return [ + [ 0 ], + [ 1 ], + [ 122 ], + ]; + } + + /** + * @dataProvider provideNumberOfRows + * @covers UserArrayFromResult::count + */ + public function testCountWithVaryingValues( $numRows ) { + $object = new UserArrayFromResult( $this->getMockResultWrapper( + $this->getRowWithUsername(), + $numRows + ) ); + $this->assertEquals( $numRows, $object->count() ); + } + + /** + * @covers UserArrayFromResult::current + */ + public function testCurrentAfterConstruction() { + $username = 'addshore'; + $userRow = $this->getRowWithUsername( $username ); + $object = new UserArrayFromResult( $this->getMockResultWrapper( $userRow ) ); + $this->assertInstanceOf( User::class, $object->current() ); + $this->assertEquals( $username, $object->current()->mName ); + } + + public function provideTestValid() { + return [ + [ $this->getRowWithUsername(), true ], + [ false, false ], + ]; + } + + /** + * @dataProvider provideTestValid + * @covers UserArrayFromResult::valid + */ + public function testValid( $input, $expected ) { + $object = new UserArrayFromResult( $this->getMockResultWrapper( $input ) ); + $this->assertEquals( $expected, $object->valid() ); + } + + // @todo unit test for key() + // @todo unit test for next() + // @todo unit test for rewind() +} diff --git a/tests/phpunit/unit/includes/watcheditem/NoWriteWatchedItemStoreUnitTest.php b/tests/phpunit/unit/includes/watcheditem/NoWriteWatchedItemStoreUnitTest.php new file mode 100644 index 0000000000..556f518e54 --- /dev/null +++ b/tests/phpunit/unit/includes/watcheditem/NoWriteWatchedItemStoreUnitTest.php @@ -0,0 +1,250 @@ +getMockForAbstractClass( WatchedItemStoreInterface::class ); + $innerService->expects( $this->never() )->method( 'addWatch' ); + $noWriteService = new NoWriteWatchedItemStore( $innerService ); + + $this->setExpectedException( DBReadOnlyError::class ); + $noWriteService->addWatch( + new UserIdentityValue( 1, 'MockUser', 0 ), new TitleValue( 0, 'Foo' ) ); + } + + public function testAddWatchBatchForUser() { + /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ + $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); + $innerService->expects( $this->never() )->method( 'addWatchBatchForUser' ); + $noWriteService = new NoWriteWatchedItemStore( $innerService ); + + $this->setExpectedException( DBReadOnlyError::class ); + $noWriteService->addWatchBatchForUser( new UserIdentityValue( 1, 'MockUser', 0 ), [] ); + } + + public function testRemoveWatch() { + /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ + $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); + $innerService->expects( $this->never() )->method( 'removeWatch' ); + $noWriteService = new NoWriteWatchedItemStore( $innerService ); + + $this->setExpectedException( DBReadOnlyError::class ); + $noWriteService->removeWatch( + new UserIdentityValue( 1, 'MockUser', 0 ), new TitleValue( 0, 'Foo' ) ); + } + + public function testSetNotificationTimestampsForUser() { + /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ + $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); + $innerService->expects( $this->never() )->method( 'setNotificationTimestampsForUser' ); + $noWriteService = new NoWriteWatchedItemStore( $innerService ); + + $this->setExpectedException( DBReadOnlyError::class ); + $noWriteService->setNotificationTimestampsForUser( + new UserIdentityValue( 1, 'MockUser', 0 ), + 'timestamp', + [] + ); + } + + public function testUpdateNotificationTimestamp() { + /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ + $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); + $innerService->expects( $this->never() )->method( 'updateNotificationTimestamp' ); + $noWriteService = new NoWriteWatchedItemStore( $innerService ); + + $this->setExpectedException( DBReadOnlyError::class ); + $noWriteService->updateNotificationTimestamp( + new UserIdentityValue( 1, 'MockUser', 0 ), + new TitleValue( 0, 'Foo' ), + 'timestamp' + ); + } + + public function testResetNotificationTimestamp() { + /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ + $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); + $innerService->expects( $this->never() )->method( 'resetNotificationTimestamp' ); + $noWriteService = new NoWriteWatchedItemStore( $innerService ); + + $this->setExpectedException( DBReadOnlyError::class ); + $noWriteService->resetNotificationTimestamp( + new UserIdentityValue( 1, 'MockUser', 0 ), + new TitleValue( 0, 'Foo' ) + ); + } + + public function testCountWatchedItems() { + /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ + $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); + $innerService->expects( $this->once() )->method( 'countWatchedItems' )->willReturn( __METHOD__ ); + $noWriteService = new NoWriteWatchedItemStore( $innerService ); + + $return = $noWriteService->countWatchedItems( + new UserIdentityValue( 1, 'MockUser', 0 ) + ); + $this->assertEquals( __METHOD__, $return ); + } + + public function testCountWatchers() { + /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ + $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); + $innerService->expects( $this->once() )->method( 'countWatchers' )->willReturn( __METHOD__ ); + $noWriteService = new NoWriteWatchedItemStore( $innerService ); + + $return = $noWriteService->countWatchers( + new TitleValue( 0, 'Foo' ) + ); + $this->assertEquals( __METHOD__, $return ); + } + + public function testCountVisitingWatchers() { + /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ + $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); + $innerService->expects( $this->once() ) + ->method( 'countVisitingWatchers' ) + ->willReturn( __METHOD__ ); + $noWriteService = new NoWriteWatchedItemStore( $innerService ); + + $return = $noWriteService->countVisitingWatchers( + new TitleValue( 0, 'Foo' ), + 9 + ); + $this->assertEquals( __METHOD__, $return ); + } + + public function testCountWatchersMultiple() { + /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ + $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); + $innerService->expects( $this->once() ) + ->method( 'countVisitingWatchersMultiple' ) + ->willReturn( __METHOD__ ); + $noWriteService = new NoWriteWatchedItemStore( $innerService ); + + $return = $noWriteService->countWatchersMultiple( + [ new TitleValue( 0, 'Foo' ) ], + [] + ); + $this->assertEquals( __METHOD__, $return ); + } + + public function testCountVisitingWatchersMultiple() { + /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ + $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); + $innerService->expects( $this->once() ) + ->method( 'countVisitingWatchersMultiple' ) + ->willReturn( __METHOD__ ); + $noWriteService = new NoWriteWatchedItemStore( $innerService ); + + $return = $noWriteService->countVisitingWatchersMultiple( + [ [ new TitleValue( 0, 'Foo' ), 99 ] ], + 11 + ); + $this->assertEquals( __METHOD__, $return ); + } + + public function testGetWatchedItem() { + /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ + $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); + $innerService->expects( $this->once() )->method( 'getWatchedItem' )->willReturn( __METHOD__ ); + $noWriteService = new NoWriteWatchedItemStore( $innerService ); + + $return = $noWriteService->getWatchedItem( + new UserIdentityValue( 1, 'MockUser', 0 ), + new TitleValue( 0, 'Foo' ) + ); + $this->assertEquals( __METHOD__, $return ); + } + + public function testLoadWatchedItem() { + /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ + $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); + $innerService->expects( $this->once() )->method( 'loadWatchedItem' )->willReturn( __METHOD__ ); + $noWriteService = new NoWriteWatchedItemStore( $innerService ); + + $return = $noWriteService->loadWatchedItem( + new UserIdentityValue( 1, 'MockUser', 0 ), + new TitleValue( 0, 'Foo' ) + ); + $this->assertEquals( __METHOD__, $return ); + } + + public function testGetWatchedItemsForUser() { + /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ + $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); + $innerService->expects( $this->once() ) + ->method( 'getWatchedItemsForUser' ) + ->willReturn( __METHOD__ ); + $noWriteService = new NoWriteWatchedItemStore( $innerService ); + + $return = $noWriteService->getWatchedItemsForUser( + new UserIdentityValue( 1, 'MockUser', 0 ), + [] + ); + $this->assertEquals( __METHOD__, $return ); + } + + public function testIsWatched() { + /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ + $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); + $innerService->expects( $this->once() )->method( 'isWatched' )->willReturn( __METHOD__ ); + $noWriteService = new NoWriteWatchedItemStore( $innerService ); + + $return = $noWriteService->isWatched( + new UserIdentityValue( 1, 'MockUser', 0 ), + new TitleValue( 0, 'Foo' ) + ); + $this->assertEquals( __METHOD__, $return ); + } + + public function testGetNotificationTimestampsBatch() { + /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ + $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); + $innerService->expects( $this->once() ) + ->method( 'getNotificationTimestampsBatch' ) + ->willReturn( __METHOD__ ); + $noWriteService = new NoWriteWatchedItemStore( $innerService ); + + $return = $noWriteService->getNotificationTimestampsBatch( + new UserIdentityValue( 1, 'MockUser', 0 ), + [ new TitleValue( 0, 'Foo' ) ] + ); + $this->assertEquals( __METHOD__, $return ); + } + + public function testCountUnreadNotifications() { + /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ + $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); + $innerService->expects( $this->once() ) + ->method( 'countUnreadNotifications' ) + ->willReturn( __METHOD__ ); + $noWriteService = new NoWriteWatchedItemStore( $innerService ); + + $return = $noWriteService->countUnreadNotifications( + new UserIdentityValue( 1, 'MockUser', 0 ), + 88 + ); + $this->assertEquals( __METHOD__, $return ); + } + + public function testDuplicateAllAssociatedEntries() { + /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */ + $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class ); + $noWriteService = new NoWriteWatchedItemStore( $innerService ); + + $this->setExpectedException( DBReadOnlyError::class ); + $noWriteService->duplicateAllAssociatedEntries( + new TitleValue( 0, 'Foo' ), + new TitleValue( 0, 'Bar' ) + ); + } + +}