Move trivial unit tests, round II
authorAmir Sarabadani <Ladsgroup@gmail.com>
Tue, 2 Jul 2019 17:04:44 +0000 (19:04 +0200)
committerAmir Sarabadani <Ladsgroup@gmail.com>
Tue, 2 Jul 2019 17:52:29 +0000 (19:52 +0200)
Change-Id: I18e5a1514d7372b34f7fb460adf506a1ac65001f

20 files changed:
tests/phpunit/includes/MediaWikiVersionFetcherTest.php [deleted file]
tests/phpunit/includes/Rest/EntryPointTest.php [deleted file]
tests/phpunit/includes/Rest/Handler/HelloHandlerTest.php [deleted file]
tests/phpunit/includes/Rest/testRoutes.json [deleted file]
tests/phpunit/includes/Revision/RevisionStoreFactoryTest.php [deleted file]
tests/phpunit/includes/config/ConfigFactoryTest.php [deleted file]
tests/phpunit/includes/media/GIFMetadataExtractorTest.php [deleted file]
tests/phpunit/includes/media/SVGMetadataExtractorTest.php [deleted file]
tests/phpunit/includes/site/CachingSiteStoreTest.php [deleted file]
tests/phpunit/includes/site/HashSiteStoreTest.php [deleted file]
tests/phpunit/unit/includes/MediaWikiVersionFetcherTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/Rest/EntryPointTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/Rest/Handler/HelloHandlerTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/Rest/testRoutes.json [new file with mode: 0644]
tests/phpunit/unit/includes/Revision/RevisionStoreFactoryTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/config/ConfigFactoryTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/media/GIFMetadataExtractorTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/media/SVGMetadataExtractorTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/site/CachingSiteStoreTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/site/HashSiteStoreTest.php [new file with mode: 0644]

diff --git a/tests/phpunit/includes/MediaWikiVersionFetcherTest.php b/tests/phpunit/includes/MediaWikiVersionFetcherTest.php
deleted file mode 100644 (file)
index 9803081..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<?php
-
-/**
- * Note: this is not a unit test, as it touches the file system and reads an actual file.
- * If unit tests are added for MediaWikiVersionFetcher, this should be done in a distinct test case.
- *
- * @covers MediaWikiVersionFetcher
- *
- * @group ComposerHooks
- *
- * @author Jeroen De Dauw < jeroendedauw@gmail.com >
- */
-class MediaWikiVersionFetcherTest extends MediaWikiTestCase {
-
-       public function testReturnsResult() {
-               global $wgVersion;
-               $versionFetcher = new MediaWikiVersionFetcher();
-               $this->assertSame( $wgVersion, $versionFetcher->fetchVersion() );
-       }
-
-}
diff --git a/tests/phpunit/includes/Rest/EntryPointTest.php b/tests/phpunit/includes/Rest/EntryPointTest.php
deleted file mode 100644 (file)
index 4f87a70..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-<?php
-
-namespace MediaWiki\Tests\Rest;
-
-use EmptyBagOStuff;
-use GuzzleHttp\Psr7\Uri;
-use GuzzleHttp\Psr7\Stream;
-use MediaWiki\Rest\Handler;
-use MediaWikiTestCase;
-use MediaWiki\Rest\EntryPoint;
-use MediaWiki\Rest\RequestData;
-use MediaWiki\Rest\ResponseFactory;
-use MediaWiki\Rest\Router;
-use WebResponse;
-
-/**
- * @covers \MediaWiki\Rest\EntryPoint
- * @covers \MediaWiki\Rest\Router
- */
-class EntryPointTest extends MediaWikiTestCase {
-       private static $mockHandler;
-
-       private function createRouter() {
-               return new Router(
-                       [ __DIR__ . '/testRoutes.json' ],
-                       [],
-                       '/rest',
-                       new EmptyBagOStuff(),
-                       new ResponseFactory() );
-       }
-
-       private function createWebResponse() {
-               return $this->getMockBuilder( WebResponse::class )
-                       ->setMethods( [ 'header' ] )
-                       ->getMock();
-       }
-
-       public static function mockHandlerHeader() {
-               return new class extends Handler {
-                       public function execute() {
-                               $response = $this->getResponseFactory()->create();
-                               $response->setHeader( 'Foo', 'Bar' );
-                               return $response;
-                       }
-               };
-       }
-
-       public function testHeader() {
-               $webResponse = $this->createWebResponse();
-               $webResponse->expects( $this->any() )
-                       ->method( 'header' )
-                       ->withConsecutive(
-                               [ 'HTTP/1.1 200 OK', true, null ],
-                               [ 'Foo: Bar', true, null ]
-                       );
-
-               $entryPoint = new EntryPoint(
-                       new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/header' ) ] ),
-                       $webResponse,
-                       $this->createRouter() );
-               $entryPoint->execute();
-               $this->assertTrue( true );
-       }
-
-       public static function mockHandlerBodyRewind() {
-               return new class extends Handler {
-                       public function execute() {
-                               $response = $this->getResponseFactory()->create();
-                               $stream = new Stream( fopen( 'php://memory', 'w+' ) );
-                               $stream->write( 'hello' );
-                               $response->setBody( $stream );
-                               return $response;
-                       }
-               };
-       }
-
-       /**
-        * Make sure EntryPoint rewinds a seekable body stream before reading.
-        */
-       public function testBodyRewind() {
-               $entryPoint = new EntryPoint(
-                       new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/bodyRewind' ) ] ),
-                       $this->createWebResponse(),
-                       $this->createRouter() );
-               ob_start();
-               $entryPoint->execute();
-               $this->assertSame( 'hello', ob_get_clean() );
-       }
-
-}
diff --git a/tests/phpunit/includes/Rest/Handler/HelloHandlerTest.php b/tests/phpunit/includes/Rest/Handler/HelloHandlerTest.php
deleted file mode 100644 (file)
index afbaafb..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-<?php
-
-namespace MediaWiki\Tests\Rest\Handler;
-
-use EmptyBagOStuff;
-use GuzzleHttp\Psr7\Uri;
-use MediaWiki\Rest\RequestData;
-use MediaWiki\Rest\ResponseFactory;
-use MediaWiki\Rest\Router;
-use MediaWikiTestCase;
-
-/**
- * @covers \MediaWiki\Rest\Handler\HelloHandler
- */
-class HelloHandlerTest extends MediaWikiTestCase {
-       public static function provideTestViaRouter() {
-               return [
-                       'normal' => [
-                               [
-                                       'method' => 'GET',
-                                       'uri' => self::makeUri( '/user/Tim/hello' ),
-                               ],
-                               [
-                                       'statusCode' => 200,
-                                       'reasonPhrase' => 'OK',
-                                       'protocolVersion' => '1.1',
-                                       'body' => '{"message":"Hello, Tim!"}',
-                               ],
-                       ],
-                       'method not allowed' => [
-                               [
-                                       'method' => 'POST',
-                                       'uri' => self::makeUri( '/user/Tim/hello' ),
-                               ],
-                               [
-                                       'statusCode' => 405,
-                                       'reasonPhrase' => 'Method Not Allowed',
-                                       'protocolVersion' => '1.1',
-                                       'body' => '{"httpCode":405,"httpReason":"Method Not Allowed"}',
-                               ],
-                       ],
-               ];
-       }
-
-       private static function makeUri( $path ) {
-               return new Uri( "http://www.example.com/rest$path" );
-       }
-
-       /** @dataProvider provideTestViaRouter */
-       public function testViaRouter( $requestInfo, $responseInfo ) {
-               $router = new Router(
-                       [ __DIR__ . '/../testRoutes.json' ],
-                       [],
-                       '/rest',
-                       new EmptyBagOStuff(),
-                       new ResponseFactory() );
-               $request = new RequestData( $requestInfo );
-               $response = $router->execute( $request );
-               if ( isset( $responseInfo['statusCode'] ) ) {
-                       $this->assertSame( $responseInfo['statusCode'], $response->getStatusCode() );
-               }
-               if ( isset( $responseInfo['reasonPhrase'] ) ) {
-                       $this->assertSame( $responseInfo['reasonPhrase'], $response->getReasonPhrase() );
-               }
-               if ( isset( $responseInfo['protocolVersion'] ) ) {
-                       $this->assertSame( $responseInfo['protocolVersion'], $response->getProtocolVersion() );
-               }
-               if ( isset( $responseInfo['body'] ) ) {
-                       $this->assertSame( $responseInfo['body'], $response->getBody()->getContents() );
-               }
-               $this->assertSame(
-                       [],
-                       array_diff( array_keys( $responseInfo ), [
-                               'statusCode',
-                               'reasonPhrase',
-                               'protocolVersion',
-                               'body'
-                       ] ),
-                       '$responseInfo may not contain unknown keys' );
-       }
-}
diff --git a/tests/phpunit/includes/Rest/testRoutes.json b/tests/phpunit/includes/Rest/testRoutes.json
deleted file mode 100644 (file)
index 7e43bb0..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-[
-       {
-               "path": "/user/{name}/hello",
-               "class": "MediaWiki\\Rest\\Handler\\HelloHandler"
-       },
-       {
-               "path": "/mock/EntryPoint/header",
-               "factory": "MediaWiki\\Tests\\Rest\\EntryPointTest::mockHandlerHeader"
-       },
-       {
-               "path": "/mock/EntryPoint/bodyRewind",
-               "factory": "MediaWiki\\Tests\\Rest\\EntryPointTest::mockHandlerBodyRewind"
-       }
-]
diff --git a/tests/phpunit/includes/Revision/RevisionStoreFactoryTest.php b/tests/phpunit/includes/Revision/RevisionStoreFactoryTest.php
deleted file mode 100644 (file)
index 84c815d..0000000
+++ /dev/null
@@ -1,194 +0,0 @@
-<?php
-
-namespace MediaWiki\Tests\Revision;
-
-use ActorMigration;
-use CommentStore;
-use MediaWiki\Logger\Spi as LoggerSpi;
-use MediaWiki\Revision\RevisionStore;
-use MediaWiki\Revision\RevisionStoreFactory;
-use MediaWiki\Revision\SlotRoleRegistry;
-use MediaWiki\Storage\BlobStore;
-use MediaWiki\Storage\BlobStoreFactory;
-use MediaWiki\Storage\NameTableStore;
-use MediaWiki\Storage\NameTableStoreFactory;
-use MediaWiki\Storage\SqlBlobStore;
-use MediaWikiTestCase;
-use Psr\Log\LoggerInterface;
-use Psr\Log\NullLogger;
-use WANObjectCache;
-use Wikimedia\Rdbms\ILBFactory;
-use Wikimedia\Rdbms\ILoadBalancer;
-use Wikimedia\TestingAccessWrapper;
-
-class RevisionStoreFactoryTest extends MediaWikiTestCase {
-
-       /**
-        * @covers \MediaWiki\Revision\RevisionStoreFactory::__construct
-        */
-       public function testValidConstruction_doesntCauseErrors() {
-               new RevisionStoreFactory(
-                       $this->getMockLoadBalancerFactory(),
-                       $this->getMockBlobStoreFactory(),
-                       $this->getNameTableStoreFactory(),
-                       $this->getMockSlotRoleRegistry(),
-                       $this->getHashWANObjectCache(),
-                       $this->getMockCommentStore(),
-                       ActorMigration::newMigration(),
-                       MIGRATION_OLD,
-                       $this->getMockLoggerSpi(),
-                       true
-               );
-               $this->assertTrue( true );
-       }
-
-       public function provideWikiIds() {
-               yield [ true ];
-               yield [ false ];
-               yield [ 'somewiki' ];
-               yield [ 'somewiki', MIGRATION_OLD , false ];
-               yield [ 'somewiki', MIGRATION_NEW , true ];
-       }
-
-       /**
-        * @dataProvider provideWikiIds
-        * @covers \MediaWiki\Revision\RevisionStoreFactory::getRevisionStore
-        */
-       public function testGetRevisionStore(
-               $dbDomain,
-               $mcrMigrationStage = MIGRATION_OLD,
-               $contentHandlerUseDb = true
-       ) {
-               $lbFactory = $this->getMockLoadBalancerFactory();
-               $blobStoreFactory = $this->getMockBlobStoreFactory();
-               $nameTableStoreFactory = $this->getNameTableStoreFactory();
-               $slotRoleRegistry = $this->getMockSlotRoleRegistry();
-               $cache = $this->getHashWANObjectCache();
-               $commentStore = $this->getMockCommentStore();
-               $actorMigration = ActorMigration::newMigration();
-               $loggerProvider = $this->getMockLoggerSpi();
-
-               $factory = new RevisionStoreFactory(
-                       $lbFactory,
-                       $blobStoreFactory,
-                       $nameTableStoreFactory,
-                       $slotRoleRegistry,
-                       $cache,
-                       $commentStore,
-                       $actorMigration,
-                       $mcrMigrationStage,
-                       $loggerProvider,
-                       $contentHandlerUseDb
-               );
-
-               $store = $factory->getRevisionStore( $dbDomain );
-               $wrapper = TestingAccessWrapper::newFromObject( $store );
-
-               // ensure the correct object type is returned
-               $this->assertInstanceOf( RevisionStore::class, $store );
-
-               // ensure the RevisionStore is for the given wikiId
-               $this->assertSame( $dbDomain, $wrapper->dbDomain );
-
-               // ensure all other required services are correctly set
-               $this->assertSame( $cache, $wrapper->cache );
-               $this->assertSame( $commentStore, $wrapper->commentStore );
-               $this->assertSame( $mcrMigrationStage, $wrapper->mcrMigrationStage );
-               $this->assertSame( $actorMigration, $wrapper->actorMigration );
-               $this->assertSame( $contentHandlerUseDb, $store->getContentHandlerUseDB() );
-
-               $this->assertInstanceOf( ILoadBalancer::class, $wrapper->loadBalancer );
-               $this->assertInstanceOf( BlobStore::class, $wrapper->blobStore );
-               $this->assertInstanceOf( NameTableStore::class, $wrapper->contentModelStore );
-               $this->assertInstanceOf( NameTableStore::class, $wrapper->slotRoleStore );
-               $this->assertInstanceOf( LoggerInterface::class, $wrapper->logger );
-       }
-
-       /**
-        * @return \PHPUnit_Framework_MockObject_MockObject|ILoadBalancer
-        */
-       private function getMockLoadBalancer() {
-               return $this->getMockBuilder( ILoadBalancer::class )
-                       ->disableOriginalConstructor()->getMock();
-       }
-
-       /**
-        * @return \PHPUnit_Framework_MockObject_MockObject|ILBFactory
-        */
-       private function getMockLoadBalancerFactory() {
-               $mock = $this->getMockBuilder( ILBFactory::class )
-                       ->disableOriginalConstructor()->getMock();
-
-               $mock->method( 'getMainLB' )
-                       ->willReturnCallback( function () {
-                               return $this->getMockLoadBalancer();
-                       } );
-
-               return $mock;
-       }
-
-       /**
-        * @return \PHPUnit_Framework_MockObject_MockObject|SqlBlobStore
-        */
-       private function getMockSqlBlobStore() {
-               return $this->getMockBuilder( SqlBlobStore::class )
-                       ->disableOriginalConstructor()->getMock();
-       }
-
-       /**
-        * @return \PHPUnit_Framework_MockObject_MockObject|BlobStoreFactory
-        */
-       private function getMockBlobStoreFactory() {
-               $mock = $this->getMockBuilder( BlobStoreFactory::class )
-                       ->disableOriginalConstructor()->getMock();
-
-               $mock->method( 'newSqlBlobStore' )
-                       ->willReturnCallback( function () {
-                               return $this->getMockSqlBlobStore();
-                       } );
-
-               return $mock;
-       }
-
-       /**
-        * @return SlotRoleRegistry
-        */
-       private function getMockSlotRoleRegistry() {
-               return $this->createMock( SlotRoleRegistry::class );
-       }
-
-       /**
-        * @return NameTableStoreFactory
-        */
-       private function getNameTableStoreFactory() {
-               return new NameTableStoreFactory(
-                       $this->getMockLoadBalancerFactory(),
-                       $this->getHashWANObjectCache(),
-                       new NullLogger() );
-       }
-
-       /**
-        * @return \PHPUnit_Framework_MockObject_MockObject|CommentStore
-        */
-       private function getMockCommentStore() {
-               return $this->getMockBuilder( CommentStore::class )
-                       ->disableOriginalConstructor()->getMock();
-       }
-
-       private function getHashWANObjectCache() {
-               return new WANObjectCache( [ 'cache' => new \HashBagOStuff() ] );
-       }
-
-       /**
-        * @return \PHPUnit_Framework_MockObject_MockObject|LoggerSpi
-        */
-       private function getMockLoggerSpi() {
-               $mock = $this->getMock( LoggerSpi::class );
-
-               $mock->method( 'getLogger' )
-                       ->willReturn( new NullLogger() );
-
-               return $mock;
-       }
-
-}
diff --git a/tests/phpunit/includes/config/ConfigFactoryTest.php b/tests/phpunit/includes/config/ConfigFactoryTest.php
deleted file mode 100644 (file)
index ea747af..0000000
+++ /dev/null
@@ -1,168 +0,0 @@
-<?php
-
-use MediaWiki\MediaWikiServices;
-
-class ConfigFactoryTest extends MediaWikiTestCase {
-
-       /**
-        * @covers ConfigFactory::register
-        */
-       public function testRegister() {
-               $factory = new ConfigFactory();
-               $factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
-               $this->assertInstanceOf( GlobalVarConfig::class, $factory->makeConfig( 'unittest' ) );
-       }
-
-       /**
-        * @covers ConfigFactory::register
-        */
-       public function testRegisterInvalid() {
-               $factory = new ConfigFactory();
-               $this->setExpectedException( InvalidArgumentException::class );
-               $factory->register( 'invalid', 'Invalid callback' );
-       }
-
-       /**
-        * @covers ConfigFactory::register
-        */
-       public function testRegisterInvalidInstance() {
-               $factory = new ConfigFactory();
-               $this->setExpectedException( InvalidArgumentException::class );
-               $factory->register( 'invalidInstance', new stdClass );
-       }
-
-       /**
-        * @covers ConfigFactory::register
-        */
-       public function testRegisterInstance() {
-               $config = GlobalVarConfig::newInstance();
-               $factory = new ConfigFactory();
-               $factory->register( 'unittest', $config );
-               $this->assertSame( $config, $factory->makeConfig( 'unittest' ) );
-       }
-
-       /**
-        * @covers ConfigFactory::register
-        */
-       public function testRegisterAgain() {
-               $factory = new ConfigFactory();
-               $factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
-               $config1 = $factory->makeConfig( 'unittest' );
-
-               $factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
-               $config2 = $factory->makeConfig( 'unittest' );
-
-               $this->assertNotSame( $config1, $config2 );
-       }
-
-       /**
-        * @covers ConfigFactory::salvage
-        */
-       public function testSalvage() {
-               $oldFactory = new ConfigFactory();
-               $oldFactory->register( 'foo', 'GlobalVarConfig::newInstance' );
-               $oldFactory->register( 'bar', 'GlobalVarConfig::newInstance' );
-               $oldFactory->register( 'quux', 'GlobalVarConfig::newInstance' );
-
-               // instantiate two of the three defined configurations
-               $foo = $oldFactory->makeConfig( 'foo' );
-               $bar = $oldFactory->makeConfig( 'bar' );
-               $quux = $oldFactory->makeConfig( 'quux' );
-
-               // define new config instance
-               $newFactory = new ConfigFactory();
-               $newFactory->register( 'foo', 'GlobalVarConfig::newInstance' );
-               $newFactory->register( 'bar', function () {
-                       return new HashConfig();
-               } );
-
-               // "foo" and "quux" are defined in the old and the new factory.
-               // The old factory has instances for "foo" and "bar", but not "quux".
-               $newFactory->salvage( $oldFactory );
-
-               $newFoo = $newFactory->makeConfig( 'foo' );
-               $this->assertSame( $foo, $newFoo, 'existing instance should be salvaged' );
-
-               $newBar = $newFactory->makeConfig( 'bar' );
-               $this->assertNotSame( $bar, $newBar, 'don\'t salvage if callbacks differ' );
-
-               // the new factory doesn't have quux defined, so the quux instance should not be salvaged
-               $this->setExpectedException( ConfigException::class );
-               $newFactory->makeConfig( 'quux' );
-       }
-
-       /**
-        * @covers ConfigFactory::getConfigNames
-        */
-       public function testGetConfigNames() {
-               $factory = new ConfigFactory();
-               $factory->register( 'foo', 'GlobalVarConfig::newInstance' );
-               $factory->register( 'bar', new HashConfig() );
-
-               $this->assertEquals( [ 'foo', 'bar' ], $factory->getConfigNames() );
-       }
-
-       /**
-        * @covers ConfigFactory::makeConfig
-        */
-       public function testMakeConfigWithCallback() {
-               $factory = new ConfigFactory();
-               $factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
-
-               $conf = $factory->makeConfig( 'unittest' );
-               $this->assertInstanceOf( Config::class, $conf );
-               $this->assertSame( $conf, $factory->makeConfig( 'unittest' ) );
-       }
-
-       /**
-        * @covers ConfigFactory::makeConfig
-        */
-       public function testMakeConfigWithObject() {
-               $factory = new ConfigFactory();
-               $conf = new HashConfig();
-               $factory->register( 'test', $conf );
-               $this->assertSame( $conf, $factory->makeConfig( 'test' ) );
-       }
-
-       /**
-        * @covers ConfigFactory::makeConfig
-        */
-       public function testMakeConfigFallback() {
-               $factory = new ConfigFactory();
-               $factory->register( '*', 'GlobalVarConfig::newInstance' );
-               $conf = $factory->makeConfig( 'unittest' );
-               $this->assertInstanceOf( Config::class, $conf );
-       }
-
-       /**
-        * @covers ConfigFactory::makeConfig
-        */
-       public function testMakeConfigWithNoBuilders() {
-               $factory = new ConfigFactory();
-               $this->setExpectedException( ConfigException::class );
-               $factory->makeConfig( 'nobuilderregistered' );
-       }
-
-       /**
-        * @covers ConfigFactory::makeConfig
-        */
-       public function testMakeConfigWithInvalidCallback() {
-               $factory = new ConfigFactory();
-               $factory->register( 'unittest', function () {
-                       return true; // Not a Config object
-               } );
-               $this->setExpectedException( UnexpectedValueException::class );
-               $factory->makeConfig( 'unittest' );
-       }
-
-       /**
-        * @covers ConfigFactory::getDefaultInstance
-        */
-       public function testGetDefaultInstance() {
-               // NOTE: the global config factory returned here has been overwritten
-               // for operation in test mode. It may not reflect LocalSettings.
-               $factory = MediaWikiServices::getInstance()->getConfigFactory();
-               $this->assertInstanceOf( Config::class, $factory->makeConfig( 'main' ) );
-       }
-
-}
diff --git a/tests/phpunit/includes/media/GIFMetadataExtractorTest.php b/tests/phpunit/includes/media/GIFMetadataExtractorTest.php
deleted file mode 100644 (file)
index 278b441..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-<?php
-
-/**
- * @group Media
- */
-class GIFMetadataExtractorTest extends MediaWikiTestCase {
-
-       protected function setUp() {
-               parent::setUp();
-
-               $this->mediaPath = __DIR__ . '/../../data/media/';
-       }
-
-       /**
-        * Put in a file, and see if the metadata coming out is as expected.
-        * @param string $filename
-        * @param array $expected The extracted metadata.
-        * @dataProvider provideGetMetadata
-        * @covers GIFMetadataExtractor::getMetadata
-        */
-       public function testGetMetadata( $filename, $expected ) {
-               $actual = GIFMetadataExtractor::getMetadata( $this->mediaPath . $filename );
-               $this->assertEquals( $expected, $actual );
-       }
-
-       public static function provideGetMetadata() {
-               $xmpNugget = <<<EOF
-<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
-<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 7.30'>
-<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
-
- <rdf:Description rdf:about=''
-  xmlns:Iptc4xmpCore='http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/'>
-  <Iptc4xmpCore:Location>The interwebs</Iptc4xmpCore:Location>
- </rdf:Description>
-
- <rdf:Description rdf:about=''
-  xmlns:tiff='http://ns.adobe.com/tiff/1.0/'>
-  <tiff:Artist>Bawolff</tiff:Artist>
-  <tiff:ImageDescription>
-   <rdf:Alt>
-    <rdf:li xml:lang='x-default'>A file to test GIF</rdf:li>
-   </rdf:Alt>
-  </tiff:ImageDescription>
- </rdf:Description>
-</rdf:RDF>
-</x:xmpmeta>
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-<?xpacket end='w'?>
-EOF;
-               $xmpNugget = str_replace( "\r", '', $xmpNugget ); // Windows compat
-
-               return [
-                       [
-                               'nonanimated.gif',
-                               [
-                                       'comment' => [ 'GIF test file ⁕ Created with GIMP' ],
-                                       'duration' => 0.1,
-                                       'frameCount' => 1,
-                                       'looped' => false,
-                                       'xmp' => '',
-                               ]
-                       ],
-                       [
-                               'animated.gif',
-                               [
-                                       'comment' => [ 'GIF test file . Created with GIMP' ],
-                                       'duration' => 2.4,
-                                       'frameCount' => 4,
-                                       'looped' => true,
-                                       'xmp' => '',
-                               ]
-                       ],
-
-                       [
-                               'animated-xmp.gif',
-                               [
-                                       'xmp' => $xmpNugget,
-                                       'duration' => 2.4,
-                                       'frameCount' => 4,
-                                       'looped' => true,
-                                       'comment' => [ 'GIƒ·test·file' ],
-                               ]
-                       ],
-               ];
-       }
-}
diff --git a/tests/phpunit/includes/media/SVGMetadataExtractorTest.php b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php
deleted file mode 100644 (file)
index 6b94d0a..0000000
+++ /dev/null
@@ -1,201 +0,0 @@
-<?php
-
-/**
- * @group Media
- * @covers SVGMetadataExtractor
- */
-class SVGMetadataExtractorTest extends MediaWikiTestCase {
-
-       /**
-        * @dataProvider provideSvgFiles
-        */
-       public function testGetMetadata( $infile, $expected ) {
-               $this->assertMetadata( $infile, $expected );
-       }
-
-       /**
-        * @dataProvider provideSvgFilesWithXMLMetadata
-        */
-       public function testGetXMLMetadata( $infile, $expected ) {
-               $r = new XMLReader();
-               $this->assertMetadata( $infile, $expected );
-       }
-
-       /**
-        * @dataProvider provideSvgUnits
-        */
-       public function testScaleSVGUnit( $inUnit, $expected ) {
-               $this->assertEquals(
-                       $expected,
-                       SVGReader::scaleSVGUnit( $inUnit ),
-                       'SVG unit conversion and scaling failure'
-               );
-       }
-
-       function assertMetadata( $infile, $expected ) {
-               try {
-                       $data = SVGMetadataExtractor::getMetadata( $infile );
-                       $this->assertEquals( $expected, $data, 'SVG metadata extraction test' );
-               } catch ( MWException $e ) {
-                       if ( $expected === false ) {
-                               $this->assertTrue( true, 'SVG metadata extracted test (expected failure)' );
-                       } else {
-                               throw $e;
-                       }
-               }
-       }
-
-       public static function provideSvgFiles() {
-               $base = __DIR__ . '/../../data/media';
-
-               return [
-                       [
-                               "$base/Wikimedia-logo.svg",
-                               [
-                                       'width' => 1024,
-                                       'height' => 1024,
-                                       'originalWidth' => '1024',
-                                       'originalHeight' => '1024',
-                                       'translations' => [],
-                               ]
-                       ],
-                       [
-                               "$base/QA_icon.svg",
-                               [
-                                       'width' => 60,
-                                       'height' => 60,
-                                       'originalWidth' => '60',
-                                       'originalHeight' => '60',
-                                       'translations' => [],
-                               ]
-                       ],
-                       [
-                               "$base/Gtk-media-play-ltr.svg",
-                               [
-                                       'width' => 60,
-                                       'height' => 60,
-                                       'originalWidth' => '60.0000000',
-                                       'originalHeight' => '60.0000000',
-                                       'translations' => [],
-                               ]
-                       ],
-                       [
-                               "$base/Toll_Texas_1.svg",
-                               // This file triggered T33719, needs entity expansion in the xmlns checks
-                               [
-                                       'width' => 385,
-                                       'height' => 385,
-                                       'originalWidth' => '385',
-                                       'originalHeight' => '385.0004883',
-                                       'translations' => [],
-                               ]
-                       ],
-                       [
-                               "$base/Tux.svg",
-                               [
-                                       'width' => 512,
-                                       'height' => 594,
-                                       'originalWidth' => '100%',
-                                       'originalHeight' => '100%',
-                                       'title' => 'Tux',
-                                       'translations' => [],
-                                       'description' => 'For more information see: http://commons.wikimedia.org/wiki/Image:Tux.svg',
-                               ]
-                       ],
-                       [
-                               "$base/Speech_bubbles.svg",
-                               [
-                                       'width' => 627,
-                                       'height' => 461,
-                                       'originalWidth' => '17.7cm',
-                                       'originalHeight' => '13cm',
-                                       'translations' => [
-                                               'de' => SVGReader::LANG_FULL_MATCH,
-                                               'fr' => SVGReader::LANG_FULL_MATCH,
-                                               'nl' => SVGReader::LANG_FULL_MATCH,
-                                               'tlh-ca' => SVGReader::LANG_FULL_MATCH,
-                                               'tlh' => SVGReader::LANG_PREFIX_MATCH
-                                       ],
-                               ]
-                       ],
-                       [
-                               "$base/Soccer_ball_animated.svg",
-                               [
-                                       'width' => 150,
-                                       'height' => 150,
-                                       'originalWidth' => '150',
-                                       'originalHeight' => '150',
-                                       'animated' => true,
-                                       'translations' => []
-                               ],
-                       ],
-                       [
-                               "$base/comma_separated_viewbox.svg",
-                               [
-                                       'width' => 512,
-                                       'height' => 594,
-                                       'originalWidth' => '100%',
-                                       'originalHeight' => '100%',
-                                       'translations' => []
-                               ],
-                       ],
-               ];
-       }
-
-       public static function provideSvgFilesWithXMLMetadata() {
-               $base = __DIR__ . '/../../data/media';
-               // phpcs:disable Generic.Files.LineLength
-               $metadata = '<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
-      <ns4:Work xmlns:ns4="http://creativecommons.org/ns#" rdf:about="">
-        <ns5:format xmlns:ns5="http://purl.org/dc/elements/1.1/">image/svg+xml</ns5:format>
-        <ns5:type xmlns:ns5="http://purl.org/dc/elements/1.1/" rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
-      </ns4:Work>
-    </rdf:RDF>';
-               // phpcs:enable
-
-               $metadata = str_replace( "\r", '', $metadata ); // Windows compat
-               return [
-                       [
-                               "$base/US_states_by_total_state_tax_revenue.svg",
-                               [
-                                       'height' => 593,
-                                       'metadata' => $metadata,
-                                       'width' => 959,
-                                       'originalWidth' => '958.69',
-                                       'originalHeight' => '592.78998',
-                                       'translations' => [],
-                               ]
-                       ],
-               ];
-       }
-
-       public static function provideSvgUnits() {
-               return [
-                       [ '1' , 1 ],
-                       [ '1.1' , 1.1 ],
-                       [ '0.1' , 0.1 ],
-                       [ '.1' , 0.1 ],
-                       [ '1e2' , 100 ],
-                       [ '1E2' , 100 ],
-                       [ '+1' , 1 ],
-                       [ '-1' , -1 ],
-                       [ '-1.1' , -1.1 ],
-                       [ '1e+2' , 100 ],
-                       [ '1e-2' , 0.01 ],
-                       [ '10px' , 10 ],
-                       [ '10pt' , 10 * 1.25 ],
-                       [ '10pc' , 10 * 15 ],
-                       [ '10mm' , 10 * 3.543307 ],
-                       [ '10cm' , 10 * 35.43307 ],
-                       [ '10in' , 10 * 90 ],
-                       [ '10em' , 10 * 16 ],
-                       [ '10ex' , 10 * 12 ],
-                       [ '10%' , 51.2 ],
-                       [ '10 px' , 10 ],
-                       // Invalid values
-                       [ '1e1.1', 10 ],
-                       [ '10bp', 10 ],
-                       [ 'p10', null ],
-               ];
-       }
-}
diff --git a/tests/phpunit/includes/site/CachingSiteStoreTest.php b/tests/phpunit/includes/site/CachingSiteStoreTest.php
deleted file mode 100644 (file)
index f04d35c..0000000
+++ /dev/null
@@ -1,167 +0,0 @@
-<?php
-
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @since 1.25
- *
- * @ingroup Site
- * @ingroup Test
- *
- * @group Site
- * @group Database
- *
- * @author Jeroen De Dauw < jeroendedauw@gmail.com >
- */
-class CachingSiteStoreTest extends MediaWikiTestCase {
-
-       /**
-        * @covers CachingSiteStore::getSites
-        */
-       public function testGetSites() {
-               $testSites = TestSites::getSites();
-
-               $store = new CachingSiteStore(
-                       $this->getHashSiteStore( $testSites ),
-                       ObjectCache::getLocalClusterInstance()
-               );
-
-               $sites = $store->getSites();
-
-               $this->assertInstanceOf( SiteList::class, $sites );
-
-               /**
-                * @var Site $site
-                */
-               foreach ( $sites as $site ) {
-                       $this->assertInstanceOf( Site::class, $site );
-               }
-
-               foreach ( $testSites as $site ) {
-                       if ( $site->getGlobalId() !== null ) {
-                               $this->assertTrue( $sites->hasSite( $site->getGlobalId() ) );
-                       }
-               }
-       }
-
-       /**
-        * @covers CachingSiteStore::saveSites
-        */
-       public function testSaveSites() {
-               $store = new CachingSiteStore(
-                       new HashSiteStore(), ObjectCache::getLocalClusterInstance()
-               );
-
-               $sites = [];
-
-               $site = new Site();
-               $site->setGlobalId( 'ertrywuutr' );
-               $site->setLanguageCode( 'en' );
-               $sites[] = $site;
-
-               $site = new MediaWikiSite();
-               $site->setGlobalId( 'sdfhxujgkfpth' );
-               $site->setLanguageCode( 'nl' );
-               $sites[] = $site;
-
-               $this->assertTrue( $store->saveSites( $sites ) );
-
-               $site = $store->getSite( 'ertrywuutr' );
-               $this->assertInstanceOf( Site::class, $site );
-               $this->assertEquals( 'en', $site->getLanguageCode() );
-
-               $site = $store->getSite( 'sdfhxujgkfpth' );
-               $this->assertInstanceOf( Site::class, $site );
-               $this->assertEquals( 'nl', $site->getLanguageCode() );
-       }
-
-       /**
-        * @covers CachingSiteStore::reset
-        */
-       public function testReset() {
-               $dbSiteStore = $this->getMockBuilder( SiteStore::class )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-
-               $dbSiteStore->expects( $this->any() )
-                       ->method( 'getSite' )
-                       ->will( $this->returnValue( $this->getTestSite() ) );
-
-               $dbSiteStore->expects( $this->any() )
-                       ->method( 'getSites' )
-                       ->will( $this->returnCallback( function () {
-                               $siteList = new SiteList();
-                               $siteList->setSite( $this->getTestSite() );
-
-                               return $siteList;
-                       } ) );
-
-               $store = new CachingSiteStore( $dbSiteStore, ObjectCache::getLocalClusterInstance() );
-
-               // initialize internal cache
-               $this->assertGreaterThan( 0, $store->getSites()->count(), 'count sites' );
-
-               $store->getSite( 'enwiki' )->setLanguageCode( 'en-ca' );
-
-               // sanity check: $store should have the new language code for 'enwiki'
-               $this->assertEquals( 'en-ca', $store->getSite( 'enwiki' )->getLanguageCode(), 'sanity check' );
-
-               // purge cache
-               $store->reset();
-
-               // the internal cache of $store should be updated, and now pulling
-               // the site from the 'fallback' DBSiteStore with the original language code.
-               $this->assertEquals( 'en', $store->getSite( 'enwiki' )->getLanguageCode(), 'reset' );
-       }
-
-       public function getTestSite() {
-               $enwiki = new MediaWikiSite();
-               $enwiki->setGlobalId( 'enwiki' );
-               $enwiki->setLanguageCode( 'en' );
-
-               return $enwiki;
-       }
-
-       /**
-        * @covers CachingSiteStore::clear
-        */
-       public function testClear() {
-               $store = new CachingSiteStore(
-                       new HashSiteStore(), ObjectCache::getLocalClusterInstance()
-               );
-               $this->assertTrue( $store->clear() );
-
-               $site = $store->getSite( 'enwiki' );
-               $this->assertNull( $site );
-
-               $sites = $store->getSites();
-               $this->assertEquals( 0, $sites->count() );
-       }
-
-       /**
-        * @param Site[] $sites
-        *
-        * @return SiteStore
-        */
-       private function getHashSiteStore( array $sites ) {
-               $siteStore = new HashSiteStore();
-               $siteStore->saveSites( $sites );
-
-               return $siteStore;
-       }
-
-}
diff --git a/tests/phpunit/includes/site/HashSiteStoreTest.php b/tests/phpunit/includes/site/HashSiteStoreTest.php
deleted file mode 100644 (file)
index 6269fd3..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-<?php
-
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @since 1.25
- *
- * @ingroup Site
- * @group Site
- *
- * @author Katie Filbert < aude.wiki@gmail.com >
- */
-class HashSiteStoreTest extends MediaWikiTestCase {
-
-       /**
-        * @covers HashSiteStore::getSites
-        */
-       public function testGetSites() {
-               $expectedSites = [];
-
-               foreach ( TestSites::getSites() as $testSite ) {
-                       $siteId = $testSite->getGlobalId();
-                       $expectedSites[$siteId] = $testSite;
-               }
-
-               $siteStore = new HashSiteStore( $expectedSites );
-
-               $this->assertEquals( new SiteList( $expectedSites ), $siteStore->getSites() );
-       }
-
-       /**
-        * @covers HashSiteStore::saveSite
-        * @covers HashSiteStore::getSite
-        */
-       public function testSaveSite() {
-               $store = new HashSiteStore();
-
-               $site = new Site();
-               $site->setGlobalId( 'dewiki' );
-
-               $this->assertCount( 0, $store->getSites(), '0 sites in store' );
-
-               $store->saveSite( $site );
-
-               $this->assertCount( 1, $store->getSites(), 'Store has 1 sites' );
-               $this->assertEquals( $site, $store->getSite( 'dewiki' ), 'Store has dewiki' );
-       }
-
-       /**
-        * @covers HashSiteStore::saveSites
-        */
-       public function testSaveSites() {
-               $store = new HashSiteStore();
-
-               $sites = [];
-
-               $site = new Site();
-               $site->setGlobalId( 'enwiki' );
-               $site->setLanguageCode( 'en' );
-               $sites[] = $site;
-
-               $site = new MediaWikiSite();
-               $site->setGlobalId( 'eswiki' );
-               $site->setLanguageCode( 'es' );
-               $sites[] = $site;
-
-               $this->assertCount( 0, $store->getSites(), '0 sites in store' );
-
-               $store->saveSites( $sites );
-
-               $this->assertCount( 2, $store->getSites(), 'Store has 2 sites' );
-               $this->assertTrue( $store->getSites()->hasSite( 'enwiki' ), 'Store has enwiki' );
-               $this->assertTrue( $store->getSites()->hasSite( 'eswiki' ), 'Store has eswiki' );
-       }
-
-       /**
-        * @covers HashSiteStore::clear
-        */
-       public function testClear() {
-               $store = new HashSiteStore();
-
-               $site = new Site();
-               $site->setGlobalId( 'arwiki' );
-               $store->saveSite( $site );
-
-               $this->assertCount( 1, $store->getSites(), '1 site in store' );
-
-               $store->clear();
-               $this->assertCount( 0, $store->getSites(), '0 sites in store' );
-       }
-}
diff --git a/tests/phpunit/unit/includes/MediaWikiVersionFetcherTest.php b/tests/phpunit/unit/includes/MediaWikiVersionFetcherTest.php
new file mode 100644 (file)
index 0000000..dfdbfa7
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * Note: this is not a unit test, as it touches the file system and reads an actual file.
+ * If unit tests are added for MediaWikiVersionFetcher, this should be done in a distinct test case.
+ *
+ * @covers MediaWikiVersionFetcher
+ *
+ * @group ComposerHooks
+ *
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class MediaWikiVersionFetcherTest extends \MediaWikiUnitTestCase {
+
+       public function testReturnsResult() {
+               global $wgVersion;
+               $versionFetcher = new MediaWikiVersionFetcher();
+               $this->assertSame( $wgVersion, $versionFetcher->fetchVersion() );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/Rest/EntryPointTest.php b/tests/phpunit/unit/includes/Rest/EntryPointTest.php
new file mode 100644 (file)
index 0000000..e1f2c88
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+
+namespace MediaWiki\Tests\Rest;
+
+use EmptyBagOStuff;
+use GuzzleHttp\Psr7\Uri;
+use GuzzleHttp\Psr7\Stream;
+use MediaWiki\Rest\Handler;
+use MediaWiki\Rest\EntryPoint;
+use MediaWiki\Rest\RequestData;
+use MediaWiki\Rest\ResponseFactory;
+use MediaWiki\Rest\Router;
+use WebResponse;
+
+/**
+ * @covers \MediaWiki\Rest\EntryPoint
+ * @covers \MediaWiki\Rest\Router
+ */
+class EntryPointTest extends \MediaWikiUnitTestCase {
+       private static $mockHandler;
+
+       private function createRouter() {
+               return new Router(
+                       [ __DIR__ . '/testRoutes.json' ],
+                       [],
+                       '/rest',
+                       new EmptyBagOStuff(),
+                       new ResponseFactory() );
+       }
+
+       private function createWebResponse() {
+               return $this->getMockBuilder( WebResponse::class )
+                       ->setMethods( [ 'header' ] )
+                       ->getMock();
+       }
+
+       public static function mockHandlerHeader() {
+               return new class extends Handler {
+                       public function execute() {
+                               $response = $this->getResponseFactory()->create();
+                               $response->setHeader( 'Foo', 'Bar' );
+                               return $response;
+                       }
+               };
+       }
+
+       public function testHeader() {
+               $webResponse = $this->createWebResponse();
+               $webResponse->expects( $this->any() )
+                       ->method( 'header' )
+                       ->withConsecutive(
+                               [ 'HTTP/1.1 200 OK', true, null ],
+                               [ 'Foo: Bar', true, null ]
+                       );
+
+               $entryPoint = new EntryPoint(
+                       new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/header' ) ] ),
+                       $webResponse,
+                       $this->createRouter() );
+               $entryPoint->execute();
+               $this->assertTrue( true );
+       }
+
+       public static function mockHandlerBodyRewind() {
+               return new class extends Handler {
+                       public function execute() {
+                               $response = $this->getResponseFactory()->create();
+                               $stream = new Stream( fopen( 'php://memory', 'w+' ) );
+                               $stream->write( 'hello' );
+                               $response->setBody( $stream );
+                               return $response;
+                       }
+               };
+       }
+
+       /**
+        * Make sure EntryPoint rewinds a seekable body stream before reading.
+        */
+       public function testBodyRewind() {
+               $entryPoint = new EntryPoint(
+                       new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/bodyRewind' ) ] ),
+                       $this->createWebResponse(),
+                       $this->createRouter() );
+               ob_start();
+               $entryPoint->execute();
+               $this->assertSame( 'hello', ob_get_clean() );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/Rest/Handler/HelloHandlerTest.php b/tests/phpunit/unit/includes/Rest/Handler/HelloHandlerTest.php
new file mode 100644 (file)
index 0000000..c68273b
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+
+namespace MediaWiki\Tests\Rest\Handler;
+
+use EmptyBagOStuff;
+use GuzzleHttp\Psr7\Uri;
+use MediaWiki\Rest\RequestData;
+use MediaWiki\Rest\ResponseFactory;
+use MediaWiki\Rest\Router;
+
+/**
+ * @covers \MediaWiki\Rest\Handler\HelloHandler
+ */
+class HelloHandlerTest extends \MediaWikiUnitTestCase {
+       public static function provideTestViaRouter() {
+               return [
+                       'normal' => [
+                               [
+                                       'method' => 'GET',
+                                       'uri' => self::makeUri( '/user/Tim/hello' ),
+                               ],
+                               [
+                                       'statusCode' => 200,
+                                       'reasonPhrase' => 'OK',
+                                       'protocolVersion' => '1.1',
+                                       'body' => '{"message":"Hello, Tim!"}',
+                               ],
+                       ],
+                       'method not allowed' => [
+                               [
+                                       'method' => 'POST',
+                                       'uri' => self::makeUri( '/user/Tim/hello' ),
+                               ],
+                               [
+                                       'statusCode' => 405,
+                                       'reasonPhrase' => 'Method Not Allowed',
+                                       'protocolVersion' => '1.1',
+                                       'body' => '{"httpCode":405,"httpReason":"Method Not Allowed"}',
+                               ],
+                       ],
+               ];
+       }
+
+       private static function makeUri( $path ) {
+               return new Uri( "http://www.example.com/rest$path" );
+       }
+
+       /** @dataProvider provideTestViaRouter */
+       public function testViaRouter( $requestInfo, $responseInfo ) {
+               $router = new Router(
+                       [ __DIR__ . '/../testRoutes.json' ],
+                       [],
+                       '/rest',
+                       new EmptyBagOStuff(),
+                       new ResponseFactory() );
+               $request = new RequestData( $requestInfo );
+               $response = $router->execute( $request );
+               if ( isset( $responseInfo['statusCode'] ) ) {
+                       $this->assertSame( $responseInfo['statusCode'], $response->getStatusCode() );
+               }
+               if ( isset( $responseInfo['reasonPhrase'] ) ) {
+                       $this->assertSame( $responseInfo['reasonPhrase'], $response->getReasonPhrase() );
+               }
+               if ( isset( $responseInfo['protocolVersion'] ) ) {
+                       $this->assertSame( $responseInfo['protocolVersion'], $response->getProtocolVersion() );
+               }
+               if ( isset( $responseInfo['body'] ) ) {
+                       $this->assertSame( $responseInfo['body'], $response->getBody()->getContents() );
+               }
+               $this->assertSame(
+                       [],
+                       array_diff( array_keys( $responseInfo ), [
+                               'statusCode',
+                               'reasonPhrase',
+                               'protocolVersion',
+                               'body'
+                       ] ),
+                       '$responseInfo may not contain unknown keys' );
+       }
+}
diff --git a/tests/phpunit/unit/includes/Rest/testRoutes.json b/tests/phpunit/unit/includes/Rest/testRoutes.json
new file mode 100644 (file)
index 0000000..7e43bb0
--- /dev/null
@@ -0,0 +1,14 @@
+[
+       {
+               "path": "/user/{name}/hello",
+               "class": "MediaWiki\\Rest\\Handler\\HelloHandler"
+       },
+       {
+               "path": "/mock/EntryPoint/header",
+               "factory": "MediaWiki\\Tests\\Rest\\EntryPointTest::mockHandlerHeader"
+       },
+       {
+               "path": "/mock/EntryPoint/bodyRewind",
+               "factory": "MediaWiki\\Tests\\Rest\\EntryPointTest::mockHandlerBodyRewind"
+       }
+]
diff --git a/tests/phpunit/unit/includes/Revision/RevisionStoreFactoryTest.php b/tests/phpunit/unit/includes/Revision/RevisionStoreFactoryTest.php
new file mode 100644 (file)
index 0000000..8e8fbd7
--- /dev/null
@@ -0,0 +1,193 @@
+<?php
+
+namespace MediaWiki\Tests\Revision;
+
+use ActorMigration;
+use CommentStore;
+use MediaWiki\Logger\Spi as LoggerSpi;
+use MediaWiki\Revision\RevisionStore;
+use MediaWiki\Revision\RevisionStoreFactory;
+use MediaWiki\Revision\SlotRoleRegistry;
+use MediaWiki\Storage\BlobStore;
+use MediaWiki\Storage\BlobStoreFactory;
+use MediaWiki\Storage\NameTableStore;
+use MediaWiki\Storage\NameTableStoreFactory;
+use MediaWiki\Storage\SqlBlobStore;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+use WANObjectCache;
+use Wikimedia\Rdbms\ILBFactory;
+use Wikimedia\Rdbms\ILoadBalancer;
+use Wikimedia\TestingAccessWrapper;
+
+class RevisionStoreFactoryTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @covers \MediaWiki\Revision\RevisionStoreFactory::__construct
+        */
+       public function testValidConstruction_doesntCauseErrors() {
+               new RevisionStoreFactory(
+                       $this->getMockLoadBalancerFactory(),
+                       $this->getMockBlobStoreFactory(),
+                       $this->getNameTableStoreFactory(),
+                       $this->getMockSlotRoleRegistry(),
+                       $this->getHashWANObjectCache(),
+                       $this->getMockCommentStore(),
+                       ActorMigration::newMigration(),
+                       MIGRATION_OLD,
+                       $this->getMockLoggerSpi(),
+                       true
+               );
+               $this->assertTrue( true );
+       }
+
+       public function provideWikiIds() {
+               yield [ true ];
+               yield [ false ];
+               yield [ 'somewiki' ];
+               yield [ 'somewiki', MIGRATION_OLD , false ];
+               yield [ 'somewiki', MIGRATION_NEW , true ];
+       }
+
+       /**
+        * @dataProvider provideWikiIds
+        * @covers \MediaWiki\Revision\RevisionStoreFactory::getRevisionStore
+        */
+       public function testGetRevisionStore(
+               $dbDomain,
+               $mcrMigrationStage = MIGRATION_OLD,
+               $contentHandlerUseDb = true
+       ) {
+               $lbFactory = $this->getMockLoadBalancerFactory();
+               $blobStoreFactory = $this->getMockBlobStoreFactory();
+               $nameTableStoreFactory = $this->getNameTableStoreFactory();
+               $slotRoleRegistry = $this->getMockSlotRoleRegistry();
+               $cache = $this->getHashWANObjectCache();
+               $commentStore = $this->getMockCommentStore();
+               $actorMigration = ActorMigration::newMigration();
+               $loggerProvider = $this->getMockLoggerSpi();
+
+               $factory = new RevisionStoreFactory(
+                       $lbFactory,
+                       $blobStoreFactory,
+                       $nameTableStoreFactory,
+                       $slotRoleRegistry,
+                       $cache,
+                       $commentStore,
+                       $actorMigration,
+                       $mcrMigrationStage,
+                       $loggerProvider,
+                       $contentHandlerUseDb
+               );
+
+               $store = $factory->getRevisionStore( $dbDomain );
+               $wrapper = TestingAccessWrapper::newFromObject( $store );
+
+               // ensure the correct object type is returned
+               $this->assertInstanceOf( RevisionStore::class, $store );
+
+               // ensure the RevisionStore is for the given wikiId
+               $this->assertSame( $dbDomain, $wrapper->dbDomain );
+
+               // ensure all other required services are correctly set
+               $this->assertSame( $cache, $wrapper->cache );
+               $this->assertSame( $commentStore, $wrapper->commentStore );
+               $this->assertSame( $mcrMigrationStage, $wrapper->mcrMigrationStage );
+               $this->assertSame( $actorMigration, $wrapper->actorMigration );
+               $this->assertSame( $contentHandlerUseDb, $store->getContentHandlerUseDB() );
+
+               $this->assertInstanceOf( ILoadBalancer::class, $wrapper->loadBalancer );
+               $this->assertInstanceOf( BlobStore::class, $wrapper->blobStore );
+               $this->assertInstanceOf( NameTableStore::class, $wrapper->contentModelStore );
+               $this->assertInstanceOf( NameTableStore::class, $wrapper->slotRoleStore );
+               $this->assertInstanceOf( LoggerInterface::class, $wrapper->logger );
+       }
+
+       /**
+        * @return \PHPUnit_Framework_MockObject_MockObject|ILoadBalancer
+        */
+       private function getMockLoadBalancer() {
+               return $this->getMockBuilder( ILoadBalancer::class )
+                       ->disableOriginalConstructor()->getMock();
+       }
+
+       /**
+        * @return \PHPUnit_Framework_MockObject_MockObject|ILBFactory
+        */
+       private function getMockLoadBalancerFactory() {
+               $mock = $this->getMockBuilder( ILBFactory::class )
+                       ->disableOriginalConstructor()->getMock();
+
+               $mock->method( 'getMainLB' )
+                       ->willReturnCallback( function () {
+                               return $this->getMockLoadBalancer();
+                       } );
+
+               return $mock;
+       }
+
+       /**
+        * @return \PHPUnit_Framework_MockObject_MockObject|SqlBlobStore
+        */
+       private function getMockSqlBlobStore() {
+               return $this->getMockBuilder( SqlBlobStore::class )
+                       ->disableOriginalConstructor()->getMock();
+       }
+
+       /**
+        * @return \PHPUnit_Framework_MockObject_MockObject|BlobStoreFactory
+        */
+       private function getMockBlobStoreFactory() {
+               $mock = $this->getMockBuilder( BlobStoreFactory::class )
+                       ->disableOriginalConstructor()->getMock();
+
+               $mock->method( 'newSqlBlobStore' )
+                       ->willReturnCallback( function () {
+                               return $this->getMockSqlBlobStore();
+                       } );
+
+               return $mock;
+       }
+
+       /**
+        * @return SlotRoleRegistry
+        */
+       private function getMockSlotRoleRegistry() {
+               return $this->createMock( SlotRoleRegistry::class );
+       }
+
+       /**
+        * @return NameTableStoreFactory
+        */
+       private function getNameTableStoreFactory() {
+               return new NameTableStoreFactory(
+                       $this->getMockLoadBalancerFactory(),
+                       $this->getHashWANObjectCache(),
+                       new NullLogger() );
+       }
+
+       /**
+        * @return \PHPUnit_Framework_MockObject_MockObject|CommentStore
+        */
+       private function getMockCommentStore() {
+               return $this->getMockBuilder( CommentStore::class )
+                       ->disableOriginalConstructor()->getMock();
+       }
+
+       private function getHashWANObjectCache() {
+               return new WANObjectCache( [ 'cache' => new \HashBagOStuff() ] );
+       }
+
+       /**
+        * @return \PHPUnit_Framework_MockObject_MockObject|LoggerSpi
+        */
+       private function getMockLoggerSpi() {
+               $mock = $this->getMock( LoggerSpi::class );
+
+               $mock->method( 'getLogger' )
+                       ->willReturn( new NullLogger() );
+
+               return $mock;
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/config/ConfigFactoryTest.php b/tests/phpunit/unit/includes/config/ConfigFactoryTest.php
new file mode 100644 (file)
index 0000000..a136018
--- /dev/null
@@ -0,0 +1,168 @@
+<?php
+
+use MediaWiki\MediaWikiServices;
+
+class ConfigFactoryTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @covers ConfigFactory::register
+        */
+       public function testRegister() {
+               $factory = new ConfigFactory();
+               $factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
+               $this->assertInstanceOf( GlobalVarConfig::class, $factory->makeConfig( 'unittest' ) );
+       }
+
+       /**
+        * @covers ConfigFactory::register
+        */
+       public function testRegisterInvalid() {
+               $factory = new ConfigFactory();
+               $this->setExpectedException( InvalidArgumentException::class );
+               $factory->register( 'invalid', 'Invalid callback' );
+       }
+
+       /**
+        * @covers ConfigFactory::register
+        */
+       public function testRegisterInvalidInstance() {
+               $factory = new ConfigFactory();
+               $this->setExpectedException( InvalidArgumentException::class );
+               $factory->register( 'invalidInstance', new stdClass );
+       }
+
+       /**
+        * @covers ConfigFactory::register
+        */
+       public function testRegisterInstance() {
+               $config = GlobalVarConfig::newInstance();
+               $factory = new ConfigFactory();
+               $factory->register( 'unittest', $config );
+               $this->assertSame( $config, $factory->makeConfig( 'unittest' ) );
+       }
+
+       /**
+        * @covers ConfigFactory::register
+        */
+       public function testRegisterAgain() {
+               $factory = new ConfigFactory();
+               $factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
+               $config1 = $factory->makeConfig( 'unittest' );
+
+               $factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
+               $config2 = $factory->makeConfig( 'unittest' );
+
+               $this->assertNotSame( $config1, $config2 );
+       }
+
+       /**
+        * @covers ConfigFactory::salvage
+        */
+       public function testSalvage() {
+               $oldFactory = new ConfigFactory();
+               $oldFactory->register( 'foo', 'GlobalVarConfig::newInstance' );
+               $oldFactory->register( 'bar', 'GlobalVarConfig::newInstance' );
+               $oldFactory->register( 'quux', 'GlobalVarConfig::newInstance' );
+
+               // instantiate two of the three defined configurations
+               $foo = $oldFactory->makeConfig( 'foo' );
+               $bar = $oldFactory->makeConfig( 'bar' );
+               $quux = $oldFactory->makeConfig( 'quux' );
+
+               // define new config instance
+               $newFactory = new ConfigFactory();
+               $newFactory->register( 'foo', 'GlobalVarConfig::newInstance' );
+               $newFactory->register( 'bar', function () {
+                       return new HashConfig();
+               } );
+
+               // "foo" and "quux" are defined in the old and the new factory.
+               // The old factory has instances for "foo" and "bar", but not "quux".
+               $newFactory->salvage( $oldFactory );
+
+               $newFoo = $newFactory->makeConfig( 'foo' );
+               $this->assertSame( $foo, $newFoo, 'existing instance should be salvaged' );
+
+               $newBar = $newFactory->makeConfig( 'bar' );
+               $this->assertNotSame( $bar, $newBar, 'don\'t salvage if callbacks differ' );
+
+               // the new factory doesn't have quux defined, so the quux instance should not be salvaged
+               $this->setExpectedException( ConfigException::class );
+               $newFactory->makeConfig( 'quux' );
+       }
+
+       /**
+        * @covers ConfigFactory::getConfigNames
+        */
+       public function testGetConfigNames() {
+               $factory = new ConfigFactory();
+               $factory->register( 'foo', 'GlobalVarConfig::newInstance' );
+               $factory->register( 'bar', new HashConfig() );
+
+               $this->assertEquals( [ 'foo', 'bar' ], $factory->getConfigNames() );
+       }
+
+       /**
+        * @covers ConfigFactory::makeConfig
+        */
+       public function testMakeConfigWithCallback() {
+               $factory = new ConfigFactory();
+               $factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
+
+               $conf = $factory->makeConfig( 'unittest' );
+               $this->assertInstanceOf( Config::class, $conf );
+               $this->assertSame( $conf, $factory->makeConfig( 'unittest' ) );
+       }
+
+       /**
+        * @covers ConfigFactory::makeConfig
+        */
+       public function testMakeConfigWithObject() {
+               $factory = new ConfigFactory();
+               $conf = new HashConfig();
+               $factory->register( 'test', $conf );
+               $this->assertSame( $conf, $factory->makeConfig( 'test' ) );
+       }
+
+       /**
+        * @covers ConfigFactory::makeConfig
+        */
+       public function testMakeConfigFallback() {
+               $factory = new ConfigFactory();
+               $factory->register( '*', 'GlobalVarConfig::newInstance' );
+               $conf = $factory->makeConfig( 'unittest' );
+               $this->assertInstanceOf( Config::class, $conf );
+       }
+
+       /**
+        * @covers ConfigFactory::makeConfig
+        */
+       public function testMakeConfigWithNoBuilders() {
+               $factory = new ConfigFactory();
+               $this->setExpectedException( ConfigException::class );
+               $factory->makeConfig( 'nobuilderregistered' );
+       }
+
+       /**
+        * @covers ConfigFactory::makeConfig
+        */
+       public function testMakeConfigWithInvalidCallback() {
+               $factory = new ConfigFactory();
+               $factory->register( 'unittest', function () {
+                       return true; // Not a Config object
+               } );
+               $this->setExpectedException( UnexpectedValueException::class );
+               $factory->makeConfig( 'unittest' );
+       }
+
+       /**
+        * @covers ConfigFactory::getDefaultInstance
+        */
+       public function testGetDefaultInstance() {
+               // NOTE: the global config factory returned here has been overwritten
+               // for operation in test mode. It may not reflect LocalSettings.
+               $factory = MediaWikiServices::getInstance()->getConfigFactory();
+               $this->assertInstanceOf( Config::class, $factory->makeConfig( 'main' ) );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/media/GIFMetadataExtractorTest.php b/tests/phpunit/unit/includes/media/GIFMetadataExtractorTest.php
new file mode 100644 (file)
index 0000000..10c450d
--- /dev/null
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * @group Media
+ */
+class GIFMetadataExtractorTest extends \MediaWikiUnitTestCase {
+
+       protected function setUp() {
+               parent::setUp();
+
+               $this->mediaPath = __DIR__ . '/../../../data/media/';
+       }
+
+       /**
+        * Put in a file, and see if the metadata coming out is as expected.
+        * @param string $filename
+        * @param array $expected The extracted metadata.
+        * @dataProvider provideGetMetadata
+        * @covers GIFMetadataExtractor::getMetadata
+        */
+       public function testGetMetadata( $filename, $expected ) {
+               $actual = GIFMetadataExtractor::getMetadata( $this->mediaPath . $filename );
+               $this->assertEquals( $expected, $actual );
+       }
+
+       public static function provideGetMetadata() {
+               $xmpNugget = <<<EOF
+<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
+<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 7.30'>
+<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
+
+ <rdf:Description rdf:about=''
+  xmlns:Iptc4xmpCore='http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/'>
+  <Iptc4xmpCore:Location>The interwebs</Iptc4xmpCore:Location>
+ </rdf:Description>
+
+ <rdf:Description rdf:about=''
+  xmlns:tiff='http://ns.adobe.com/tiff/1.0/'>
+  <tiff:Artist>Bawolff</tiff:Artist>
+  <tiff:ImageDescription>
+   <rdf:Alt>
+    <rdf:li xml:lang='x-default'>A file to test GIF</rdf:li>
+   </rdf:Alt>
+  </tiff:ImageDescription>
+ </rdf:Description>
+</rdf:RDF>
+</x:xmpmeta>
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+<?xpacket end='w'?>
+EOF;
+               $xmpNugget = str_replace( "\r", '', $xmpNugget ); // Windows compat
+
+               return [
+                       [
+                               'nonanimated.gif',
+                               [
+                                       'comment' => [ 'GIF test file ⁕ Created with GIMP' ],
+                                       'duration' => 0.1,
+                                       'frameCount' => 1,
+                                       'looped' => false,
+                                       'xmp' => '',
+                               ]
+                       ],
+                       [
+                               'animated.gif',
+                               [
+                                       'comment' => [ 'GIF test file . Created with GIMP' ],
+                                       'duration' => 2.4,
+                                       'frameCount' => 4,
+                                       'looped' => true,
+                                       'xmp' => '',
+                               ]
+                       ],
+
+                       [
+                               'animated-xmp.gif',
+                               [
+                                       'xmp' => $xmpNugget,
+                                       'duration' => 2.4,
+                                       'frameCount' => 4,
+                                       'looped' => true,
+                                       'comment' => [ 'GIƒ·test·file' ],
+                               ]
+                       ],
+               ];
+       }
+}
diff --git a/tests/phpunit/unit/includes/media/SVGMetadataExtractorTest.php b/tests/phpunit/unit/includes/media/SVGMetadataExtractorTest.php
new file mode 100644 (file)
index 0000000..30d1008
--- /dev/null
@@ -0,0 +1,201 @@
+<?php
+
+/**
+ * @group Media
+ * @covers SVGMetadataExtractor
+ */
+class SVGMetadataExtractorTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @dataProvider provideSvgFiles
+        */
+       public function testGetMetadata( $infile, $expected ) {
+               $this->assertMetadata( $infile, $expected );
+       }
+
+       /**
+        * @dataProvider provideSvgFilesWithXMLMetadata
+        */
+       public function testGetXMLMetadata( $infile, $expected ) {
+               $r = new XMLReader();
+               $this->assertMetadata( $infile, $expected );
+       }
+
+       /**
+        * @dataProvider provideSvgUnits
+        */
+       public function testScaleSVGUnit( $inUnit, $expected ) {
+               $this->assertEquals(
+                       $expected,
+                       SVGReader::scaleSVGUnit( $inUnit ),
+                       'SVG unit conversion and scaling failure'
+               );
+       }
+
+       function assertMetadata( $infile, $expected ) {
+               try {
+                       $data = SVGMetadataExtractor::getMetadata( $infile );
+                       $this->assertEquals( $expected, $data, 'SVG metadata extraction test' );
+               } catch ( MWException $e ) {
+                       if ( $expected === false ) {
+                               $this->assertTrue( true, 'SVG metadata extracted test (expected failure)' );
+                       } else {
+                               throw $e;
+                       }
+               }
+       }
+
+       public static function provideSvgFiles() {
+               $base = __DIR__ . '/../../../data/media';
+
+               return [
+                       [
+                               "$base/Wikimedia-logo.svg",
+                               [
+                                       'width' => 1024,
+                                       'height' => 1024,
+                                       'originalWidth' => '1024',
+                                       'originalHeight' => '1024',
+                                       'translations' => [],
+                               ]
+                       ],
+                       [
+                               "$base/QA_icon.svg",
+                               [
+                                       'width' => 60,
+                                       'height' => 60,
+                                       'originalWidth' => '60',
+                                       'originalHeight' => '60',
+                                       'translations' => [],
+                               ]
+                       ],
+                       [
+                               "$base/Gtk-media-play-ltr.svg",
+                               [
+                                       'width' => 60,
+                                       'height' => 60,
+                                       'originalWidth' => '60.0000000',
+                                       'originalHeight' => '60.0000000',
+                                       'translations' => [],
+                               ]
+                       ],
+                       [
+                               "$base/Toll_Texas_1.svg",
+                               // This file triggered T33719, needs entity expansion in the xmlns checks
+                               [
+                                       'width' => 385,
+                                       'height' => 385,
+                                       'originalWidth' => '385',
+                                       'originalHeight' => '385.0004883',
+                                       'translations' => [],
+                               ]
+                       ],
+                       [
+                               "$base/Tux.svg",
+                               [
+                                       'width' => 512,
+                                       'height' => 594,
+                                       'originalWidth' => '100%',
+                                       'originalHeight' => '100%',
+                                       'title' => 'Tux',
+                                       'translations' => [],
+                                       'description' => 'For more information see: http://commons.wikimedia.org/wiki/Image:Tux.svg',
+                               ]
+                       ],
+                       [
+                               "$base/Speech_bubbles.svg",
+                               [
+                                       'width' => 627,
+                                       'height' => 461,
+                                       'originalWidth' => '17.7cm',
+                                       'originalHeight' => '13cm',
+                                       'translations' => [
+                                               'de' => SVGReader::LANG_FULL_MATCH,
+                                               'fr' => SVGReader::LANG_FULL_MATCH,
+                                               'nl' => SVGReader::LANG_FULL_MATCH,
+                                               'tlh-ca' => SVGReader::LANG_FULL_MATCH,
+                                               'tlh' => SVGReader::LANG_PREFIX_MATCH
+                                       ],
+                               ]
+                       ],
+                       [
+                               "$base/Soccer_ball_animated.svg",
+                               [
+                                       'width' => 150,
+                                       'height' => 150,
+                                       'originalWidth' => '150',
+                                       'originalHeight' => '150',
+                                       'animated' => true,
+                                       'translations' => []
+                               ],
+                       ],
+                       [
+                               "$base/comma_separated_viewbox.svg",
+                               [
+                                       'width' => 512,
+                                       'height' => 594,
+                                       'originalWidth' => '100%',
+                                       'originalHeight' => '100%',
+                                       'translations' => []
+                               ],
+                       ],
+               ];
+       }
+
+       public static function provideSvgFilesWithXMLMetadata() {
+               $base = __DIR__ . '/../../../data/media';
+               // phpcs:disable Generic.Files.LineLength
+               $metadata = '<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+      <ns4:Work xmlns:ns4="http://creativecommons.org/ns#" rdf:about="">
+        <ns5:format xmlns:ns5="http://purl.org/dc/elements/1.1/">image/svg+xml</ns5:format>
+        <ns5:type xmlns:ns5="http://purl.org/dc/elements/1.1/" rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+      </ns4:Work>
+    </rdf:RDF>';
+               // phpcs:enable
+
+               $metadata = str_replace( "\r", '', $metadata ); // Windows compat
+               return [
+                       [
+                               "$base/US_states_by_total_state_tax_revenue.svg",
+                               [
+                                       'height' => 593,
+                                       'metadata' => $metadata,
+                                       'width' => 959,
+                                       'originalWidth' => '958.69',
+                                       'originalHeight' => '592.78998',
+                                       'translations' => [],
+                               ]
+                       ],
+               ];
+       }
+
+       public static function provideSvgUnits() {
+               return [
+                       [ '1' , 1 ],
+                       [ '1.1' , 1.1 ],
+                       [ '0.1' , 0.1 ],
+                       [ '.1' , 0.1 ],
+                       [ '1e2' , 100 ],
+                       [ '1E2' , 100 ],
+                       [ '+1' , 1 ],
+                       [ '-1' , -1 ],
+                       [ '-1.1' , -1.1 ],
+                       [ '1e+2' , 100 ],
+                       [ '1e-2' , 0.01 ],
+                       [ '10px' , 10 ],
+                       [ '10pt' , 10 * 1.25 ],
+                       [ '10pc' , 10 * 15 ],
+                       [ '10mm' , 10 * 3.543307 ],
+                       [ '10cm' , 10 * 35.43307 ],
+                       [ '10in' , 10 * 90 ],
+                       [ '10em' , 10 * 16 ],
+                       [ '10ex' , 10 * 12 ],
+                       [ '10%' , 51.2 ],
+                       [ '10 px' , 10 ],
+                       // Invalid values
+                       [ '1e1.1', 10 ],
+                       [ '10bp', 10 ],
+                       [ 'p10', null ],
+               ];
+       }
+}
diff --git a/tests/phpunit/unit/includes/site/CachingSiteStoreTest.php b/tests/phpunit/unit/includes/site/CachingSiteStoreTest.php
new file mode 100644 (file)
index 0000000..92ed1f5
--- /dev/null
@@ -0,0 +1,167 @@
+<?php
+
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @since 1.25
+ *
+ * @ingroup Site
+ * @ingroup Test
+ *
+ * @group Site
+ * @group Database
+ *
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class CachingSiteStoreTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @covers CachingSiteStore::getSites
+        */
+       public function testGetSites() {
+               $testSites = TestSites::getSites();
+
+               $store = new CachingSiteStore(
+                       $this->getHashSiteStore( $testSites ),
+                       ObjectCache::getLocalClusterInstance()
+               );
+
+               $sites = $store->getSites();
+
+               $this->assertInstanceOf( SiteList::class, $sites );
+
+               /**
+                * @var Site $site
+                */
+               foreach ( $sites as $site ) {
+                       $this->assertInstanceOf( Site::class, $site );
+               }
+
+               foreach ( $testSites as $site ) {
+                       if ( $site->getGlobalId() !== null ) {
+                               $this->assertTrue( $sites->hasSite( $site->getGlobalId() ) );
+                       }
+               }
+       }
+
+       /**
+        * @covers CachingSiteStore::saveSites
+        */
+       public function testSaveSites() {
+               $store = new CachingSiteStore(
+                       new HashSiteStore(), ObjectCache::getLocalClusterInstance()
+               );
+
+               $sites = [];
+
+               $site = new Site();
+               $site->setGlobalId( 'ertrywuutr' );
+               $site->setLanguageCode( 'en' );
+               $sites[] = $site;
+
+               $site = new MediaWikiSite();
+               $site->setGlobalId( 'sdfhxujgkfpth' );
+               $site->setLanguageCode( 'nl' );
+               $sites[] = $site;
+
+               $this->assertTrue( $store->saveSites( $sites ) );
+
+               $site = $store->getSite( 'ertrywuutr' );
+               $this->assertInstanceOf( Site::class, $site );
+               $this->assertEquals( 'en', $site->getLanguageCode() );
+
+               $site = $store->getSite( 'sdfhxujgkfpth' );
+               $this->assertInstanceOf( Site::class, $site );
+               $this->assertEquals( 'nl', $site->getLanguageCode() );
+       }
+
+       /**
+        * @covers CachingSiteStore::reset
+        */
+       public function testReset() {
+               $dbSiteStore = $this->getMockBuilder( SiteStore::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $dbSiteStore->expects( $this->any() )
+                       ->method( 'getSite' )
+                       ->will( $this->returnValue( $this->getTestSite() ) );
+
+               $dbSiteStore->expects( $this->any() )
+                       ->method( 'getSites' )
+                       ->will( $this->returnCallback( function () {
+                               $siteList = new SiteList();
+                               $siteList->setSite( $this->getTestSite() );
+
+                               return $siteList;
+                       } ) );
+
+               $store = new CachingSiteStore( $dbSiteStore, ObjectCache::getLocalClusterInstance() );
+
+               // initialize internal cache
+               $this->assertGreaterThan( 0, $store->getSites()->count(), 'count sites' );
+
+               $store->getSite( 'enwiki' )->setLanguageCode( 'en-ca' );
+
+               // sanity check: $store should have the new language code for 'enwiki'
+               $this->assertEquals( 'en-ca', $store->getSite( 'enwiki' )->getLanguageCode(), 'sanity check' );
+
+               // purge cache
+               $store->reset();
+
+               // the internal cache of $store should be updated, and now pulling
+               // the site from the 'fallback' DBSiteStore with the original language code.
+               $this->assertEquals( 'en', $store->getSite( 'enwiki' )->getLanguageCode(), 'reset' );
+       }
+
+       public function getTestSite() {
+               $enwiki = new MediaWikiSite();
+               $enwiki->setGlobalId( 'enwiki' );
+               $enwiki->setLanguageCode( 'en' );
+
+               return $enwiki;
+       }
+
+       /**
+        * @covers CachingSiteStore::clear
+        */
+       public function testClear() {
+               $store = new CachingSiteStore(
+                       new HashSiteStore(), ObjectCache::getLocalClusterInstance()
+               );
+               $this->assertTrue( $store->clear() );
+
+               $site = $store->getSite( 'enwiki' );
+               $this->assertNull( $site );
+
+               $sites = $store->getSites();
+               $this->assertEquals( 0, $sites->count() );
+       }
+
+       /**
+        * @param Site[] $sites
+        *
+        * @return SiteStore
+        */
+       private function getHashSiteStore( array $sites ) {
+               $siteStore = new HashSiteStore();
+               $siteStore->saveSites( $sites );
+
+               return $siteStore;
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/site/HashSiteStoreTest.php b/tests/phpunit/unit/includes/site/HashSiteStoreTest.php
new file mode 100644 (file)
index 0000000..8b0d4e0
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @since 1.25
+ *
+ * @ingroup Site
+ * @group Site
+ *
+ * @author Katie Filbert < aude.wiki@gmail.com >
+ */
+class HashSiteStoreTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @covers HashSiteStore::getSites
+        */
+       public function testGetSites() {
+               $expectedSites = [];
+
+               foreach ( TestSites::getSites() as $testSite ) {
+                       $siteId = $testSite->getGlobalId();
+                       $expectedSites[$siteId] = $testSite;
+               }
+
+               $siteStore = new HashSiteStore( $expectedSites );
+
+               $this->assertEquals( new SiteList( $expectedSites ), $siteStore->getSites() );
+       }
+
+       /**
+        * @covers HashSiteStore::saveSite
+        * @covers HashSiteStore::getSite
+        */
+       public function testSaveSite() {
+               $store = new HashSiteStore();
+
+               $site = new Site();
+               $site->setGlobalId( 'dewiki' );
+
+               $this->assertCount( 0, $store->getSites(), '0 sites in store' );
+
+               $store->saveSite( $site );
+
+               $this->assertCount( 1, $store->getSites(), 'Store has 1 sites' );
+               $this->assertEquals( $site, $store->getSite( 'dewiki' ), 'Store has dewiki' );
+       }
+
+       /**
+        * @covers HashSiteStore::saveSites
+        */
+       public function testSaveSites() {
+               $store = new HashSiteStore();
+
+               $sites = [];
+
+               $site = new Site();
+               $site->setGlobalId( 'enwiki' );
+               $site->setLanguageCode( 'en' );
+               $sites[] = $site;
+
+               $site = new MediaWikiSite();
+               $site->setGlobalId( 'eswiki' );
+               $site->setLanguageCode( 'es' );
+               $sites[] = $site;
+
+               $this->assertCount( 0, $store->getSites(), '0 sites in store' );
+
+               $store->saveSites( $sites );
+
+               $this->assertCount( 2, $store->getSites(), 'Store has 2 sites' );
+               $this->assertTrue( $store->getSites()->hasSite( 'enwiki' ), 'Store has enwiki' );
+               $this->assertTrue( $store->getSites()->hasSite( 'eswiki' ), 'Store has eswiki' );
+       }
+
+       /**
+        * @covers HashSiteStore::clear
+        */
+       public function testClear() {
+               $store = new HashSiteStore();
+
+               $site = new Site();
+               $site->setGlobalId( 'arwiki' );
+               $store->saveSite( $site );
+
+               $this->assertCount( 1, $store->getSites(), '1 site in store' );
+
+               $store->clear();
+               $this->assertCount( 0, $store->getSites(), '0 sites in store' );
+       }
+}