diff --git a/src/Command/Command.php b/src/Command/Command.php index 280eb688..1a067b9f 100644 --- a/src/Command/Command.php +++ b/src/Command/Command.php @@ -131,4 +131,25 @@ abstract class Command implements CommandInterface return $argument !== false && $argument !== null; }); } + + /** + * {@inheritDoc} + */ + public function serializeCommand(): string + { + $commandID = $this->getId(); + $arguments = $this->getArguments(); + + $cmdlen = strlen($commandID); + $reqlen = count($arguments) + 1; + + $buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n"; + + foreach ($arguments as $argument) { + $arglen = strlen(strval($argument)); + $buffer .= "\${$arglen}\r\n{$argument}\r\n"; + } + + return $buffer; + } } diff --git a/src/Command/CommandInterface.php b/src/Command/CommandInterface.php index bd2d933b..d29deecb 100644 --- a/src/Command/CommandInterface.php +++ b/src/Command/CommandInterface.php @@ -85,4 +85,11 @@ interface CommandInterface * @return mixed */ public function parseResp3Response($data); + + /** + * Returns RESP-formatted representation of command. + * + * @return string + */ + public function serializeCommand(): string; } diff --git a/src/Command/RawCommand.php b/src/Command/RawCommand.php index 96143918..276a1184 100644 --- a/src/Command/RawCommand.php +++ b/src/Command/RawCommand.php @@ -128,4 +128,25 @@ final class RawCommand implements CommandInterface { return $data; } + + /** + * {@inheritDoc} + */ + public function serializeCommand(): string + { + $commandID = $this->getId(); + $arguments = $this->getArguments(); + + $cmdlen = strlen($commandID); + $reqlen = count($arguments) + 1; + + $buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n"; + + foreach ($arguments as $argument) { + $arglen = strlen(strval($argument)); + $buffer .= "\${$arglen}\r\n{$argument}\r\n"; + } + + return $buffer; + } } diff --git a/src/Connection/AbstractConnection.php b/src/Connection/AbstractConnection.php index fd77050e..238414ac 100644 --- a/src/Connection/AbstractConnection.php +++ b/src/Connection/AbstractConnection.php @@ -31,6 +31,11 @@ abstract class AbstractConnection implements NodeConnectionInterface */ protected $parserStrategy; + /** + * @var int|null + */ + protected $clientId; + private $resource; private $cachedId; @@ -209,6 +214,14 @@ abstract class AbstractConnection implements NodeConnectionInterface return "{$this->parameters->host}:{$this->parameters->port}"; } + /** + * {@inheritDoc} + */ + public function getClientId(): ?int + { + return $this->clientId; + } + /** * {@inheritdoc} */ diff --git a/src/Connection/Factory.php b/src/Connection/Factory.php index 941673bf..46c460b7 100644 --- a/src/Connection/Factory.php +++ b/src/Connection/Factory.php @@ -165,12 +165,20 @@ class Factory implements FactoryInterface $parameters = $connection->getParameters(); if (isset($parameters->password) && strlen($parameters->password)) { - $cmdAuthArgs = isset($parameters->username) && strlen($parameters->username) - ? [$parameters->username, $parameters->password] - : [$parameters->password]; + $cmdAuthArgs = [$parameters->protocol, 'AUTH']; + + isset($parameters->username) && strlen($parameters->username) + ? array_push($cmdAuthArgs, $parameters->username, $parameters->password) + : $cmdAuthArgs[] = $parameters->password; + + array_push($cmdAuthArgs, 'SETNAME', 'predis'); $connection->addConnectCommand( - new RawCommand('AUTH', $cmdAuthArgs) + new RawCommand('HELLO', $cmdAuthArgs) + ); + } else { + $connection->addConnectCommand( + new RawCommand('HELLO', [$parameters->protocol ?? 2, 'SETNAME', 'predis']) ); } @@ -182,12 +190,6 @@ class Factory implements FactoryInterface new RawCommand('CLIENT', ['SETINFO', 'LIB-VER', Client::VERSION]) ); - if (isset($parameters->protocol) && (int) $parameters->protocol > 2) { - $connection->addConnectCommand( - new RawCommand('HELLO', [$parameters->protocol, 'SETNAME', 'predis']) - ); - } - if (isset($parameters->database) && strlen($parameters->database)) { $connection->addConnectCommand( new RawCommand('SELECT', [$parameters->database]) diff --git a/src/Connection/NodeConnectionInterface.php b/src/Connection/NodeConnectionInterface.php index 2efbe1a5..44dbfcf8 100644 --- a/src/Connection/NodeConnectionInterface.php +++ b/src/Connection/NodeConnectionInterface.php @@ -40,6 +40,13 @@ interface NodeConnectionInterface extends ConnectionInterface */ public function getParameters(); + /** + * Returns Client ID assigned by Redis server to current connection. + * + * @return int|null + */ + public function getClientId(): ?int; + /** * Pushes the given command into a queue of commands executed when * establishing the actual connection to Redis. @@ -55,6 +62,15 @@ interface NodeConnectionInterface extends ConnectionInterface */ public function read(); + /** + * Performs a write operation over the stream of the buffer containing a + * command serialized with the Redis wire protocol. + * + * @param string $buffer + * @return void + */ + public function write(string $buffer): void; + /** * Checks if current connection has data to read from server. * diff --git a/src/Connection/ParametersInterface.php b/src/Connection/ParametersInterface.php index 464ef145..84295138 100644 --- a/src/Connection/ParametersInterface.php +++ b/src/Connection/ParametersInterface.php @@ -28,6 +28,7 @@ namespace Predis\Connection; * @property float $timeout Timeout for the connect() operation. * @property float $read_write_timeout Timeout for read() and write() operations. * @property bool $persistent Leaves the connection open after a GC collection. + * @property string $username Username to access Redis (see the AUTH command). * @property string $password Password to access Redis (see the AUTH command). * @property string $database Database index (see the SELECT command). * @property bool $async_connect Performs the connect() operation asynchronously. diff --git a/src/Connection/RelayConnection.php b/src/Connection/RelayConnection.php index 4a948ce9..b124efdd 100644 --- a/src/Connection/RelayConnection.php +++ b/src/Connection/RelayConnection.php @@ -369,4 +369,12 @@ class RelayConnection extends AbstractConnection return $parameters; } + + /** + * {@inheritDoc} + */ + public function write(string $buffer): void + { + throw new NotSupportedException('The "relay" extension does not support writing operations.'); + } } diff --git a/src/Connection/StreamConnection.php b/src/Connection/StreamConnection.php index d910203d..314224f1 100644 --- a/src/Connection/StreamConnection.php +++ b/src/Connection/StreamConnection.php @@ -14,6 +14,7 @@ namespace Predis\Connection; use InvalidArgumentException; use Predis\Command\CommandInterface; +use Predis\Command\RawCommand; use Predis\Consumer\Push\PushNotificationException; use Predis\Consumer\Push\PushResponse; use Predis\Protocol\Parser\Strategy\Resp2Strategy; @@ -235,11 +236,7 @@ class StreamConnection extends AbstractConnection foreach ($this->initCommands as $command) { $response = $this->executeCommand($command); - if ($response instanceof ErrorResponseInterface && ($command->getId() === 'CLIENT')) { - // Do nothing on CLIENT SETINFO command failure - } elseif ($response instanceof ErrorResponseInterface) { - $this->onConnectionError("`{$command->getId()}` failed: {$response->getMessage()}", 0); - } + $this->handleOnConnectResponse($response, $command); } } } @@ -259,12 +256,9 @@ class StreamConnection extends AbstractConnection } /** - * Performs a write operation over the stream of the buffer containing a - * command serialized with the Redis wire protocol. - * - * @param string $buffer Representation of a command in the Redis wire protocol. + * {@inheritDoc} */ - protected function write($buffer) + public function write(string $buffer): void { $socket = $this->getResource(); @@ -373,19 +367,7 @@ class StreamConnection extends AbstractConnection */ public function writeRequest(CommandInterface $command) { - $commandID = $command->getId(); - $arguments = $command->getArguments(); - - $cmdlen = strlen($commandID); - $reqlen = count($arguments) + 1; - - $buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n"; - - foreach ($arguments as $argument) { - $arglen = strlen(strval($argument)); - $buffer .= "\${$arglen}\r\n{$argument}\r\n"; - } - + $buffer = $command->serializeCommand(); $this->write($buffer); } @@ -433,4 +415,63 @@ class StreamConnection extends AbstractConnection return $string; } + + /** + * Handle response from on-connect command. + * + * @param $response + * @param CommandInterface $command + * @return void + */ + private function handleOnConnectResponse($response, CommandInterface $command): void + { + if ($response instanceof ErrorResponseInterface) { + $this->handleError($response, $command); + } + + if ($command->getId() === 'HELLO' && is_array($response)) { + // Searching for the CLIENT ID in RESP2 connection tricky because no dictionaries. + if ( + $this->getParameters()->protocol == 2 + && false !== $key = array_search('id', $response, true) + ) { + $this->clientId = $response[$key + 1]; + } elseif ($this->getParameters()->protocol == 3) { + $this->clientId = $response['id']; + } + } + } + + /** + * Handle server errors. + * + * @param ErrorResponseInterface $error + * @param CommandInterface $failedCommand + * @return void + */ + private function handleError(ErrorResponseInterface $error, CommandInterface $failedCommand): void + { + if ($failedCommand->getId() === 'CLIENT') { + // Do nothing on CLIENT SETINFO command failure + return; + } + + if ($failedCommand->getId() === 'HELLO') { + if (in_array('AUTH', $failedCommand->getArguments(), true)) { + $parameters = $this->getParameters(); + + $auth = new RawCommand('AUTH', [$parameters->username, $parameters->password]); + $response = $this->executeCommand($auth); + $this->handleOnConnectResponse($response, $auth); + } + + $setName = new RawCommand('CLIENT', ['SETNAME', 'predis']); + $response = $this->executeCommand($setName); + $this->handleOnConnectResponse($response, $setName); + + return; + } + + $this->onConnectionError("Failed: {$error->getMessage()}"); + } } diff --git a/tests/Predis/Command/CommandTest.php b/tests/Predis/Command/CommandTest.php index 537f8154..7487facf 100644 --- a/tests/Predis/Command/CommandTest.php +++ b/tests/Predis/Command/CommandTest.php @@ -156,4 +156,27 @@ class CommandTest extends PredisTestCase $arguments = [new stdClass()]; $this->assertSame($arguments, Command::normalizeVariadic($arguments)); } + + /** + * @group disconnected + */ + public function testSerializeCommand(): void + { + $command = new class() extends Command { + public function getId() + { + return 'Test'; + } + + public function getArguments() + { + return ['foo', 'bar']; + } + }; + + $this->assertSame( + "*3\r\n\$4\r\nTest\r\n\$3\r\nfoo\r\n\$3\r\nbar\r\n", + $command->serializeCommand() + ); + } } diff --git a/tests/Predis/Command/RawCommandTest.php b/tests/Predis/Command/RawCommandTest.php index b316d2db..e8484613 100644 --- a/tests/Predis/Command/RawCommandTest.php +++ b/tests/Predis/Command/RawCommandTest.php @@ -138,4 +138,17 @@ class RawCommandTest extends PredisTestCase $this->assertSame('SET', $command->getId()); } + + /** + * @group disconnected + */ + public function testSerializeCommand(): void + { + $command = new RawCommand('Test', ['foo', 'bar']); + + $this->assertSame( + "*3\r\n\$4\r\nTEST\r\n\$3\r\nfoo\r\n\$3\r\nbar\r\n", + $command->serializeCommand() + ); + } } diff --git a/tests/Predis/Command/Redis/CLIENT_Test.php b/tests/Predis/Command/Redis/CLIENT_Test.php index 44be4fd7..976c4567 100644 --- a/tests/Predis/Command/Redis/CLIENT_Test.php +++ b/tests/Predis/Command/Redis/CLIENT_Test.php @@ -194,7 +194,7 @@ BUFFER; { $redis = $this->getClient(); $clientName = $redis->client->getName(); - $this->assertNull($clientName); + $this->assertEquals('predis', $clientName); $expectedConnectionName = 'foo-bar'; $this->assertEquals('OK', $redis->client->setName($expectedConnectionName)); diff --git a/tests/Predis/Connection/CompositeStreamConnectionTest.php b/tests/Predis/Connection/CompositeStreamConnectionTest.php index 2798d797..8bb8461a 100644 --- a/tests/Predis/Connection/CompositeStreamConnectionTest.php +++ b/tests/Predis/Connection/CompositeStreamConnectionTest.php @@ -32,19 +32,21 @@ class CompositeStreamConnectionTest extends PredisConnectionTestCase public function testThrowsExceptionOnInitializationCommandFailure(): void { $this->expectException('Predis\Connection\ConnectionException'); - $this->expectExceptionMessage('`SELECT` failed: ERR invalid DB index [tcp://127.0.0.1:6379]'); + $this->expectExceptionMessage('Failed: ERR invalid DB index [tcp://127.0.0.1:6379]'); $cmdSelect = RawCommand::create('SELECT', '1000'); /** @var NodeConnectionInterface|MockObject */ $connection = $this ->getMockBuilder($this->getConnectionClass()) - ->onlyMethods(['executeCommand', 'createResource']) + ->onlyMethods(['write', 'read', 'createResource']) ->setConstructorArgs([new Parameters()]) ->getMock(); $connection - ->method('executeCommand') - ->with($cmdSelect) + ->expects($this->once()) + ->method('write'); + $connection + ->method('read') ->willReturn( new ErrorResponse('ERR invalid DB index') ); diff --git a/tests/Predis/Connection/FactoryTest.php b/tests/Predis/Connection/FactoryTest.php index c0ac90ab..eb2e50c0 100644 --- a/tests/Predis/Connection/FactoryTest.php +++ b/tests/Predis/Connection/FactoryTest.php @@ -307,7 +307,7 @@ class FactoryTest extends PredisTestCase ->expects($this->exactly(4)) ->method('addConnectCommand') ->withConsecutive( - [$this->isRedisCommand('AUTH', ['foobar'])], + [$this->isRedisCommand('HELLO', [2, 'AUTH', 'foobar', 'SETNAME', 'predis'])], [$this->isRedisCommand('CLIENT', ['SETINFO', 'LIB-NAME', 'predis'])], [$this->isRedisCommand('CLIENT', ['SETINFO', 'LIB-VER', Client::VERSION])], [$this->isRedisCommand('SELECT', ['0'])] @@ -338,7 +338,7 @@ class FactoryTest extends PredisTestCase $connection->expects($this->exactly(3)) ->method('addConnectCommand') ->withConsecutive( - [$this->isRedisCommand('AUTH', ['foobar'])], + [$this->isRedisCommand('HELLO', [2, 'AUTH', 'foobar', 'SETNAME', 'predis'])], [$this->isRedisCommand('CLIENT', ['SETINFO', 'LIB-NAME', 'predis'])], [$this->isRedisCommand('CLIENT', ['SETINFO', 'LIB-VER', Client::VERSION])] ); @@ -369,7 +369,7 @@ class FactoryTest extends PredisTestCase $connection->expects($this->exactly(3)) ->method('addConnectCommand') ->withConsecutive( - [$this->isRedisCommand('AUTH', ['myusername', 'foobar'])], + [$this->isRedisCommand('HELLO', [2, 'AUTH', 'myusername', 'foobar', 'SETNAME', 'predis'])], [$this->isRedisCommand('CLIENT', ['SETINFO', 'LIB-NAME', 'predis'])], [$this->isRedisCommand('CLIENT', ['SETINFO', 'LIB-VER', Client::VERSION])] ); @@ -396,9 +396,10 @@ class FactoryTest extends PredisTestCase $connection->expects($this->once()) ->method('getParameters') ->will($this->returnValue($parameters)); - $connection->expects($this->exactly(2)) + $connection->expects($this->exactly(3)) ->method('addConnectCommand') ->withConsecutive( + [$this->isRedisCommand('HELLO', [2, 'SETNAME', 'predis'])], [$this->isRedisCommand('CLIENT', ['SETINFO', 'LIB-NAME', 'predis'])], [$this->isRedisCommand('CLIENT', ['SETINFO', 'LIB-VER', Client::VERSION])] ); @@ -426,9 +427,10 @@ class FactoryTest extends PredisTestCase $connection->expects($this->once()) ->method('getParameters') ->will($this->returnValue($parameters)); - $connection->expects($this->exactly(2)) + $connection->expects($this->exactly(3)) ->method('addConnectCommand') ->withConsecutive( + [$this->isRedisCommand('HELLO', [2, 'SETNAME', 'predis'])], [$this->isRedisCommand('CLIENT', ['SETINFO', 'LIB-NAME', 'predis'])], [$this->isRedisCommand('CLIENT', ['SETINFO', 'LIB-VER', Client::VERSION])] ); @@ -571,28 +573,35 @@ class FactoryTest extends PredisTestCase $initCommands = $connection->getInitCommands(); $this->assertInstanceOf(RawCommand::class, $initCommands[0]); - $this->assertSame('CLIENT', $initCommands[0]->getId()); - $this->assertSame(['SETINFO', 'LIB-NAME', 'predis'], $initCommands[0]->getArguments()); + $this->assertSame('HELLO', $initCommands[0]->getId()); + $this->assertSame([2, 'SETNAME', 'predis'], $initCommands[0]->getArguments()); $this->assertInstanceOf(RawCommand::class, $initCommands[1]); $this->assertSame('CLIENT', $initCommands[1]->getId()); - $this->assertSame(['SETINFO', 'LIB-VER', Client::VERSION], $initCommands[1]->getArguments()); + $this->assertSame(['SETINFO', 'LIB-NAME', 'predis'], $initCommands[1]->getArguments()); + + $this->assertInstanceOf(RawCommand::class, $initCommands[2]); + $this->assertSame('CLIENT', $initCommands[2]->getId()); + $this->assertSame(['SETINFO', 'LIB-VER', Client::VERSION], $initCommands[2]->getArguments()); } /** + * @dataProvider onConnectionProvider * @group disconnected + * @param array $parameters + * @param array $expectedCommands * @return void */ - public function testCreatesResp3ConnectionOnProtocolParameterGiven(): void + public function testCreatesConnectionWithParameters(array $parameters, array $expectedCommands): void { - $parameters = ['protocol' => 3]; - $factory = new Factory(); $connection = $factory->create($parameters); $initCommands = $connection->getInitCommands(); - $this->assertInstanceOf(RawCommand::class, $initCommands[0]); - $this->assertSame('HELLO', $initCommands[2]->getId()); + for ($i = 0, $iMax = count($initCommands); $i < $iMax; $i++) { + $this->assertSame($expectedCommands[$i]->getId(), $initCommands[$i]->getId()); + $this->assertSameValues($expectedCommands[$i]->getArguments(), $initCommands[$i]->getArguments()); + } } // ******************************************************************** // @@ -634,4 +643,46 @@ class FactoryTest extends PredisTestCase ['database', null], ]; } + + public function onConnectionProvider(): array + { + return [ + 'resp_2_no_auth' => [ + [], + [ + new RawCommand('HELLO', [2, 'SETNAME', 'predis']), + new RawCommand('CLIENT', ['SETINFO', 'LIB-NAME', 'predis']), + new RawCommand('CLIENT', ['SETINFO', 'LIB-VER', Client::VERSION]), + new RawCommand('SELECT', [0]), + ], + ], + 'resp_2_auth' => [ + ['username' => 'foo', 'password' => 'bar'], + [ + new RawCommand('HELLO', [2, 'AUTH', 'foo', 'bar', 'SETNAME', 'predis']), + new RawCommand('CLIENT', ['SETINFO', 'LIB-NAME', 'predis']), + new RawCommand('CLIENT', ['SETINFO', 'LIB-VER', Client::VERSION]), + new RawCommand('SELECT', [0]), + ], + ], + 'resp_3_no_auth' => [ + ['protocol' => 3], + [ + new RawCommand('HELLO', [3, 'SETNAME', 'predis']), + new RawCommand('CLIENT', ['SETINFO', 'LIB-NAME', 'predis']), + new RawCommand('CLIENT', ['SETINFO', 'LIB-VER', Client::VERSION]), + new RawCommand('SELECT', [0]), + ], + ], + 'resp_3_auth' => [ + ['protocol' => 3, 'username' => 'foo', 'password' => 'bar'], + [ + new RawCommand('HELLO', [3, 'AUTH', 'foo', 'bar', 'SETNAME', 'predis']), + new RawCommand('CLIENT', ['SETINFO', 'LIB-NAME', 'predis']), + new RawCommand('CLIENT', ['SETINFO', 'LIB-VER', Client::VERSION]), + new RawCommand('SELECT', [0]), + ], + ], + ]; + } } diff --git a/tests/Predis/Connection/RelayConnectionTest.php b/tests/Predis/Connection/RelayConnectionTest.php index 2726cbf4..c6fa8954 100644 --- a/tests/Predis/Connection/RelayConnectionTest.php +++ b/tests/Predis/Connection/RelayConnectionTest.php @@ -13,6 +13,7 @@ namespace Predis\Connection; use Predis\Command\RawCommand; +use Predis\NotSupportedException; use Predis\Response\Error as ErrorResponse; use PredisTestCase; use Relay\Relay; @@ -61,6 +62,28 @@ class RelayConnectionTest extends PredisTestCase $this->assertTrue($this->connection->isConnected()); } + /** + * @group disconnected + */ + public function testReadThrowsException(): void + { + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessage('The "relay" extension does not support reading responses.'); + + $this->connection->read(); + } + + /** + * @group disconnected + */ + public function testWriteThrowsException(): void + { + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessage('The "relay" extension does not support writing operations.'); + + $this->connection->write('foobar'); + } + /** * @group disconnected * @return void diff --git a/tests/Predis/Connection/StreamConnectionTest.php b/tests/Predis/Connection/StreamConnectionTest.php index 99f5b0e8..88bc3b27 100644 --- a/tests/Predis/Connection/StreamConnectionTest.php +++ b/tests/Predis/Connection/StreamConnectionTest.php @@ -33,19 +33,21 @@ class StreamConnectionTest extends PredisConnectionTestCase public function testThrowsExceptionOnInitializationCommandFailure(): void { $this->expectException('Predis\Connection\ConnectionException'); - $this->expectExceptionMessage('`SELECT` failed: ERR invalid DB index [tcp://127.0.0.1:6379]'); + $this->expectExceptionMessage('Failed: ERR invalid DB index [tcp://127.0.0.1:6379]'); $cmdSelect = RawCommand::create('SELECT', '1000'); /** @var NodeConnectionInterface|MockObject */ $connection = $this ->getMockBuilder($this->getConnectionClass()) - ->onlyMethods(['executeCommand', 'createResource']) + ->onlyMethods(['write', 'read', 'createResource']) ->setConstructorArgs([new Parameters()]) ->getMock(); $connection - ->method('executeCommand') - ->with($cmdSelect) + ->expects($this->once()) + ->method('write'); + $connection + ->method('read') ->willReturn( new ErrorResponse('ERR invalid DB index') ); @@ -215,6 +217,105 @@ class StreamConnectionTest extends PredisConnectionTestCase $this->assertTrue(true); } + /** + * @group connected + * @return void + * @requiresRedisVersion >= 6.2.0 + */ + public function testSetClientIdOnResp2Connection(): void + { + $connection = $this->createConnectionWithParams([]); + $connection->addConnectCommand( + new RawCommand('HELLO', [2]) + ); + $connection->connect(); + + $this->assertNotNull($connection->getClientId()); + } + + /** + * @group connected + * @return void + * @requiresRedisVersion >= 6.2.0 + */ + public function testDoNotSetClientIdOnResp2ConnectionIfNotHelloCommand(): void + { + $connection = $this->createConnectionWithParams([]); + $connection->addConnectCommand( + new RawCommand('INFO') + ); + $connection->connect(); + + $this->assertNull($connection->getClientId()); + } + + /** + * @group connected + * @return void + * @requiresRedisVersion >= 6.2.0 + */ + public function testSetClientIdOnResp3Connection(): void + { + $connection = $this->createConnectionWithParams(['protocol' => 3]); + $connection->addConnectCommand( + new RawCommand('HELLO', [3]) + ); + $connection->connect(); + + $this->assertNotNull($connection->getClientId()); + } + + /** + * @group connected + * @return void + */ + public function testConnectionDoesNotThrowsExceptionOnClientCommandFail(): void + { + $failedCommand = new RawCommand('CLIENT', ['FOOBAR']); + + $connection = $this->createConnection(); + $connection->addConnectCommand($failedCommand); + + $connection->connect(); + + $this->assertTrue(true); + } + + /** + * @group connected + * @return void + * @requiresRedisVersion >= 6.2.0 + */ + public function testConnectionRetriesOnFailingHelloCommand(): void + { + $failedCommand = new RawCommand('HELLO', ['FOOBAR']); + + $connection = $this->createConnection(); + $connection->addConnectCommand($failedCommand); + + $connection->connect(); + + $clientName = $connection->executeCommand(new RawCommand('CLIENT', ['GETNAME'])); + + $this->assertSame('predis', $clientName); + } + + /** + * @group connected + * @return void + * @requiresRedisVersion >= 6.2.0 + */ + public function testConnectionRetriesOnFailingHelloCommandButFailsOnAuth(): void + { + $failedCommand = new RawCommand('HELLO', ['FOOBAR', 'AUTH', 'foobar']); + + $connection = $this->createConnection(); + $connection->addConnectCommand($failedCommand); + + $this->expectException(ConnectionException::class); + $connection->connect(); + } + /** * @group disconnected * @return void