aboutsummaryrefslogtreecommitdiffstats
path: root/tests/phpunit/unit/includes/config/EtcdConfigTest.php
diff options
context:
space:
mode:
authorDan Duvall <dduvall@wikimedia.org>2021-12-07 16:04:56 -0800
committerKrinkle <krinkle@fastmail.com>2022-02-16 15:00:45 +0000
commitaa5fdf9e6ff246f6fe91ba5f4aba273216a58fce (patch)
tree60e65b11fa2862b5737b632430f88f351e3b02c8 /tests/phpunit/unit/includes/config/EtcdConfigTest.php
parent97056794fca9cca5ab43101f40a47dd8fa168d7a (diff)
downloadmediawikicore-aa5fdf9e6ff246f6fe91ba5f4aba273216a58fce.tar.gz
mediawikicore-aa5fdf9e6ff246f6fe91ba5f4aba273216a58fce.zip
Simplify DnsSrvDiscoverer interface
Refactor DnsSrvDiscoverer constructor to accept service, protocol, and domain as discrete arguments to better match the specifications outlined in RFC 2782 and make clearer the concerns and contract between the discoverer and the caller. The latter should not need to know about discovery details but only the service to be discovered and the scope of that discovery (the domain). Domain is now optional where an omitted value results in discovery relative to the host's configured search domain, supporting installations where full DNS based discovery is desired over explicit configuration. This makes the simplest use case: $servers = ( new DnsSrvDiscoverer( 'service' ) )->getServers(); Overall use of the interface is simplified by changing `getServers()` to unconditionally sort results by priority according to RFC 2872 and return a simplified result of host/port tuples (the only data relevant in primary discovery use cases). This makes the handling of discovery fallback simpler since the caller can construct a default without having to populate all response record details. Example $dsd = new DnsSrvDiscoverer( $service, $proto, $host ); $servers = $dsd->getServers() ?: [ [ $host, $defaultPort ] ]; A `getRecords()` method is provided as public to allow a caller to obtain details on the response (and do its own sorting) should that be needed. Added an optional `$resolver` argument to the constructor (which defaults to `dns_get_record`) to make the class implementation more testable, and wrote functional tests. EtcdConfig has been refactored to reflect the new usage of DnsSrvDiscoverer and now sets the discoverer as a property in the constructor to allow for mocking in tests. Implemented tests for etcd DNS SRV discovery within EtcdConfig. Bug: T296771 Change-Id: Idbd60049853439f96ff6045e01aa03014b4e587f
Diffstat (limited to 'tests/phpunit/unit/includes/config/EtcdConfigTest.php')
-rw-r--r--tests/phpunit/unit/includes/config/EtcdConfigTest.php147
1 files changed, 145 insertions, 2 deletions
diff --git a/tests/phpunit/unit/includes/config/EtcdConfigTest.php b/tests/phpunit/unit/includes/config/EtcdConfigTest.php
index 8b730f0ba2e6..a63b7588208c 100644
--- a/tests/phpunit/unit/includes/config/EtcdConfigTest.php
+++ b/tests/phpunit/unit/includes/config/EtcdConfigTest.php
@@ -4,14 +4,14 @@ use Wikimedia\TestingAccessWrapper;
class EtcdConfigTest extends MediaWikiUnitTestCase {
- private function createConfigMock( array $options = [] ) {
+ private function createConfigMock( array $options = [], ?array $methods = null ) {
return $this->getMockBuilder( EtcdConfig::class )
->setConstructorArgs( [ $options + [
'host' => 'etcd-tcp.example.net',
'directory' => '/',
'timeout' => 0.1,
] ] )
- ->onlyMethods( [ 'fetchAllFromEtcd' ] )
+ ->onlyMethods( $methods ?? [ 'fetchAllFromEtcd' ] )
->getMock();
}
@@ -35,6 +35,13 @@ class EtcdConfigTest extends MediaWikiUnitTestCase {
return $mock;
}
+ private function createCallableMock() {
+ return $this
+ ->getMockBuilder( \stdClass::class )
+ ->addMethods( [ '__invoke' ] )
+ ->getMock();
+ }
+
/**
* @covers EtcdConfig::has
*/
@@ -615,4 +622,140 @@ class EtcdConfigTest extends MediaWikiUnitTestCase {
$conf->fetchAllFromEtcdServer( 'etcd-tcp.example.net' )
);
}
+
+ /**
+ * @covers EtcdConfig::fetchAllFromEtcdServer
+ */
+ public function testFetchFromServerWithoutPort() {
+ $conf = $this->getMockBuilder( EtcdConfig::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $http = $this->getMockBuilder( MultiHttpClient::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $conf = TestingAccessWrapper::newFromObject( $conf );
+ $conf->protocol = 'https';
+ $conf->http = $http;
+
+ $http
+ ->expects( $this->once() )
+ ->method( 'run' )
+ ->with(
+ $this->logicalAnd(
+ $this->arrayHasKey( 'url' ),
+ $this->callback( function ( $request ) {
+ $this->assertStringStartsWith(
+ 'https://etcd.example/',
+ $request['url']
+ );
+ return true;
+ } )
+ )
+ );
+
+ $conf->fetchAllFromEtcdServer( 'etcd.example' );
+ }
+
+ /**
+ * @covers EtcdConfig::fetchAllFromEtcdServer
+ */
+ public function testFetchFromServerWithPort() {
+ $conf = $this->getMockBuilder( EtcdConfig::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $http = $this->getMockBuilder( MultiHttpClient::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $conf = TestingAccessWrapper::newFromObject( $conf );
+ $conf->protocol = 'https';
+ $conf->http = $http;
+
+ $http
+ ->expects( $this->once() )
+ ->method( 'run' )
+ ->with(
+ $this->logicalAnd(
+ $this->arrayHasKey( 'url' ),
+ $this->callback( function ( $request ) {
+ $this->assertStringStartsWith(
+ 'https://etcd.example:4001/',
+ $request['url']
+ );
+ return true;
+ } )
+ )
+ );
+
+ $conf->fetchAllFromEtcdServer( 'etcd.example', 4001 );
+ }
+
+ /**
+ * @covers EtcdConfig::fetchAllFromEtcd
+ */
+ public function testServiceDiscovery() {
+ $conf = $this->createConfigMock(
+ [ 'host' => 'an.example' ],
+ [ 'fetchAllFromEtcdServer' ]
+ );
+ $conf = TestingAccessWrapper::newFromObject( $conf );
+
+ $conf->dsd = TestingAccessWrapper::newFromObject( $conf->dsd );
+ $conf->dsd->resolver = $this->createCallableMock();
+ $conf->dsd->resolver
+ ->expects( $this->once() )
+ ->method( '__invoke' )
+ ->with( '_etcd._tcp.an.example' )
+ ->willReturn( [
+ [
+ 'target' => 'etcd-target.an.example',
+ 'port' => '2379',
+ 'pri' => '1',
+ 'weight' => '1',
+ ],
+ ] );
+
+ $conf->expects( $this->once() )
+ ->method( 'fetchAllFromEtcdServer' )
+ ->with( 'etcd-target.an.example', 2379 )
+ ->willReturn( self::createEtcdResponse( [ 'foo' => true ] ) );
+
+ $conf->fetchAllFromEtcd();
+ }
+
+ /**
+ * @covers EtcdConfig::fetchAllFromEtcd
+ */
+ public function testServiceDiscoverySrvRecordAsHost() {
+ $conf = $this->createConfigMock(
+ [ 'host' => '_etcd-client-ssl._tcp.an.example' ],
+ [ 'fetchAllFromEtcdServer' ]
+ );
+ $conf = TestingAccessWrapper::newFromObject( $conf );
+
+ $conf->dsd = TestingAccessWrapper::newFromObject( $conf->dsd );
+ $conf->dsd->resolver = $this->createCallableMock();
+ $conf->dsd->resolver
+ ->expects( $this->once() )
+ ->method( '__invoke' )
+ ->with( '_etcd-client-ssl._tcp.an.example' )
+ ->willReturn( [
+ [
+ 'target' => 'etcd-target.an.example',
+ 'port' => '2379',
+ 'pri' => '1',
+ 'weight' => '1',
+ ],
+ ] );
+
+ $conf->expects( $this->once() )
+ ->method( 'fetchAllFromEtcdServer' )
+ ->with( 'etcd-target.an.example', 2379 )
+ ->willReturn( self::createEtcdResponse( [ 'foo' => true ] ) );
+
+ $conf->fetchAllFromEtcd();
+ }
}