3 use Wikimedia\TestingAccessWrapper;
7 use MediaWikiCoversValidator;
8 use PHPUnit4And6Compat;
13 'host' =>
'etcd-tcp.example.net',
17 ->setMethods( [
'fetchAllFromEtcd' ] )
28 return array_merge( $baseResponse,
$response );
33 $mock->expects( $this->once() )->method(
'fetchAllFromEtcd' )
34 ->willReturn( self::createEtcdResponse( [
36 'modifiedIndex' => $index,
48 $this->assertSame(
true, $config->has(
'known' ) );
59 $this->assertSame(
'value', $config->get(
'known' ) );
69 $this->assertSame(
false, $config->has(
'unknown' ) );
80 $config->get(
'unknown' );
88 [
'some' =>
'value' ],
91 $this->assertSame( 123, $config->getModifiedIndex() );
99 ->setMethods( [
'get' ] )
101 $cache->expects( $this->once() )->method(
'get' )
103 'config' => [
'known' =>
'from-cache' ],
105 'modifiedIndex' => 123
109 $this->assertSame(
'from-cache', $config->get(
'known' ) );
119 $config->expects( $this->once() )->method(
'fetchAllFromEtcd' )
120 ->willReturn( self::createEtcdResponse(
121 [
'config' => [
'known' =>
'from-fetch' ], ] ) );
123 $this->assertSame(
'from-fetch', $config->get(
'known' ) );
174 ->setMethods( [
'get',
'lock' ] )
177 $cache->expects( $this->once() )->method(
'get' )
178 ->willReturn(
false );
180 $cache->expects( $this->once() )->method(
'lock' )
181 ->willReturn(
true );
187 $mock->expects( $this->once() )->method(
'fetchAllFromEtcd' )
189 self::createEtcdResponse( [
'config' => [
'known' =>
'from-fetch' ] ] ) );
191 $this->assertSame(
'from-fetch', $mock->get(
'known' ) );
200 ->setMethods( [
'get',
'lock' ] )
203 $cache->expects( $this->once() )->method(
'get' )
204 ->willReturn(
false );
206 $cache->expects( $this->once() )->method(
'lock' )
207 ->willReturn(
true );
213 $mock->expects( $this->once() )->method(
'fetchAllFromEtcd' )
214 ->willReturn( self::createEtcdResponse( [
'error' =>
'Fake error', ] ) );
226 ->setMethods( [
'get',
'lock' ] )
228 $cache->expects( $this->exactly( 2 ) )->method(
'get' )
229 ->will( $this->onConsecutiveCalls(
234 'config' => [
'known' =>
'from-cache' ],
236 'modifiedIndex' => 123
240 $cache->expects( $this->once() )->method(
'lock' )
241 ->willReturn(
false );
247 $mock->expects( $this->never() )->method(
'fetchAllFromEtcd' );
249 $this->assertSame(
'from-cache', $mock->get(
'known' ) );
258 ->setMethods( [
'get',
'lock' ] )
260 $cache->expects( $this->once() )->method(
'get' )
263 'config' => [
'known' =>
'from-cache' ],
265 'modifiedIndex' => 0,
267 $cache->expects( $this->never() )->method(
'lock' );
273 $mock->expects( $this->never() )->method(
'fetchAllFromEtcd' );
275 $this->assertSame(
'from-cache', $mock->get(
'known' ) );
284 ->setMethods( [
'get',
'lock' ] )
286 $cache->expects( $this->once() )->method(
'get' )
289 'config' => [
'known' =>
'from-cache' ],
291 'modifiedIndex' => 0,
293 $cache->expects( $this->never() )->method(
'lock' );
299 $mock->expects( $this->never() )->method(
'fetchAllFromEtcd' );
301 $this->assertSame(
'from-cache', $mock->get(
'known' ),
'Cache hit' );
302 $this->assertSame(
'from-cache', $mock->get(
'known' ),
'Process cache hit' );
311 ->setMethods( [
'get',
'lock' ] )
313 $cache->expects( $this->once() )->method(
'get' )->willReturn(
316 'config' => [
'known' =>
'from-cache-expired' ],
318 'modifiedIndex' => 0,
322 $cache->expects( $this->once() )->method(
'lock' )
323 ->willReturn(
true );
329 $mock->expects( $this->once() )->method(
'fetchAllFromEtcd' )
330 ->willReturn( self::createEtcdResponse( [
'config' => [
'known' =>
'from-fetch' ] ] ) );
332 $this->assertSame(
'from-fetch', $mock->get(
'known' ) );
341 ->setMethods( [
'get',
'lock' ] )
343 $cache->expects( $this->once() )->method(
'get' )->willReturn(
346 'config' => [
'known' =>
'from-cache-expired' ],
348 'modifiedIndex' => 0,
352 $cache->expects( $this->once() )->method(
'lock' )
353 ->willReturn(
true );
359 $mock->expects( $this->once() )->method(
'fetchAllFromEtcd' )
360 ->willReturn( self::createEtcdResponse( [
'error' =>
'Fake failure',
'retry' =>
true ] ) );
362 $this->assertSame(
'from-cache-expired', $mock->get(
'known' ) );
371 ->setMethods( [
'get',
'lock' ] )
373 $cache->expects( $this->once() )->method(
'get' )
376 'config' => [
'known' =>
'from-cache-expired' ],
378 'modifiedIndex' => 0,
381 $cache->expects( $this->once() )->method(
'lock' )
382 ->willReturn(
false );
388 $mock->expects( $this->never() )->method(
'fetchAllFromEtcd' );
390 $this->assertSame(
'from-cache-expired', $mock->get(
'known' ) );
395 '200 OK - Success' => [
400 'body' => json_encode( [
'node' => [
'nodes' => [
402 'key' =>
'/example/foo',
403 'value' => json_encode( [
'val' =>
true ] ),
404 'modifiedIndex' => 123
410 'config' => [
'foo' =>
true ],
411 'modifiedIndex' => 123
414 '200 OK - Empty dir' => [
419 'body' => json_encode( [
'node' => [
'nodes' => [
421 'key' =>
'/example/foo',
422 'value' => json_encode( [
'val' =>
true ] ),
423 'modifiedIndex' => 123
426 'key' =>
'/example/sub',
428 'modifiedIndex' => 234,
432 'key' =>
'/example/bar',
433 'value' => json_encode( [
'val' =>
false ] ),
434 'modifiedIndex' => 125
440 'config' => [
'foo' =>
true,
'bar' =>
false ],
441 'modifiedIndex' => 125
444 '200 OK - Recursive' => [
449 'body' => json_encode( [
'node' => [
'nodes' => [
451 'key' =>
'/example/a',
453 'modifiedIndex' => 124,
457 'value' => json_encode( [
'val' =>
true ] ),
458 'modifiedIndex' => 123,
463 'value' => json_encode( [
'val' =>
false ] ),
464 'modifiedIndex' => 123,
472 'config' => [
'a/b' =>
true,
'a/c' =>
false ],
473 'modifiedIndex' => 123
476 '200 OK - Missing nodes at second level' => [
481 'body' => json_encode( [
'node' => [
'nodes' => [
483 'key' =>
'/example/a',
485 'modifiedIndex' => 0,
491 'error' =>
"Unexpected JSON response in dir 'a'; missing 'nodes' list.",
494 '200 OK - Directory with non-array "nodes" key' => [
499 'body' => json_encode( [
'node' => [
'nodes' => [
501 'key' =>
'/example/a',
503 'nodes' =>
'not an array'
509 'error' =>
"Unexpected JSON response in dir 'a'; 'nodes' is not an array.",
512 '200 OK - Correctly encoded garbage response' => [
517 'body' => json_encode( [
'foo' =>
'bar' ] ),
521 'error' =>
"Unexpected JSON response: Missing or invalid node at top level.",
524 '200 OK - Bad value' => [
529 'body' => json_encode( [
'node' => [
'nodes' => [
531 'key' =>
'/example/foo',
532 'value' =>
';"broken{value',
533 'modifiedIndex' => 123,
539 'error' =>
"Failed to parse value for 'foo'.",
542 '200 OK - Empty node list' => [
547 'body' =>
'{"node":{"nodes":[], "modifiedIndex": 12 }}',
554 '200 OK - Invalid JSON' => [
558 'headers' => [
'content-length' => 0 ],
560 'error' =>
'(curl error: no status set)',
563 'error' =>
"Error unserializing JSON response.",
569 'reason' =>
'Not Found',
570 'headers' => [
'content-length' => 0 ],
575 'error' =>
'HTTP 404 (Not Found)',
578 '400 Bad Request - custom error' => [
581 'reason' =>
'Bad Request',
582 'headers' => [
'content-length' => 0 ],
584 'error' =>
'No good reason',
587 'error' =>
'No good reason',
604 ->disableOriginalConstructor()
606 $http->expects( $this->once() )->method(
'run' )
607 ->willReturn( array_values( $httpResponse ) );
610 ->disableOriginalConstructor()
613 $conf = TestingAccessWrapper::newFromObject( $conf );
618 $conf->fetchAllFromEtcdServer(
'etcd-tcp.example.net' )