Files
predis/tests/Predis/ClientTest.php
T
2026-03-09 13:30:46 -07:00

1777 lines
58 KiB
PHP

<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2026 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis;
use Exception;
use Iterator;
use PHPUnit\Framework\MockObject\MockObject;
use Predis\Command\Factory as CommandFactory;
use Predis\Command\Processor\KeyPrefixProcessor;
use Predis\Command\RawCommand;
use Predis\Connection\Cluster\RedisCluster;
use Predis\Connection\Factory;
use Predis\Connection\NodeConnectionInterface;
use Predis\Connection\Parameters;
use Predis\Connection\ParametersInterface;
use Predis\Connection\Replication\MasterSlaveReplication;
use Predis\Connection\Replication\SentinelReplication;
use Predis\Connection\Resource\StreamFactoryInterface;
use Predis\Connection\StreamConnection;
use Predis\Retry\Retry;
use Predis\Retry\Strategy\ExponentialBackoff;
use PredisTestCase;
use Psr\Http\Message\StreamInterface;
use ReflectionProperty;
use RuntimeException;
use stdClass;
class ClientTest extends PredisTestCase
{
/**
* @group disconnected
*/
public function testConstructorWithoutArguments(): void
{
$client = new Client();
/** @var NodeConnectionInterface */
$connection = $client->getConnection();
$this->assertInstanceOf('Predis\Connection\NodeConnectionInterface', $connection);
$parameters = $connection->getParameters();
$this->assertSame($parameters->host, '127.0.0.1');
$this->assertSame($parameters->port, 6379);
$options = $client->getOptions();
$this->assertSame($options->commands, $client->getCommandFactory());
$this->assertFalse($client->isConnected());
}
/**
* @group disconnected
*/
public function testConstructorWithNullArgument(): void
{
$client = new Client(null);
/** @var NodeConnectionInterface */
$connection = $client->getConnection();
$this->assertInstanceOf('Predis\Connection\NodeConnectionInterface', $connection);
$parameters = $connection->getParameters();
$this->assertSame($parameters->host, '127.0.0.1');
$this->assertSame($parameters->port, 6379);
$options = $client->getOptions();
$this->assertSame($options->commands, $client->getCommandFactory());
$this->assertFalse($client->isConnected());
}
/**
* @group disconnected
*/
public function testConstructorWithNullAndNullArguments(): void
{
$client = new Client(null, null);
/** @var NodeConnectionInterface */
$connection = $client->getConnection();
$this->assertInstanceOf('Predis\Connection\NodeConnectionInterface', $connection);
$parameters = $connection->getParameters();
$this->assertSame($parameters->host, '127.0.0.1');
$this->assertSame($parameters->port, 6379);
$options = $client->getOptions();
$this->assertSame($options->commands, $client->getCommandFactory());
$this->assertFalse($client->isConnected());
}
/**
* @group disconnected
*/
public function testConstructorWithArrayArgument(): void
{
$client = new Client($arg1 = ['host' => 'localhost', 'port' => 7000]);
/** @var NodeConnectionInterface */
$connection = $client->getConnection();
$parameters = $connection->getParameters();
$this->assertSame($parameters->host, $arg1['host']);
$this->assertSame($parameters->port, $arg1['port']);
}
/**
* @group disconnected
*/
public function testConstructorThrowsExceptionWithArrayOfParametersArgumentAndMissingOption(): void
{
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage('Array of connection parameters requires `cluster`, `replication` or `aggregate` client option');
$arg1 = [
['host' => 'localhost', 'port' => 7000],
['host' => 'localhost', 'port' => 7001],
];
$client = new Client($arg1);
}
/**
* @group disconnected
*/
public function testConstructorWithArrayOfArrayArgumentAndClusterOption(): void
{
$arg1 = [
['host' => 'localhost', 'port' => 7000],
['host' => 'localhost', 'port' => 7001],
];
$client = new Client($arg1, [
'aggregate' => $this->getAggregateInitializer($arg1),
]);
$this->assertInstanceOf('Predis\Connection\AggregateConnectionInterface', $client->getConnection());
}
/**
* @group disconnected
*/
public function testConstructorWithStringArgument(): void
{
$client = new Client('tcp://localhost:7000');
/** @var NodeConnectionInterface */
$connection = $client->getConnection();
$parameters = $connection->getParameters();
$this->assertSame($parameters->host, 'localhost');
$this->assertSame($parameters->port, 7000);
}
/**
* @group disconnected
*/
public function testConstructorWithArrayOfStringArgument(): void
{
$arg1 = ['tcp://localhost:7000', 'tcp://localhost:7001'];
$client = new Client($arg1, [
'aggregate' => $this->getAggregateInitializer($arg1),
]);
$this->assertInstanceOf('Predis\Connection\AggregateConnectionInterface', $client->getConnection());
}
/**
* @group disconnected
*/
public function testConstructorWithArrayOfConnectionsArgument(): void
{
$arg1 = [
$this->getMockBuilder('Predis\Connection\NodeConnectionInterface')->getMock(),
$this->getMockBuilder('Predis\Connection\NodeConnectionInterface')->getMock(),
];
$client = new Client($arg1, [
'aggregate' => $this->getAggregateInitializer($arg1),
]);
$this->assertInstanceOf('Predis\Connection\AggregateConnectionInterface', $client->getConnection());
}
/**
* @group disconnected
*/
public function testConstructorWithConnectionArgument(): void
{
$factory = new Factory();
$connection = $factory->create('tcp://localhost:7000');
$client = new Client($connection);
$this->assertInstanceOf('Predis\Connection\NodeConnectionInterface', $client->getConnection());
$this->assertSame($connection, $client->getConnection());
/** @var NodeConnectionInterface */
$connection = $client->getConnection();
$parameters = $connection->getParameters();
$this->assertSame($parameters->host, 'localhost');
$this->assertSame($parameters->port, 7000);
}
/**
* @group disconnected
*/
public function testConstructorWithClusterArgument(): void
{
$cluster = new Connection\Cluster\PredisCluster(new Parameters());
$factory = new Factory();
$cluster->add($factory->create('tcp://localhost:7000'));
$cluster->add($factory->create('tcp://localhost:7001'));
$client = new Client($cluster);
$this->assertInstanceOf('Predis\Connection\Cluster\ClusterInterface', $client->getConnection());
$this->assertSame($cluster, $client->getConnection());
}
/**
* @group disconnected
*/
public function testConstructorWithReplicationArgument(): void
{
$replication = new MasterSlaveReplication();
$factory = new Factory();
$replication->add($factory->create('tcp://host1?alias=master'));
$replication->add($factory->create('tcp://host2?alias=slave'));
$client = new Client($replication);
$this->assertInstanceOf('Predis\Connection\Replication\ReplicationInterface', $client->getConnection());
$this->assertSame($replication, $client->getConnection());
}
/**
* @group disconnected
*/
public function testConstructorWithCallableArgument(): void
{
$connection = $this->getMockBuilder('Predis\Connection\ConnectionInterface')->getMock();
$callable = $this->getMockBuilder('stdClass')
->addMethods(['__invoke'])
->getMock();
$callable
->expects($this->once())
->method('__invoke')
->with($this->isInstanceOf('Predis\Configuration\OptionsInterface'))
->willReturn($connection);
$client = new Client($callable);
$this->assertSame($connection, $client->getConnection());
}
/**
* @group disconnected
*/
public function testConstructorWithCallableConnectionInitializerThrowsExceptionOnInvalidReturnType(): void
{
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage('Callable parameters must return a valid connection');
$wrongType = $this->getMockBuilder('stdClass')->getMock();
$callable = $this->getMockBuilder('stdClass')
->addMethods(['__invoke'])
->getMock();
$callable
->expects($this->once())
->method('__invoke')
->with($this->isInstanceOf('Predis\Configuration\OptionsInterface'))
->willReturn($wrongType);
new Client($callable);
}
/**
* @group disconnected
*/
public function testConstructorWithNullAndArrayArgument(): void
{
$connections = $this->getMockBuilder('Predis\Connection\FactoryInterface')->getMock();
$arg2 = ['prefix' => 'prefix:', 'connections' => $connections];
$client = new Client(null, $arg2);
/** @var CommandFactory */
$commands = $client->getCommandFactory();
$this->assertInstanceOf('Predis\Command\FactoryInterface', $commands);
/** @var KeyPrefixProcessor */
$processor = $commands->getProcessor();
$this->assertInstanceOf('Predis\Command\Processor\KeyPrefixProcessor', $processor);
$this->assertSame('prefix:', $processor->getPrefix());
}
/**
* @group disconnected
*/
public function testConstructorWithArrayAndOptionReplication(): void
{
$arg1 = ['tcp://127.0.0.1:6379?role=master', 'tcp://127.0.0.1:6380?role=slave'];
$arg2 = ['replication' => 'predis'];
$client = new Client($arg1, $arg2);
/** @var MasterSlaveReplication */
$connection = $client->getConnection();
$this->assertInstanceOf('Predis\Connection\Replication\ReplicationInterface', $connection);
$this->assertSame('127.0.0.1:6379', (string) $connection->getConnectionByRole('master'));
$this->assertSame('127.0.0.1:6380', (string) $connection->getConnectionByRole('slave'));
}
/**
* @group disconnected
*/
public function testClusterOptionHasPrecedenceOverReplicationOptionAndAggregateOption(): void
{
$arg1 = ['tcp://host1', 'tcp://host2'];
$connection = $this->getMockBuilder('Predis\Connection\AggregateConnectionInterface')->getMock();
$fncluster = $this->getMockBuilder('stdClass')
->addMethods(['__invoke'])
->getMock();
$fncluster
->expects($this->once())
->method('__invoke')
->with(
$arg1,
$this->isInstanceOf('Predis\Configuration\OptionsInterface'),
$this->isInstanceOf('Predis\Configuration\OptionInterface')
)
->willReturn($connection);
$fnreplication = $this->getMockBuilder('stdClass')
->addMethods(['__invoke'])
->getMock();
$fnreplication
->expects($this->never())
->method('__invoke');
$fnaggregate = $this->getMockBuilder('stdClass')
->addMethods(['__invoke'])
->getMock();
$fnaggregate
->expects($this->never())
->method('__invoke');
$arg2 = [
'cluster' => $fncluster,
'replication' => $fnreplication,
'aggregate' => $fnaggregate,
];
$client = new Client($arg1, $arg2);
$this->assertSame($connection, $client->getConnection());
}
/**
* @group disconnected
*/
public function testReplicationOptionHasPrecedenceOverAggregateOption(): void
{
$arg1 = ['tcp://host1', 'tcp://host2'];
$connection = $this->getMockBuilder('Predis\Connection\AggregateConnectionInterface')->getMock();
$fnreplication = $this->getMockBuilder('stdClass')
->addMethods(['__invoke'])
->getMock();
$fnreplication
->expects($this->once())
->method('__invoke')
->with(
$arg1,
$this->isInstanceOf('Predis\Configuration\OptionsInterface'),
$this->isInstanceOf('Predis\Configuration\OptionInterface')
)
->willReturn($connection);
$fnaggregate = $this->getMockBuilder('stdClass')
->addMethods(['__invoke'])
->getMock();
$fnaggregate
->expects($this->never())
->method('__invoke');
$arg2 = [
'replication' => $fnreplication,
'aggregate' => $fnaggregate,
];
$client = new Client($arg1, $arg2);
}
/**
* @group disconnected
*/
public function testAggregateOptionDoesNotTriggerAggregationInClient(): void
{
$arg1 = ['tcp://host1', 'tcp://host2'];
$connections = $this->getMockBuilder('Predis\Connection\FactoryInterface')->getMock();
$connections
->expects($this->never())
->method('create');
$connection = $this->getMockBuilder('Predis\Connection\AggregateConnectionInterface')
->getMock();
$connection
->expects($this->never())
->method('add');
$fnaggregate = $this->getMockBuilder('stdClass')
->addMethods(['__invoke'])
->getMock();
$fnaggregate
->expects($this->once())
->method('__invoke')
->with(
$arg1,
$this->isInstanceOf('Predis\Configuration\OptionsInterface'),
$this->isInstanceOf('Predis\Configuration\OptionInterface')
)
->willReturn($connection);
$arg2 = ['aggregate' => $fnaggregate, 'connections' => $connections];
$client = new Client($arg1, $arg2);
$this->assertSame($connection, $client->getConnection());
}
/**
* @group disconnected
*/
public function testConstructorWithInvalidArgumentType(): void
{
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage('Invalid type for connection parameters');
$client = new Client(new stdClass());
}
/**
* @group disconnected
*/
public function testConstructorWithInvalidOptionType(): void
{
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage('Invalid type for client options');
$client = new Client('tcp://host1', new stdClass());
}
/**
* @group disconnected
*/
public function testConnectAndDisconnect(): void
{
$connection = $this->getMockBuilder('Predis\Connection\ConnectionInterface')->getMock();
$connection
->expects($this->once())
->method('connect');
$connection
->expects($this->once())
->method('disconnect');
$client = new Client($connection);
$client->connect();
$client->disconnect();
}
/**
* @group disconnected
*/
public function testIsConnectedChecksConnectionState(): void
{
$connection = $this->getMockBuilder('Predis\Connection\ConnectionInterface')->getMock();
$connection
->expects($this->once())
->method('isConnected');
$client = new Client($connection);
$client->isConnected();
}
/**
* @group disconnected
*/
public function testQuitIsAliasForDisconnect(): void
{
$connection = $this->getMockBuilder('Predis\Connection\ConnectionInterface')->getMock();
$connection
->expects($this->once())
->method('disconnect');
$client = new Client($connection);
$client->quit();
}
/**
* @group disconnected
*/
public function testCreatesNewCommandUsingSpecifiedCommandFactory(): void
{
$ping = $this->getCommandFactory()->create('ping', []);
$commands = $this->getMockBuilder('Predis\Command\FactoryInterface')->getMock();
$commands
->expects($this->once())
->method('create')
->with('ping', [])
->willReturn($ping);
$client = new Client(null, ['commands' => $commands]);
$this->assertSame($ping, $client->createCommand('ping', []));
}
/**
* @group disconnected
*/
public function testExecuteCommandReturnsParsedResponses(): void
{
$commands = $this->getCommandFactory();
$ping = $commands->create('ping', []);
$hgetall = $commands->create('hgetall', ['metavars', 'foo', 'hoge']);
$connection = $this->getMockBuilder('Predis\Connection\ConnectionInterface')->getMock();
$connection
->expects($this->exactly(2))
->method('executeCommand')
->withConsecutive(
[$ping],
[$hgetall]
)
->willReturnOnConsecutiveCalls(
new Response\Status('PONG'),
['foo', 'bar', 'hoge', 'piyo']
);
$connection
->expects($this->exactly(2))
->method('getParameters')
->willReturn(new Parameters(['protocol' => 2]));
$client = new Client($connection);
$this->assertEquals('PONG', $client->executeCommand($ping));
$this->assertSame(['foo' => 'bar', 'hoge' => 'piyo'], $client->executeCommand($hgetall));
}
/**
* @group disconnected
*/
public function testExecuteCommandReturnsResp3ParsedResponses(): void
{
$commands = $this->getCommandFactory();
$ping = $commands->create('ping', []);
$get = $commands->create('get', []);
$connection = $this->getMockBuilder('Predis\Connection\ConnectionInterface')->getMock();
$connection
->expects($this->exactly(2))
->method('executeCommand')
->withConsecutive(
[$ping],
[$get]
)
->willReturnOnConsecutiveCalls(
new Response\Status('PONG'),
[]
);
$connection
->expects($this->exactly(2))
->method('getParameters')
->willReturn(new Parameters(['protocol' => 3]));
$client = new Client($connection);
$this->assertEquals('PONG', $client->executeCommand($ping));
$this->assertSame([], $client->executeCommand($get));
}
/**
* @group disconnected
*/
public function testExecuteCommandThrowsExceptionOnRedisError(): void
{
$this->expectException('Predis\Response\ServerException');
$this->expectExceptionMessage('Operation against a key holding the wrong kind of value');
$ping = $this->getCommandFactory()->create('ping', []);
$expectedResponse = new Response\Error('ERR Operation against a key holding the wrong kind of value');
$connection = $this->getMockBuilder('Predis\Connection\ConnectionInterface')->getMock();
$connection
->expects($this->once())
->method('executeCommand')
->willReturn($expectedResponse);
$connection
->expects($this->once())
->method('getParameters')
->willReturn(new Parameters());
$client = new Client($connection);
$client->executeCommand($ping);
}
/**
* @group disconnected
*/
public function testExecuteCommandReturnsErrorResponseOnRedisError(): void
{
$ping = $this->getCommandFactory()->create('ping', []);
$expectedResponse = new Response\Error('ERR Operation against a key holding the wrong kind of value');
$connection = $this->getMockBuilder('Predis\Connection\ConnectionInterface')->getMock();
$connection
->expects($this->once())
->method('executeCommand')
->willReturn($expectedResponse);
$connection
->expects($this->once())
->method('getParameters')
->willReturn(new Parameters());
$client = new Client($connection, ['exceptions' => false]);
$response = $client->executeCommand($ping);
$this->assertSame($response, $expectedResponse);
}
/**
* @group disconnected
*/
public function testCallingRedisCommandExecutesInstanceOfCommand(): void
{
$ping = $this->getCommandFactory()->create('ping', []);
$connection = $this->getMockBuilder('Predis\Connection\ConnectionInterface')->getMock();
$connection
->expects($this->once())
->method('executeCommand')
->with($this->isInstanceOf('Predis\Command\Redis\PING'))
->willReturn('PONG');
$connection
->expects($this->once())
->method('getParameters')
->willReturn(new Parameters(['protocol' => 2]));
$commands = $this->getMockBuilder('Predis\Command\FactoryInterface')->getMock();
$commands
->expects($this->once())
->method('create')
->with('ping', [])
->willReturn($ping);
$options = ['commands' => $commands];
/** @var ClientInterface */
$client = $this->getMockBuilder('Predis\Client')
->onlyMethods([])
->setConstructorArgs([$connection, $options])
->getMock();
$this->assertEquals('PONG', $client->ping());
}
/**
* @group disconnected
*/
public function testCallingRedisCommandThrowsExceptionOnServerError(): void
{
$this->expectException('Predis\Response\ServerException');
$this->expectExceptionMessage('Operation against a key holding the wrong kind of value');
$expectedResponse = new Response\Error('ERR Operation against a key holding the wrong kind of value');
$connection = $this->getMockBuilder('Predis\Connection\ConnectionInterface')->getMock();
$connection
->expects($this->once())
->method('executeCommand')
->with($this->isRedisCommand('PING'))
->willReturn($expectedResponse);
$connection
->expects($this->once())
->method('getParameters')
->willReturn(new Parameters());
$client = new Client($connection);
$client->ping();
}
/**
* @group disconnected
*/
public function testCallingRedisCommandReturnsErrorResponseOnRedisError(): void
{
$expectedResponse = new Response\Error('ERR Operation against a key holding the wrong kind of value');
$connection = $this->getMockBuilder('Predis\Connection\ConnectionInterface')->getMock();
$connection
->expects($this->once())
->method('executeCommand')
->with($this->isRedisCommand('PING'))
->willReturn($expectedResponse);
$connection
->expects($this->once())
->method('getParameters')
->willReturn(new Parameters());
$client = new Client($connection, ['exceptions' => false]);
$response = $client->ping();
$this->assertSame($response, $expectedResponse);
}
/**
* @group disconnected
*/
public function testRawCommand(): void
{
$connection = $this->getMockBuilder('Predis\Connection\ConnectionInterface')->getMock();
$connection
->expects($this->exactly(3))
->method('executeCommand')
->withConsecutive(
[$this->isRedisCommand('SET', ['foo', 'bar'])],
[$this->isRedisCommand('GET', ['foo'])],
[$this->isRedisCommand('PING')]
)
->willReturnOnConsecutiveCalls(
new Response\Status('OK'),
'bar',
'PONG'
);
$client = new Client($connection);
$this->assertSame('OK', $client->executeRaw(['SET', 'foo', 'bar']));
$this->assertSame('bar', $client->executeRaw(['GET', 'foo']));
$error = true; // $error is always populated by reference.
$this->assertSame('PONG', $client->executeRaw(['PING'], $error));
$this->assertFalse($error);
}
/**
* @group disconnected
*/
public function testRawCommandNeverAppliesPrefix(): void
{
$connection = $this->getMockBuilder('Predis\Connection\ConnectionInterface')->getMock();
$connection
->expects($this->exactly(2))
->method('executeCommand')
->withConsecutive(
[$this->isRedisCommand('SET', ['foo', 'bar'])],
[$this->isRedisCommand('GET', ['foo'])]
)
->willReturnOnConsecutiveCalls(
new Response\Status('OK'),
'bar'
);
$client = new Client($connection, ['prefix' => 'predis:']);
$this->assertSame('OK', $client->executeRaw(['SET', 'foo', 'bar']));
$this->assertSame('bar', $client->executeRaw(['GET', 'foo']));
}
/**
* @group disconnected
*/
public function testRawCommandNeverThrowsExceptions(): void
{
$message = 'ERR Mock error response';
$response = new Response\Error($message);
$connection = $this->getMockBuilder('Predis\Connection\ConnectionInterface')->getMock();
$connection
->expects($this->once())
->method('executeCommand')
->with($this->isRedisCommand('PING'))
->willReturn($response);
$client = new Client($connection, ['exceptions' => true]);
$this->assertSame($message, $client->executeRaw(['PING'], $error));
$this->assertTrue($error);
}
/**
* @group disconnected
*/
public function testThrowsExceptionOnNonRegisteredRedisCommand(): void
{
$this->expectException('Predis\ClientException');
$this->expectExceptionMessage('Command `INVALIDCOMMAND` is not a registered Redis command');
$client = new Client();
$client->invalidCommand();
}
/**
* @group disconnected
*/
public function testGetClientByMethodReturnsInstanceOfSubclass(): void
{
/** @var Client */
$client = $this->getMockBuilder('Predis\Client')
->onlyMethods([])
->setConstructorArgs([
['tcp://host1?alias=node01', 'tcp://host2?alias=node02'],
['cluster' => 'predis'],
])
->setMockClassName('SubclassedClient')
->getMock();
$this->assertInstanceOf('SubclassedClient', $client->getClientBy('alias', 'node02'));
}
/**
* @group disconnected
*/
public function testGetClientByMethodSupportsSelectingConnectionById(): void
{
$connection = $this->getMockBuilder('Predis\Connection\ConnectionInterface')->getMock();
$aggregate = $this->getMockBuilder('Predis\Connection\AggregateConnectionInterface')
->onlyMethods(['getConnectionById'])
->getMockForAbstractClass();
$aggregate
->expects($this->once())
->method('getConnectionById')
->with('127.0.0.1:6379')
->willReturn($connection);
$client = new Client($aggregate);
$nodeClient = $client->getClientBy('id', '127.0.0.1:6379');
$this->assertSame($connection, $nodeClient->getConnection());
$this->assertSame($client->getOptions(), $nodeClient->getOptions());
}
/**
* @group disconnected
*/
public function testGetClientByMethodThrowsExceptionSelectingConnectionByUnknownId(): void
{
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage('Cannot find a connection by id matching `127.0.0.1:7000`');
$aggregate = $this->getMockBuilder('Predis\Connection\AggregateConnectionInterface')->getMock();
$aggregate
->expects($this->once())
->method('getConnectionById')
->with('127.0.0.1:7000')
->willReturn(null);
$client = new Client($aggregate);
$client->getClientBy('id', '127.0.0.1:7000');
}
/**
* @group disconnected
*/
public function testGetClientByMethodSupportsSelectingConnectionByAlias(): void
{
$connection = $this->getMockBuilder('Predis\Connection\ConnectionInterface')->getMock();
$aggregate = $this->getMockBuilder('Predis\Connection\AggregateConnectionInterface')
->addMethods(['getConnectionByAlias'])
->getMockForAbstractClass();
$aggregate
->expects($this->once())
->method('getConnectionByAlias')
->with('myalias')
->willReturn($connection);
$client = new Client($aggregate);
$nodeClient = $client->getClientBy('alias', 'myalias');
$this->assertSame($connection, $nodeClient->getConnection());
$this->assertSame($client->getOptions(), $nodeClient->getOptions());
}
/**
* @group disconnected
*/
public function testGetClientByMethodSupportsSelectingConnectionByKey(): void
{
$connection = $this->getMockBuilder('Predis\Connection\ConnectionInterface')->getMock();
$aggregate = $this->getMockBuilder('Predis\Connection\AggregateConnectionInterface')
->addMethods(['getConnectionByKey'])
->getMockForAbstractClass();
$aggregate
->expects($this->once())
->method('getConnectionByKey')
->with('key:1')
->willReturn($connection);
$client = new Client($aggregate);
$nodeClient = $client->getClientBy('key', 'key:1');
$this->assertSame($connection, $nodeClient->getConnection());
$this->assertSame($client->getOptions(), $nodeClient->getOptions());
}
/**
* @group disconnected
*/
public function testGetClientByMethodSupportsSelectingConnectionBySlot(): void
{
$connection = $this->getMockBuilder('Predis\Connection\ConnectionInterface')->getMock();
$aggregate = $this->getMockBuilder('Predis\Connection\AggregateConnectionInterface')
->addMethods(['getConnectionBySlot'])
->getMockForAbstractClass();
$aggregate
->expects($this->once())
->method('getConnectionBySlot')
->with(5460)
->willReturn($connection);
$client = new Client($aggregate);
$nodeClient = $client->getClientBy('slot', 5460);
$this->assertSame($connection, $nodeClient->getConnection());
$this->assertSame($client->getOptions(), $nodeClient->getOptions());
}
/**
* @group disconnected
*/
public function testGetClientByMethodSupportsSelectingConnectionByRole(): void
{
$connection = $this->getMockBuilder('Predis\Connection\ConnectionInterface')->getMock();
$aggregate = $this->getMockBuilder('Predis\Connection\AggregateConnectionInterface')
->addMethods(['getConnectionByRole'])
->getMockForAbstractClass();
$aggregate
->expects($this->once())
->method('getConnectionByRole')
->with('master')
->willReturn($connection);
$client = new Client($aggregate);
$nodeClient = $client->getClientBy('role', 'master');
$this->assertSame($connection, $nodeClient->getConnection());
$this->assertSame($client->getOptions(), $nodeClient->getOptions());
}
/**
* @group disconnected
*/
public function testGetClientByMethodSupportsSelectingConnectionByCommand(): void
{
$command = RawCommand::create('GET', 'key');
$connection = $this->getMockBuilder('Predis\Connection\ConnectionInterface')->getMock();
$aggregate = $this->getMockBuilder('Predis\Connection\AggregateConnectionInterface')
->onlyMethods(['getConnectionByCommand'])
->getMockForAbstractClass();
$aggregate
->expects($this->once())
->method('getConnectionByCommand')
->with($command)
->willReturn($connection);
$client = new Client($aggregate);
$nodeClient = $client->getClientBy('command', $command);
$this->assertSame($connection, $nodeClient->getConnection());
$this->assertSame($client->getOptions(), $nodeClient->getOptions());
}
/**
* @group disconnected
*/
public function testGetClientByMethodThrowsExceptionWhenSelectingConnectionByUnknownType(): void
{
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage('Invalid selector type: `unknown`');
$client = new Client('tcp://127.0.0.1?alias=node01');
$client->getClientBy('unknown', 'test');
}
/**
* @group disconnected
*/
public function testGetClientByMethodThrowsExceptionWhenConnectionDoesNotSupportSelectorType(): void
{
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage('Selecting connection by id is not supported by Predis\Connection\StreamConnection');
$client = new Client('tcp://127.0.0.1?alias=node01');
$client->getClientBy('id', 'node01');
}
/**
* @group disconnected
*/
public function testPipelineWithoutArgumentsReturnsPipeline(): void
{
$client = new Client();
$this->assertInstanceOf('Predis\Pipeline\Pipeline', $client->pipeline());
}
/**
* @group disconnected
*/
public function testPipelineWithArrayReturnsPipeline(): void
{
$client = new Client();
$this->assertInstanceOf('Predis\Pipeline\Pipeline', $client->pipeline([]));
$this->assertInstanceOf('Predis\Pipeline\Atomic', $client->pipeline(['atomic' => true]));
$this->assertInstanceOf('Predis\Pipeline\FireAndForget', $client->pipeline(['fire-and-forget' => true]));
}
/**
* @group disconnected
*/
public function testPipelineWithCallableExecutesPipeline(): void
{
$callable = $this->getMockBuilder('stdClass')
->addMethods(['__invoke'])
->getMock();
$callable
->expects($this->once())
->method('__invoke')
->with($this->isInstanceOf('Predis\Pipeline\Pipeline'));
$client = new Client();
$client->pipeline($callable);
}
/**
* @group disconnected
*/
public function testPubSubLoopWithoutArgumentsReturnsPubSubConsumer(): void
{
$client = new Client();
$this->assertInstanceOf('Predis\Consumer\PubSub\Consumer', $client->pubSubLoop());
}
/**
* @group disconnected
*/
public function testPubSubLoopWithArrayReturnsPubSubConsumerWithOptions(): void
{
$connection = $this->getMockBuilder('Predis\Connection\NodeConnectionInterface')->getMock();
$options = ['subscribe' => 'channel'];
$client = new Client($connection);
$this->assertInstanceOf('Predis\Consumer\PubSub\Consumer', $pubsub = $client->pubSubLoop($options));
$reflection = new ReflectionProperty($pubsub, 'options');
$reflection->setAccessible(true);
$this->assertSame($options, $reflection->getValue($pubsub));
}
/**
* @group disconnected
*/
public function testPubSubLoopWithArrayAndCallableExecutesPubSub(): void
{
// NOTE: we use a subscribe count of 0 in the message payload to trick
// the context and forcing it to be closed since there are no more
// active subscriptions.
$connection = $this->getMockBuilder('Predis\Connection\NodeConnectionInterface')->getMock();
$connection
->expects($this->once())
->method('read')
->willReturn(['subscribe', 'channel', 0]);
$callable = $this->getMockBuilder('stdClass')
->addMethods(['__invoke'])
->getMock();
$callable
->expects($this->once())
->method('__invoke');
$client = new Client($connection);
$this->assertNull($client->pubSubLoop(['subscribe' => 'channel'], $callable));
}
/**
* @group disconnected
*/
public function testPubSubLoopWithCallableReturningFalseStopsPubSubConsumer(): void
{
$connection = $this->getMockBuilder('Predis\Connection\NodeConnectionInterface')->getMock();
$connection
->expects($this->exactly(2))
->method('read')
->willReturnOnConsecutiveCalls(
['subscribe', 'channel', 1],
['unsubscribe', 'channel', 0]
);
$connection
->expects($this->exactly(2))
->method('writeRequest')
->withConsecutive(
[$this->isRedisCommand('SUBSCRIBE')],
[$this->isRedisCommand('UNSUBSCRIBE')]
);
$callable = $this->getMockBuilder('stdClass')
->addMethods(['__invoke'])
->getMock();
$callable
->expects($this->exactly(2))
->method('__invoke')
->withConsecutive(
[
$this->isInstanceOf('Predis\Consumer\PubSub\Consumer'),
(object) ['kind' => 'subscribe', 'channel' => 'channel', 'payload' => 1],
],
[
$this->isInstanceOf('Predis\Consumer\PubSub\Consumer'),
(object) ['kind' => 'unsubscribe', 'channel' => 'channel', 'payload' => 0],
]
)
->willReturnOnConsecutiveCalls(
false,
null // <-- this value would be ignored as it is the callback to UNSUBSCRIBE
);
$client = new Client($connection);
$this->assertNull($client->pubSubLoop(['subscribe' => 'channel'], $callable));
}
/**
* @group disconnected
*/
public function testTransactionWithoutArgumentsReturnsMultiExec(): void
{
$client = new Client();
$this->assertInstanceOf('Predis\Transaction\MultiExec', $client->transaction());
}
/**
* @group disconnected
*/
public function testTransactionWithArrayReturnsMultiExecTransactionWithOptions(): void
{
$options = ['cas' => true, 'retry' => 3];
$client = new Client();
$this->assertInstanceOf('Predis\Transaction\MultiExec', $tx = $client->transaction($options));
// I hate this part but reflection is the easiest way in this case.
$property = new ReflectionProperty($tx, 'modeCAS');
$property->setAccessible(true);
$this->assertSame($options['cas'], $property->getValue($tx));
$property = new ReflectionProperty($tx, 'attempts');
$property->setAccessible(true);
$this->assertSame($options['retry'], $property->getValue($tx));
}
/**
* @group disconnected
*/
public function testTransactionWithArrayAndCallableExecutesMultiExec(): void
{
// We use CAS here as we don't care about the actual MULTI/EXEC context.
$options = ['cas' => true, 'retry' => 3];
$connection = $this->getMockBuilder('Predis\Connection\NodeConnectionInterface')->getMock();
$connection
->expects($this->once())
->method('executeCommand')
->willReturn(new Response\Status('QUEUED'));
$connection
->expects($this->any())
->method('getParameters')
->willReturn(new Parameters());
$callable = $this->getMockBuilder('stdClass')
->addMethods(['__invoke'])
->getMock();
$callable
->expects($this->once())
->method('__invoke')
->willReturnCallback(static function ($tx) { $tx->ping(); });
$client = new Client($connection);
$client->transaction($options, $callable);
}
/**
* @group disconnected
*/
public function testMonitorReturnsMonitorConsumer(): void
{
$connection = $this->getMockBuilder('Predis\Connection\NodeConnectionInterface')->getMock();
$connection
->expects($this->once())
->method('getParameters')
->willReturn(new Parameters(['protocol' => 2]));
$client = new Client($connection);
$this->assertInstanceOf('Predis\Monitor\Consumer', $monitor = $client->monitor());
}
/**
* @group disconnected
*/
public function testClientResendScriptCommandUsingEvalOnNoScriptErrors(): void
{
$luaScriptBody = 'return redis.call(\'exists\', KEYS[1])';
$command = $this->getMockForAbstractClass('Predis\Command\ScriptCommand', [], '', true, true, true, ['parseResponse']);
$command
->expects($this->once())
->method('getScript')
->willReturn($luaScriptBody);
$command
->expects($this->once())
->method('parseResponse')
->with('OK')
->willReturn(true);
$connection = $this->getMockBuilder('Predis\Connection\NodeConnectionInterface')->getMock();
$connection
->expects($this->exactly(2))
->method('executeCommand')
->withConsecutive(
[$command],
[$this->isRedisCommand('EVAL', [$luaScriptBody])]
)
->willReturnOnConsecutiveCalls(
new Response\Error('NOSCRIPT'),
'OK'
);
$connection
->expects($this->exactly(2))
->method('getParameters')
->willReturn(new Parameters(['protocol' => 2]));
$client = new Client($connection);
$this->assertTrue($client->executeCommand($command));
}
/**
* @group disconnected
*/
public function testGetIteratorWithTraversableConnections(): void
{
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6381');
$connection2 = $this->getMockConnection('tcp://127.0.0.1:6382');
$connection3 = $this->getMockConnection('tcp://127.0.0.1:6383');
$aggregate = new Connection\Cluster\PredisCluster(new Parameters());
$aggregate->add($connection1);
$aggregate->add($connection2);
$aggregate->add($connection3);
$client = new Client($aggregate);
/** @var Iterator */
$iterator = $client->getIterator();
$this->assertInstanceOf('\Predis\Client', $nodeClient = $iterator->current());
$this->assertSame($connection1, $nodeClient->getConnection());
$this->assertSame('127.0.0.1:6381', $iterator->key());
$iterator->next();
$this->assertInstanceOf('\Predis\Client', $nodeClient = $iterator->current());
$this->assertSame($connection2, $nodeClient->getConnection());
$this->assertSame('127.0.0.1:6382', $iterator->key());
$iterator->next();
$this->assertInstanceOf('\Predis\Client', $nodeClient = $iterator->current());
$this->assertSame($connection3, $nodeClient->getConnection());
$this->assertSame('127.0.0.1:6383', $iterator->key());
}
/**
* @group disconnected
*/
public function testGetIteratorWithNonTraversableConnectionNoException(): void
{
$connection = $this->getMockConnection('tcp://127.0.0.1:6381');
$client = new Client($connection);
/** @var Iterator */
$iterator = $client->getIterator();
$this->assertInstanceOf('\Predis\Client', $nodeClient = $iterator->current());
$this->assertSame($connection, $nodeClient->getConnection());
$this->assertSame('127.0.0.1:6381', $iterator->key());
}
/**
* @group disconnected
*/
public function testExecuteCommandRetryCommandOnRetryableException()
{
$mockStream = $this->getMockBuilder(StreamInterface::class)->getMock();
$mockStreamFactory = $this->getMockBuilder(StreamFactoryInterface::class)->getMock();
$parameters = new Parameters([
'retry' => new Retry(new ExponentialBackoff(1000, 10000), 3),
]);
$mockStream
->expects($this->atLeast(3))
->method('close')
->withAnyParameters();
$mockStream
->expects($this->exactly(4))
->method('write')
->withAnyParameters()
->willReturnOnConsecutiveCalls(
$this->throwException(new RuntimeException('', 2)),
$this->throwException(new RuntimeException('', 2)),
$this->throwException(new RuntimeException('', 2)),
1000
);
$mockStream
->expects($this->once())
->method('read')
->withAnyParameters()
->willReturn("+PONG\r\n");
$mockStreamFactory
->expects($this->exactly(4))
->method('createStream')
->withAnyParameters()
->willReturn($mockStream);
$connection = new StreamConnection($parameters, $mockStreamFactory);
$client = new Client($connection);
$this->assertEquals('PONG', $client->ping());
}
/**
* @group connected
* @group relay-incompatible
* @requiresRedisVersion >= 7.2.0
*/
public function testSetClientInfoOnConnection(): void
{
$client = new Client($this->getParameters());
$libName = $client->client('LIST')[0]['lib-name'];
$libVer = $client->client('LIST')[0]['lib-ver'];
$this->assertSame('predis', $libName);
$this->assertSame(Client::VERSION, $libVer);
}
/**
* @group connected
* @requiresRedisVersion >= 5.0.0
*/
public function testClientsCreateDifferentPersistentConnections(): void
{
$client1 = new Client($this->getParameters(['database' => 14, 'persistent' => true, 'conn_uid' => 1]));
$client2 = new Client($this->getParameters(['database' => 15, 'persistent' => true, 'conn_uid' => 2]));
$client1->set('foo', 'bar');
$client2->set('foo', 'baz');
$this->assertSame('bar', $client1->get('foo'));
$this->assertSame('baz', $client2->get('foo'));
$this->assertNotSame($client1->client('ID'), $client2->client('ID'));
$client1->disconnect();
$client2->disconnect();
}
/**
* @group connected
* @requiresRedisVersion >= 5.0.0
*/
public function testClientsCreateSamePersistentConnections(): void
{
$client1 = new Client($this->getParameters(['persistent' => true]));
$client2 = new Client($this->getParameters(['persistent' => true]));
$client1->set('foo', 'bar');
$client2->set('foo', 'baz');
$this->assertSame('baz', $client2->get('foo'));
$this->assertSame($client1->client('ID'), $client2->client('ID'));
$client1->disconnect();
}
/**
* @group connected
* @group cluster
* @requiresRedisVersion >= 2.0.0
*/
public function testClusterClientsCreateDifferentPersistentConnections(): void
{
$client1 = new Client(
$this->getDefaultParametersArray(),
['cluster' => 'redis', 'parameters' => ['persistent' => true, 'conn_uid' => 1]]
);
$client2 = new Client(
$this->getDefaultParametersArray(),
['cluster' => 'redis', 'parameters' => ['persistent' => true, 'conn_uid' => 2]]
);
$client1->set('{shard1}foo', 'bar');
$client2->set('{shard2}foo', 'baz');
$this->assertSame('bar', $client1->get('{shard1}foo'));
$this->assertSame('baz', $client2->get('{shard2}foo'));
$client1->disconnect();
$client2->disconnect();
}
/**
* @group connected
* @group cluster
* @requiresRedisVersion >= 2.0.0
* @requires PHP >= 7.4
* @return void
*/
public function testCreatesClusterWithRelayConnection(): void
{
$client = new Client($this->getDefaultParametersArray(), ['cluster' => 'redis', 'connections' => 'relay']);
$this->assertEquals('OK', $client->set('key', 'value'));
$this->assertSame('value', $client->get('key'));
}
/**
* @group connected
* @group unprotected
* @requiresRedisVersion >= 2.0.0
* @return void
*/
public function testClientAuthenticationAgainstUnprotectedServer(): void
{
$client = new Client($this->getParameters());
$this->assertEquals('OK', $client->set('key', 'value'));
$this->assertSame('value', $client->get('key'));
// AUTH doesn't throw exception if no authentication requires.
$clientWithPassword = new Client($this->getParameters(
['password' => getenv('REDIS_PASSWORD') ?: constant('REDIS_PASSWORD')])
);
$this->assertEquals('OK', $clientWithPassword->set('key', 'value'));
$this->assertSame('value', $clientWithPassword->get('key'));
$this->assertEquals(
'OK',
$client->acl->setUser(
'test_user',
'on',
'>foobar',
'allcommands',
'allkeys'
)
);
$clientTestUser = new Client($this->getParameters(
['username' => 'test_user', 'password' => 'foobar'])
);
$this->assertEquals('test_user', $clientTestUser->acl->whoami());
$this->assertEquals('OK', $clientTestUser->set('key', 'value'));
$this->assertSame('value', $clientTestUser->get('key'));
$this->assertEquals(1, $clientTestUser->acl->delUser('test_user'));
}
/**
* @group connected
* @return void
* @requiresRedisVersion >= 7.0.0
*/
public function testStandaloneNodeRetryCommandExecutionOnTimeoutException(): void
{
$retries = 0;
$mockDisconnect = static function () use (&$retries) {
$streamConnection = new StreamConnection(new Parameters([
'retry' => new Retry(new ExponentialBackoff(1000, 10000), 3),
]));
$disconnectFunc = [$streamConnection, 'disconnect'];
++$retries;
$disconnectFunc();
};
$stubConnection = $this->getMockBuilder(StreamConnection::class)
->setConstructorArgs([new Parameters([
'retry' => new Retry(new ExponentialBackoff(100, 1000), 3),
'read_write_timeout' => 0.1,
])])
->onlyMethods(['disconnect'])
->getMock();
$stubConnection
->expects($this->exactly(7))
->method('disconnect')
->willReturnCallback($mockDisconnect);
$stubConnection->addConnectCommand(new RawCommand('auth', ['foobar']));
$client = new Client($stubConnection);
$this->expectException(TimeoutException::class);
$client->blmpop(3, ['random_key']);
$this->assertEquals(3, $retries);
}
/**
* @group connected
* @group relay-incompatible
* @return void
* @requiresRedisVersion >= 7.0.0
*/
public function testStandaloneNodeRetryCommandExecutionOnTimeoutExceptionIntegration(): void
{
// Retry used to wrap callback around, so we can count retries
$retry = new Retry(new ExponentialBackoff(100, 1000), 3);
$retriesCount = 0;
$retryWrapperFunc = static function (callable $do, ?callable $fail = null) use ($retry, &$retriesCount) {
$failWrapperFunc = static function (Exception $e) use (&$retriesCount, $fail) {
++$retriesCount;
$fail($e);
};
return $retry->callWithRetry($do, $failWrapperFunc);
};
$mockRetry = $this->getMockBuilder(Retry::class)
->setConstructorArgs([new ExponentialBackoff(100, 1000), 3])
->onlyMethods(['callWithRetry'])
->getMock();
$mockRetry
->expects($this->any())
->method('callWithRetry')
->willReturnCallback($retryWrapperFunc);
// Create a real connection with mocked retry and short read_write_timeout
$client = $this->createClient([
'retry' => $mockRetry,
'read_write_timeout' => 0.1,
]);
$this->expectException(TimeoutException::class);
try {
// blmpop with 3 second timeout will exceed the 0.1 second read_write_timeout
// causing TimeoutException to be thrown and retried 3 times before failing
$client->blmpop(3, ['random_key_that_does_not_exist']);
} finally {
$this->assertGreaterThanOrEqual(3, $retriesCount);
}
}
/**
* @group connected
* @group cluster
* @return void
* @requiresRedisVersion >= 2.0.0
*/
public function testClusterRetryCommandExecutionOnTimeoutException(): void
{
$defaultParams = $this->getDefaultParametersArray();
$parsedParams = [];
foreach ($defaultParams as $param) {
$parsedParam = Parameters::parse($param);
$parsedParam['retry'] = new Retry(new ExponentialBackoff(1000, 10000), 3);
$parsedParam['read_write_timeout'] = 0.1;
$parsedParams[] = Parameters::create($parsedParam);
}
$retries = 0;
$mockDisconnect = static function () use (&$retries) {
$streamConnection = new StreamConnection(new Parameters([
'retry' => new Retry(new ExponentialBackoff(1000, 10000), 3),
]));
$disconnectFunc = [$streamConnection, 'disconnect'];
++$retries;
$disconnectFunc();
};
$stubConnection1 = $this->getMockBuilder(StreamConnection::class)
->setConstructorArgs([$parsedParams[0]])
->onlyMethods(['disconnect'])
->getMock();
$stubConnection1
->expects($this->any())
->method('disconnect')
->willReturnCallback($mockDisconnect);
$stubConnection1->addConnectCommand(new RawCommand('auth', [$parsedParams[0]->password]));
$stubConnection2 = $this->getMockBuilder(StreamConnection::class)
->setConstructorArgs([$parsedParams[1]])
->onlyMethods(['disconnect'])
->getMock();
$stubConnection2
->expects($this->any())
->method('disconnect')
->willReturnCallback($mockDisconnect);
$stubConnection2->addConnectCommand(new RawCommand('auth', [$parsedParams[1]->password]));
$stubConnection3 = $this->getMockBuilder(StreamConnection::class)
->setConstructorArgs([$parsedParams[2]])
->onlyMethods(['disconnect'])
->getMock();
$stubConnection3
->expects($this->any())
->method('disconnect')
->willReturnCallback($mockDisconnect);
$stubConnection3->addConnectCommand(new RawCommand('auth', [$parsedParams[2]->password]));
$mockFactory = $this->getMockBuilder(Factory::class)->getMock();
$clusterConnection = new RedisCluster($mockFactory, $parsedParams[0]);
$clusterConnection->add($stubConnection1);
$clusterConnection->add($stubConnection2);
$clusterConnection->add($stubConnection3);
$client = new Client($clusterConnection);
$this->expectException(TimeoutException::class);
$client->blpop(['random_key'], 3);
$this->assertEquals(3, $retries);
}
/**
* @group connected
* @group sentinel
* @group relay-incompatible
* @requiresRedisVersion >= 7.0.0
*/
public function testConnectToSentinelAndExecuteCommands(): void
{
$client = $this->createClient();
$this->assertEquals('PONG', $client->ping());
$client->set('sentinel_test_key', 'sentinel_test_value');
$this->assertEquals('sentinel_test_value', $client->get('sentinel_test_key'));
}
/**
* @group connected
* @group sentinel
* @group relay-incompatible
* @requiresRedisVersion >= 7.0.0
*/
public function testSentinelReplicationConnection(): void
{
$client = $this->createClient();
$connection = $client->getConnection();
$this->assertInstanceOf(SentinelReplication::class, $connection);
// Verify we can get master connection
$master = $connection->getMaster();
$this->assertInstanceOf('Predis\Connection\NodeConnectionInterface', $master);
// Verify master is connected
$this->assertTrue($master->isConnected());
}
// ******************************************************************** //
// ---- HELPER METHODS ------------------------------------------------ //
// ******************************************************************** //
/**
* Returns an URI string representation of the specified connection parameters.
*
* @param array $parameters Array of connection parameters.
*
* @return string URI string.
*/
protected function getParametersString(array $parameters): string
{
$defaults = $this->getDefaultParametersArray();
$scheme = $parameters['scheme'] ?? $defaults['scheme'];
$host = $parameters['host'] ?? $defaults['host'];
$port = $parameters['port'] ?? $defaults['port'];
unset($parameters['scheme'], $parameters['host'], $parameters['port']);
$uriString = "$scheme://$host:$port/?";
foreach ($parameters as $k => $v) {
$uriString .= "$k=$v&";
}
return $uriString;
}
/**
* Returns a mock object simulating an aggregate connection initializer.
*
* @param ParametersInterface|array|string $parameters Expected connection parameters
*
* @return callable|MockObject
*/
protected function getAggregateInitializer($parameters)
{
$connection = $this->getMockBuilder('Predis\Connection\AggregateConnectionInterface')->getMock();
$callable = $this->getMockBuilder('stdClass')
->addMethods(['__invoke'])
->getMock();
$callable
->expects($this->once())
->method('__invoke')
->with(
$parameters,
$this->isInstanceOf('Predis\Configuration\OptionsInterface'),
$this->isInstanceOf('Predis\Configuration\OptionInterface')
)
->willReturn($connection);
return $callable;
}
}