diff options
Diffstat (limited to 'www/wiki/tests/phpunit/includes/config')
5 files changed, 988 insertions, 0 deletions
diff --git a/www/wiki/tests/phpunit/includes/config/ConfigFactoryTest.php b/www/wiki/tests/phpunit/includes/config/ConfigFactoryTest.php new file mode 100644 index 00000000..ea747afa --- /dev/null +++ b/www/wiki/tests/phpunit/includes/config/ConfigFactoryTest.php @@ -0,0 +1,168 @@ +<?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/www/wiki/tests/phpunit/includes/config/EtcdConfigTest.php b/www/wiki/tests/phpunit/includes/config/EtcdConfigTest.php new file mode 100644 index 00000000..3eecf827 --- /dev/null +++ b/www/wiki/tests/phpunit/includes/config/EtcdConfigTest.php @@ -0,0 +1,621 @@ +<?php + +use Wikimedia\TestingAccessWrapper; + +class EtcdConfigTest extends PHPUnit\Framework\TestCase { + + use MediaWikiCoversValidator; + use PHPUnit4And6Compat; + + private function createConfigMock( array $options = [] ) { + return $this->getMockBuilder( EtcdConfig::class ) + ->setConstructorArgs( [ $options + [ + 'host' => 'etcd-tcp.example.net', + 'directory' => '/', + 'timeout' => 0.1, + ] ] ) + ->setMethods( [ 'fetchAllFromEtcd' ] ) + ->getMock(); + } + + private static function createEtcdResponse( array $response ) { + $baseResponse = [ + 'config' => null, + 'error' => null, + 'retry' => false, + 'modifiedIndex' => 0, + ]; + return array_merge( $baseResponse, $response ); + } + + private function createSimpleConfigMock( array $config, $index = 0 ) { + $mock = $this->createConfigMock(); + $mock->expects( $this->once() )->method( 'fetchAllFromEtcd' ) + ->willReturn( self::createEtcdResponse( [ + 'config' => $config, + 'modifiedIndex' => $index, + ] ) ); + return $mock; + } + + /** + * @covers EtcdConfig::has + */ + public function testHasKnown() { + $config = $this->createSimpleConfigMock( [ + 'known' => 'value' + ] ); + $this->assertSame( true, $config->has( 'known' ) ); + } + + /** + * @covers EtcdConfig::__construct + * @covers EtcdConfig::get + */ + public function testGetKnown() { + $config = $this->createSimpleConfigMock( [ + 'known' => 'value' + ] ); + $this->assertSame( 'value', $config->get( 'known' ) ); + } + + /** + * @covers EtcdConfig::has + */ + public function testHasUnknown() { + $config = $this->createSimpleConfigMock( [ + 'known' => 'value' + ] ); + $this->assertSame( false, $config->has( 'unknown' ) ); + } + + /** + * @covers EtcdConfig::get + */ + public function testGetUnknown() { + $config = $this->createSimpleConfigMock( [ + 'known' => 'value' + ] ); + $this->setExpectedException( ConfigException::class ); + $config->get( 'unknown' ); + } + + /** + * @covers EtcdConfig::getModifiedIndex + */ + public function testGetModifiedIndex() { + $config = $this->createSimpleConfigMock( + [ 'some' => 'value' ], + 123 + ); + $this->assertSame( 123, $config->getModifiedIndex() ); + } + + /** + * @covers EtcdConfig::__construct + */ + public function testConstructCacheObj() { + $cache = $this->getMockBuilder( HashBagOStuff::class ) + ->setMethods( [ 'get' ] ) + ->getMock(); + $cache->expects( $this->once() )->method( 'get' ) + ->willReturn( [ + 'config' => [ 'known' => 'from-cache' ], + 'expires' => INF, + 'modifiedIndex' => 123 + ] ); + $config = $this->createConfigMock( [ 'cache' => $cache ] ); + + $this->assertSame( 'from-cache', $config->get( 'known' ) ); + } + + /** + * @covers EtcdConfig::__construct + */ + public function testConstructCacheSpec() { + $config = $this->createConfigMock( [ 'cache' => [ + 'class' => HashBagOStuff::class + ] ] ); + $config->expects( $this->once() )->method( 'fetchAllFromEtcd' ) + ->willReturn( self::createEtcdResponse( + [ 'config' => [ 'known' => 'from-fetch' ], ] ) ); + + $this->assertSame( 'from-fetch', $config->get( 'known' ) ); + } + + /** + * Test matrix + * + * - [x] Cache miss + * Result: Fetched value + * > cache miss | gets lock | backend succeeds + * + * - [x] Cache miss with backend error + * Result: ConfigException + * > cache miss | gets lock | backend error (no retry) + * + * - [x] Cache hit after retry + * Result: Cached value (populated by process holding lock) + * > cache miss | no lock | cache retry + * + * - [x] Cache hit + * Result: Cached value + * > cache hit + * + * - [x] Process cache hit + * Result: Cached value + * > process cache hit + * + * - [x] Cache expired + * Result: Fetched value + * > cache expired | gets lock | backend succeeds + * + * - [x] Cache expired with backend failure + * Result: Cached value (stale) + * > cache expired | gets lock | backend fails (allows retry) + * + * - [x] Cache expired and no lock + * Result: Cached value (stale) + * > cache expired | no lock + * + * Other notable scenarios: + * + * - [ ] Cache miss with backend retry + * Result: Fetched value + * > cache expired | gets lock | backend failure (allows retry) + */ + + /** + * @covers EtcdConfig::load + */ + public function testLoadCacheMiss() { + // Create cache mock + $cache = $this->getMockBuilder( HashBagOStuff::class ) + ->setMethods( [ 'get', 'lock' ] ) + ->getMock(); + // .. misses cache + $cache->expects( $this->once() )->method( 'get' ) + ->willReturn( false ); + // .. gets lock + $cache->expects( $this->once() )->method( 'lock' ) + ->willReturn( true ); + + // Create config mock + $mock = $this->createConfigMock( [ + 'cache' => $cache, + ] ); + $mock->expects( $this->once() )->method( 'fetchAllFromEtcd' ) + ->willReturn( + self::createEtcdResponse( [ 'config' => [ 'known' => 'from-fetch' ] ] ) ); + + $this->assertSame( 'from-fetch', $mock->get( 'known' ) ); + } + + /** + * @covers EtcdConfig::load + */ + public function testLoadCacheMissBackendError() { + // Create cache mock + $cache = $this->getMockBuilder( HashBagOStuff::class ) + ->setMethods( [ 'get', 'lock' ] ) + ->getMock(); + // .. misses cache + $cache->expects( $this->once() )->method( 'get' ) + ->willReturn( false ); + // .. gets lock + $cache->expects( $this->once() )->method( 'lock' ) + ->willReturn( true ); + + // Create config mock + $mock = $this->createConfigMock( [ + 'cache' => $cache, + ] ); + $mock->expects( $this->once() )->method( 'fetchAllFromEtcd' ) + ->willReturn( self::createEtcdResponse( [ 'error' => 'Fake error', ] ) ); + + $this->setExpectedException( ConfigException::class ); + $mock->get( 'key' ); + } + + /** + * @covers EtcdConfig::load + */ + public function testLoadCacheMissWithoutLock() { + // Create cache mock + $cache = $this->getMockBuilder( HashBagOStuff::class ) + ->setMethods( [ 'get', 'lock' ] ) + ->getMock(); + $cache->expects( $this->exactly( 2 ) )->method( 'get' ) + ->will( $this->onConsecutiveCalls( + // .. misses cache first time + false, + // .. hits cache on retry + [ + 'config' => [ 'known' => 'from-cache' ], + 'expires' => INF, + 'modifiedIndex' => 123 + ] + ) ); + // .. misses lock + $cache->expects( $this->once() )->method( 'lock' ) + ->willReturn( false ); + + // Create config mock + $mock = $this->createConfigMock( [ + 'cache' => $cache, + ] ); + $mock->expects( $this->never() )->method( 'fetchAllFromEtcd' ); + + $this->assertSame( 'from-cache', $mock->get( 'known' ) ); + } + + /** + * @covers EtcdConfig::load + */ + public function testLoadCacheHit() { + // Create cache mock + $cache = $this->getMockBuilder( HashBagOStuff::class ) + ->setMethods( [ 'get', 'lock' ] ) + ->getMock(); + $cache->expects( $this->once() )->method( 'get' ) + // .. hits cache + ->willReturn( [ + 'config' => [ 'known' => 'from-cache' ], + 'expires' => INF, + 'modifiedIndex' => 0, + ] ); + $cache->expects( $this->never() )->method( 'lock' ); + + // Create config mock + $mock = $this->createConfigMock( [ + 'cache' => $cache, + ] ); + $mock->expects( $this->never() )->method( 'fetchAllFromEtcd' ); + + $this->assertSame( 'from-cache', $mock->get( 'known' ) ); + } + + /** + * @covers EtcdConfig::load + */ + public function testLoadProcessCacheHit() { + // Create cache mock + $cache = $this->getMockBuilder( HashBagOStuff::class ) + ->setMethods( [ 'get', 'lock' ] ) + ->getMock(); + $cache->expects( $this->once() )->method( 'get' ) + // .. hits cache + ->willReturn( [ + 'config' => [ 'known' => 'from-cache' ], + 'expires' => INF, + 'modifiedIndex' => 0, + ] ); + $cache->expects( $this->never() )->method( 'lock' ); + + // Create config mock + $mock = $this->createConfigMock( [ + 'cache' => $cache, + ] ); + $mock->expects( $this->never() )->method( 'fetchAllFromEtcd' ); + + $this->assertSame( 'from-cache', $mock->get( 'known' ), 'Cache hit' ); + $this->assertSame( 'from-cache', $mock->get( 'known' ), 'Process cache hit' ); + } + + /** + * @covers EtcdConfig::load + */ + public function testLoadCacheExpiredLockFetchSucceeded() { + // Create cache mock + $cache = $this->getMockBuilder( HashBagOStuff::class ) + ->setMethods( [ 'get', 'lock' ] ) + ->getMock(); + $cache->expects( $this->once() )->method( 'get' )->willReturn( + // .. stale cache + [ + 'config' => [ 'known' => 'from-cache-expired' ], + 'expires' => -INF, + 'modifiedIndex' => 0, + ] + ); + // .. gets lock + $cache->expects( $this->once() )->method( 'lock' ) + ->willReturn( true ); + + // Create config mock + $mock = $this->createConfigMock( [ + 'cache' => $cache, + ] ); + $mock->expects( $this->once() )->method( 'fetchAllFromEtcd' ) + ->willReturn( self::createEtcdResponse( [ 'config' => [ 'known' => 'from-fetch' ] ] ) ); + + $this->assertSame( 'from-fetch', $mock->get( 'known' ) ); + } + + /** + * @covers EtcdConfig::load + */ + public function testLoadCacheExpiredLockFetchFails() { + // Create cache mock + $cache = $this->getMockBuilder( HashBagOStuff::class ) + ->setMethods( [ 'get', 'lock' ] ) + ->getMock(); + $cache->expects( $this->once() )->method( 'get' )->willReturn( + // .. stale cache + [ + 'config' => [ 'known' => 'from-cache-expired' ], + 'expires' => -INF, + 'modifiedIndex' => 0, + ] + ); + // .. gets lock + $cache->expects( $this->once() )->method( 'lock' ) + ->willReturn( true ); + + // Create config mock + $mock = $this->createConfigMock( [ + 'cache' => $cache, + ] ); + $mock->expects( $this->once() )->method( 'fetchAllFromEtcd' ) + ->willReturn( self::createEtcdResponse( [ 'error' => 'Fake failure', 'retry' => true ] ) ); + + $this->assertSame( 'from-cache-expired', $mock->get( 'known' ) ); + } + + /** + * @covers EtcdConfig::load + */ + public function testLoadCacheExpiredNoLock() { + // Create cache mock + $cache = $this->getMockBuilder( HashBagOStuff::class ) + ->setMethods( [ 'get', 'lock' ] ) + ->getMock(); + $cache->expects( $this->once() )->method( 'get' ) + // .. hits cache (expired value) + ->willReturn( [ + 'config' => [ 'known' => 'from-cache-expired' ], + 'expires' => -INF, + 'modifiedIndex' => 0, + ] ); + // .. misses lock + $cache->expects( $this->once() )->method( 'lock' ) + ->willReturn( false ); + + // Create config mock + $mock = $this->createConfigMock( [ + 'cache' => $cache, + ] ); + $mock->expects( $this->never() )->method( 'fetchAllFromEtcd' ); + + $this->assertSame( 'from-cache-expired', $mock->get( 'known' ) ); + } + + public static function provideFetchFromServer() { + return [ + '200 OK - Success' => [ + 'http' => [ + 'code' => 200, + 'reason' => 'OK', + 'headers' => [], + 'body' => json_encode( [ 'node' => [ 'nodes' => [ + [ + 'key' => '/example/foo', + 'value' => json_encode( [ 'val' => true ] ), + 'modifiedIndex' => 123 + ], + ] ] ] ), + 'error' => '', + ], + 'expect' => self::createEtcdResponse( [ + 'config' => [ 'foo' => true ], // data + 'modifiedIndex' => 123 + ] ), + ], + '200 OK - Empty dir' => [ + 'http' => [ + 'code' => 200, + 'reason' => 'OK', + 'headers' => [], + 'body' => json_encode( [ 'node' => [ 'nodes' => [ + [ + 'key' => '/example/foo', + 'value' => json_encode( [ 'val' => true ] ), + 'modifiedIndex' => 123 + ], + [ + 'key' => '/example/sub', + 'dir' => true, + 'modifiedIndex' => 234, + 'nodes' => [], + ], + [ + 'key' => '/example/bar', + 'value' => json_encode( [ 'val' => false ] ), + 'modifiedIndex' => 125 + ], + ] ] ] ), + 'error' => '', + ], + 'expect' => self::createEtcdResponse( [ + 'config' => [ 'foo' => true, 'bar' => false ], // data + 'modifiedIndex' => 125 // largest modified index + ] ), + ], + '200 OK - Recursive' => [ + 'http' => [ + 'code' => 200, + 'reason' => 'OK', + 'headers' => [], + 'body' => json_encode( [ 'node' => [ 'nodes' => [ + [ + 'key' => '/example/a', + 'dir' => true, + 'modifiedIndex' => 124, + 'nodes' => [ + [ + 'key' => 'b', + 'value' => json_encode( [ 'val' => true ] ), + 'modifiedIndex' => 123, + + ], + [ + 'key' => 'c', + 'value' => json_encode( [ 'val' => false ] ), + 'modifiedIndex' => 123, + ], + ], + ], + ] ] ] ), + 'error' => '', + ], + 'expect' => self::createEtcdResponse( [ + 'config' => [ 'a/b' => true, 'a/c' => false ], // data + 'modifiedIndex' => 123 // largest modified index + ] ), + ], + '200 OK - Missing nodes at second level' => [ + 'http' => [ + 'code' => 200, + 'reason' => 'OK', + 'headers' => [], + 'body' => json_encode( [ 'node' => [ 'nodes' => [ + [ + 'key' => '/example/a', + 'dir' => true, + 'modifiedIndex' => 0, + ], + ] ] ] ), + 'error' => '', + ], + 'expect' => self::createEtcdResponse( [ + 'error' => "Unexpected JSON response in dir 'a'; missing 'nodes' list.", + ] ), + ], + '200 OK - Directory with non-array "nodes" key' => [ + 'http' => [ + 'code' => 200, + 'reason' => 'OK', + 'headers' => [], + 'body' => json_encode( [ 'node' => [ 'nodes' => [ + [ + 'key' => '/example/a', + 'dir' => true, + 'nodes' => 'not an array' + ], + ] ] ] ), + 'error' => '', + ], + 'expect' => self::createEtcdResponse( [ + 'error' => "Unexpected JSON response in dir 'a'; 'nodes' is not an array.", + ] ), + ], + '200 OK - Correctly encoded garbage response' => [ + 'http' => [ + 'code' => 200, + 'reason' => 'OK', + 'headers' => [], + 'body' => json_encode( [ 'foo' => 'bar' ] ), + 'error' => '', + ], + 'expect' => self::createEtcdResponse( [ + 'error' => "Unexpected JSON response: Missing or invalid node at top level.", + ] ), + ], + '200 OK - Bad value' => [ + 'http' => [ + 'code' => 200, + 'reason' => 'OK', + 'headers' => [], + 'body' => json_encode( [ 'node' => [ 'nodes' => [ + [ + 'key' => '/example/foo', + 'value' => ';"broken{value', + 'modifiedIndex' => 123, + ] + ] ] ] ), + 'error' => '', + ], + 'expect' => self::createEtcdResponse( [ + 'error' => "Failed to parse value for 'foo'.", + ] ), + ], + '200 OK - Empty node list' => [ + 'http' => [ + 'code' => 200, + 'reason' => 'OK', + 'headers' => [], + 'body' => '{"node":{"nodes":[], "modifiedIndex": 12 }}', + 'error' => '', + ], + 'expect' => self::createEtcdResponse( [ + 'config' => [], // data + ] ), + ], + '200 OK - Invalid JSON' => [ + 'http' => [ + 'code' => 200, + 'reason' => 'OK', + 'headers' => [ 'content-length' => 0 ], + 'body' => '', + 'error' => '(curl error: no status set)', + ], + 'expect' => self::createEtcdResponse( [ + 'error' => "Error unserializing JSON response.", + ] ), + ], + '404 Not Found' => [ + 'http' => [ + 'code' => 404, + 'reason' => 'Not Found', + 'headers' => [ 'content-length' => 0 ], + 'body' => '', + 'error' => '', + ], + 'expect' => self::createEtcdResponse( [ + 'error' => 'HTTP 404 (Not Found)', + ] ), + ], + '400 Bad Request - custom error' => [ + 'http' => [ + 'code' => 400, + 'reason' => 'Bad Request', + 'headers' => [ 'content-length' => 0 ], + 'body' => '', + 'error' => 'No good reason', + ], + 'expect' => self::createEtcdResponse( [ + 'error' => 'No good reason', + 'retry' => true, // retry + ] ), + ], + ]; + } + + /** + * @covers EtcdConfig::fetchAllFromEtcdServer + * @covers EtcdConfig::unserialize + * @covers EtcdConfig::parseResponse + * @covers EtcdConfig::parseDirectory + * @covers EtcdConfigParseError + * @dataProvider provideFetchFromServer + */ + public function testFetchFromServer( array $httpResponse, array $expected ) { + $http = $this->getMockBuilder( MultiHttpClient::class ) + ->disableOriginalConstructor() + ->getMock(); + $http->expects( $this->once() )->method( 'run' ) + ->willReturn( array_values( $httpResponse ) ); + + $conf = $this->getMockBuilder( EtcdConfig::class ) + ->disableOriginalConstructor() + ->getMock(); + // Access for protected member and method + $conf = TestingAccessWrapper::newFromObject( $conf ); + $conf->http = $http; + + $this->assertSame( + $expected, + $conf->fetchAllFromEtcdServer( 'etcd-tcp.example.net' ) + ); + } +} diff --git a/www/wiki/tests/phpunit/includes/config/GlobalVarConfigTest.php b/www/wiki/tests/phpunit/includes/config/GlobalVarConfigTest.php new file mode 100644 index 00000000..db5f73d4 --- /dev/null +++ b/www/wiki/tests/phpunit/includes/config/GlobalVarConfigTest.php @@ -0,0 +1,97 @@ +<?php + +class GlobalVarConfigTest extends MediaWikiTestCase { + + /** + * @covers GlobalVarConfig::newInstance + */ + public function testNewInstance() { + $config = GlobalVarConfig::newInstance(); + $this->assertInstanceOf( GlobalVarConfig::class, $config ); + $this->maybeStashGlobal( 'wgBaz' ); + $GLOBALS['wgBaz'] = 'somevalue'; + // Check prefix is set to 'wg' + $this->assertEquals( 'somevalue', $config->get( 'Baz' ) ); + } + + /** + * @covers GlobalVarConfig::__construct + * @dataProvider provideConstructor + */ + public function testConstructor( $prefix ) { + $var = $prefix . 'GlobalVarConfigTest'; + $rand = wfRandomString(); + $this->maybeStashGlobal( $var ); + $GLOBALS[$var] = $rand; + $config = new GlobalVarConfig( $prefix ); + $this->assertInstanceOf( GlobalVarConfig::class, $config ); + $this->assertEquals( $rand, $config->get( 'GlobalVarConfigTest' ) ); + } + + public static function provideConstructor() { + return [ + [ 'wg' ], + [ 'ef' ], + [ 'smw' ], + [ 'blahblahblahblah' ], + [ '' ], + ]; + } + + /** + * @covers GlobalVarConfig::has + * @covers GlobalVarConfig::hasWithPrefix + */ + public function testHas() { + $this->maybeStashGlobal( 'wgGlobalVarConfigTestHas' ); + $GLOBALS['wgGlobalVarConfigTestHas'] = wfRandomString(); + $this->maybeStashGlobal( 'wgGlobalVarConfigTestNotHas' ); + $config = new GlobalVarConfig(); + $this->assertTrue( $config->has( 'GlobalVarConfigTestHas' ) ); + $this->assertFalse( $config->has( 'GlobalVarConfigTestNotHas' ) ); + } + + public static function provideGet() { + $set = [ + 'wgSomething' => 'default1', + 'wgFoo' => 'default2', + 'efVariable' => 'default3', + 'BAR' => 'default4', + ]; + + foreach ( $set as $var => $value ) { + $GLOBALS[$var] = $value; + } + + return [ + [ 'Something', 'wg', 'default1' ], + [ 'Foo', 'wg', 'default2' ], + [ 'Variable', 'ef', 'default3' ], + [ 'BAR', '', 'default4' ], + [ 'ThisGlobalWasNotSetAbove', 'wg', false ] + ]; + } + + /** + * @dataProvider provideGet + * @covers GlobalVarConfig::get + * @covers GlobalVarConfig::getWithPrefix + * @param string $name + * @param string $prefix + * @param string $expected + */ + public function testGet( $name, $prefix, $expected ) { + $config = new GlobalVarConfig( $prefix ); + if ( $expected === false ) { + $this->setExpectedException( ConfigException::class, 'GlobalVarConfig::get: undefined option:' ); + } + $this->assertEquals( $config->get( $name ), $expected ); + } + + private function maybeStashGlobal( $var ) { + if ( array_key_exists( $var, $GLOBALS ) ) { + // Will be reset after this test is over + $this->stashMwGlobals( $var ); + } + } +} diff --git a/www/wiki/tests/phpunit/includes/config/HashConfigTest.php b/www/wiki/tests/phpunit/includes/config/HashConfigTest.php new file mode 100644 index 00000000..bac8311c --- /dev/null +++ b/www/wiki/tests/phpunit/includes/config/HashConfigTest.php @@ -0,0 +1,63 @@ +<?php + +class HashConfigTest extends MediaWikiTestCase { + + /** + * @covers HashConfig::newInstance + */ + public function testNewInstance() { + $conf = HashConfig::newInstance(); + $this->assertInstanceOf( HashConfig::class, $conf ); + } + + /** + * @covers HashConfig::__construct + */ + public function testConstructor() { + $conf = new HashConfig(); + $this->assertInstanceOf( HashConfig::class, $conf ); + + // Test passing arguments to the constructor + $conf2 = new HashConfig( [ + 'one' => '1', + ] ); + $this->assertEquals( '1', $conf2->get( 'one' ) ); + } + + /** + * @covers HashConfig::get + */ + public function testGet() { + $conf = new HashConfig( [ + 'one' => '1', + ] ); + $this->assertEquals( '1', $conf->get( 'one' ) ); + $this->setExpectedException( ConfigException::class, 'HashConfig::get: undefined option' ); + $conf->get( 'two' ); + } + + /** + * @covers HashConfig::has + */ + public function testHas() { + $conf = new HashConfig( [ + 'one' => '1', + ] ); + $this->assertTrue( $conf->has( 'one' ) ); + $this->assertFalse( $conf->has( 'two' ) ); + } + + /** + * @covers HashConfig::set + */ + public function testSet() { + $conf = new HashConfig( [ + 'one' => '1', + ] ); + $conf->set( 'two', '2' ); + $this->assertEquals( '2', $conf->get( 'two' ) ); + // Check that set overwrites + $conf->set( 'one', '3' ); + $this->assertEquals( '3', $conf->get( 'one' ) ); + } +} diff --git a/www/wiki/tests/phpunit/includes/config/MultiConfigTest.php b/www/wiki/tests/phpunit/includes/config/MultiConfigTest.php new file mode 100644 index 00000000..fc283951 --- /dev/null +++ b/www/wiki/tests/phpunit/includes/config/MultiConfigTest.php @@ -0,0 +1,39 @@ +<?php + +class MultiConfigTest extends MediaWikiTestCase { + + /** + * Tests that settings are fetched in the right order + * + * @covers MultiConfig::__construct + * @covers MultiConfig::get + */ + public function testGet() { + $multi = new MultiConfig( [ + new HashConfig( [ 'foo' => 'bar' ] ), + new HashConfig( [ 'foo' => 'baz', 'bar' => 'foo' ] ), + new HashConfig( [ 'bar' => 'baz' ] ), + ] ); + + $this->assertEquals( 'bar', $multi->get( 'foo' ) ); + $this->assertEquals( 'foo', $multi->get( 'bar' ) ); + $this->setExpectedException( ConfigException::class, 'MultiConfig::get: undefined option:' ); + $multi->get( 'notset' ); + } + + /** + * @covers MultiConfig::has + */ + public function testHas() { + $conf = new MultiConfig( [ + new HashConfig( [ 'foo' => 'foo' ] ), + new HashConfig( [ 'something' => 'bleh' ] ), + new HashConfig( [ 'meh' => 'eh' ] ), + ] ); + + $this->assertTrue( $conf->has( 'foo' ) ); + $this->assertTrue( $conf->has( 'something' ) ); + $this->assertTrue( $conf->has( 'meh' ) ); + $this->assertFalse( $conf->has( 'what' ) ); + } +} |