diff --git a/examples/cli.php b/examples/cli.php index 6da8c25..f4dd537 100644 --- a/examples/cli.php +++ b/examples/cli.php @@ -50,8 +50,10 @@ }); }, function (Exception $error) { echo 'CONNECTION ERROR: ' . $error->getMessage() . PHP_EOL; + if ($error->getPrevious()) { + echo $error->getPrevious()->getMessage() . PHP_EOL; + } exit(1); }); - $loop->run(); diff --git a/examples/incr.php b/examples/incr.php index f61a033..0eaaa32 100644 --- a/examples/incr.php +++ b/examples/incr.php @@ -12,6 +12,12 @@ $client->get('test')->then(function ($result) { var_dump($result); +}, function (Exception $e) { + echo 'Error: ' . $e->getMessage() . PHP_EOL; + if ($e->getPrevious()) { + echo $e->getPrevious()->getMessage() . PHP_EOL; + } + exit(1); }); $client->end(); diff --git a/examples/publish.php b/examples/publish.php index e4fe494..4da3c17 100644 --- a/examples/publish.php +++ b/examples/publish.php @@ -12,7 +12,13 @@ $client = $factory->createLazyClient('localhost'); $client->publish($channel, $message)->then(function ($received) { - echo 'successfully published. Received by ' . $received . PHP_EOL; + echo 'Successfully published. Received by ' . $received . PHP_EOL; +}, function (Exception $e) { + echo 'Unable to publish: ' . $e->getMessage() . PHP_EOL; + if ($e->getPrevious()) { + echo $e->getPrevious()->getMessage() . PHP_EOL; + } + exit(1); }); $client->end(); diff --git a/src/Factory.php b/src/Factory.php index d9491a5..5f26169 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -55,7 +55,7 @@ public function createClient($target) $connecting = $this->connector->connect($parts['authority']); $deferred = new Deferred(function ($_, $reject) use ($connecting) { // connection cancelled, start with rejecting attempt, then clean up - $reject(new \RuntimeException('Connection to database server cancelled')); + $reject(new \RuntimeException('Connection to Redis server cancelled')); // either close successful connection or cancel pending connection attempt $connecting->then(function (ConnectionInterface $connection) { @@ -67,6 +67,12 @@ public function createClient($target) $protocol = $this->protocol; $promise = $connecting->then(function (ConnectionInterface $stream) use ($protocol) { return new StreamingClient($stream, $protocol->createResponseParser(), $protocol->createSerializer()); + }, function (\Exception $e) { + throw new \RuntimeException( + 'Connection to Redis server failed because underlying transport connection failed', + 0, + $e + ); }); if (isset($parts['auth'])) { @@ -77,7 +83,12 @@ function () use ($client) { }, function ($error) use ($client) { $client->close(); - throw $error; + + throw new \RuntimeException( + 'Connection to Redis server failed because AUTH command failed', + 0, + $error + ); } ); }); @@ -91,7 +102,12 @@ function () use ($client) { }, function ($error) use ($client) { $client->close(); - throw $error; + + throw new \RuntimeException( + 'Connection to Redis server failed because SELECT command failed', + 0, + $error + ); } ); }); @@ -108,7 +124,7 @@ function ($error) use ($client) { return \React\Promise\Timer\timeout($deferred->promise(), $timeout, $this->loop)->then(null, function ($e) { if ($e instanceof TimeoutException) { throw new \RuntimeException( - 'Connection to database server timed out after ' . $e->getTimeout() . ' seconds' + 'Connection to Redis server timed out after ' . $e->getTimeout() . ' seconds' ); } throw $e; diff --git a/tests/FactoryStreamingClientTest.php b/tests/FactoryStreamingClientTest.php index 89c5555..8c43f2b 100644 --- a/tests/FactoryStreamingClientTest.php +++ b/tests/FactoryStreamingClientTest.php @@ -131,6 +131,43 @@ public function testWillWriteAuthCommandIfRedisUnixUriContainsUserInfo() $this->factory->createClient('redis+unix://hello:world@/tmp/redis.sock'); } + public function testWillResolveWhenAuthCommandReceivesOkResponseIfRedisUriContainsUserInfo() + { + $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write'))->getMock(); + $stream->expects($this->once())->method('write')->with("*2\r\n$4\r\nauth\r\n$5\r\nworld\r\n"); + + $this->connector->expects($this->once())->method('connect')->willReturn(Promise\resolve($stream)); + $promise = $this->factory->createClient('redis://:world@localhost'); + + $stream->emit('data', array("+OK\r\n")); + + $promise->then($this->expectCallableOnceWith($this->isInstanceOf('Clue\React\Redis\Client'))); + } + + public function testWillRejectAndCloseAutomaticallyWhenAuthCommandReceivesErrorResponseIfRedisUriContainsUserInfo() + { + $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock(); + $stream->expects($this->once())->method('write')->with("*2\r\n$4\r\nauth\r\n$5\r\nworld\r\n"); + $stream->expects($this->once())->method('close'); + + $this->connector->expects($this->once())->method('connect')->willReturn(Promise\resolve($stream)); + $promise = $this->factory->createClient('redis://:world@localhost'); + + $stream->emit('data', array("-ERR invalid password\r\n")); + + $promise->then(null, $this->expectCallableOnceWith( + $this->logicalAnd( + $this->isInstanceOf('RuntimeException'), + $this->callback(function (\Exception $e) { + return $e->getMessage() === 'Connection to Redis server failed because AUTH command failed'; + }), + $this->callback(function (\Exception $e) { + return $e->getPrevious()->getMessage() === 'ERR invalid password'; + }) + ) + )); + } + public function testWillWriteSelectCommandIfRedisUnixUriContainsDbQueryParameter() { $stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock(); @@ -140,19 +177,63 @@ public function testWillWriteSelectCommandIfRedisUnixUriContainsDbQueryParameter $this->factory->createClient('redis+unix:///tmp/redis.sock?db=demo'); } + public function testWillResolveWhenSelectCommandReceivesOkResponseIfRedisUriContainsPath() + { + $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write'))->getMock(); + $stream->expects($this->once())->method('write')->with("*2\r\n$6\r\nselect\r\n$3\r\n123\r\n"); + + $this->connector->expects($this->once())->method('connect')->willReturn(Promise\resolve($stream)); + $promise = $this->factory->createClient('redis://localhost/123'); + + $stream->emit('data', array("+OK\r\n")); + + $promise->then($this->expectCallableOnceWith($this->isInstanceOf('Clue\React\Redis\Client'))); + } + + public function testWillRejectAndCloseAutomaticallyWhenSelectCommandReceivesErrorResponseIfRedisUriContainsPath() + { + $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock(); + $stream->expects($this->once())->method('write')->with("*2\r\n$6\r\nselect\r\n$3\r\n123\r\n"); + $stream->expects($this->once())->method('close'); + + $this->connector->expects($this->once())->method('connect')->willReturn(Promise\resolve($stream)); + $promise = $this->factory->createClient('redis://localhost/123'); + + $stream->emit('data', array("-ERR DB index is out of range\r\n")); + + $promise->then(null, $this->expectCallableOnceWith( + $this->logicalAnd( + $this->isInstanceOf('RuntimeException'), + $this->callback(function (\Exception $e) { + return $e->getMessage() === 'Connection to Redis server failed because SELECT command failed'; + }), + $this->callback(function (\Exception $e) { + return $e->getPrevious()->getMessage() === 'ERR DB index is out of range'; + }) + ) + )); + } + public function testWillRejectIfConnectorRejects() { $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:2')->willReturn(Promise\reject(new \RuntimeException())); $promise = $this->factory->createClient('redis://127.0.0.1:2'); - $this->expectPromiseReject($promise); + $promise->then(null, $this->expectCallableOnceWith( + $this->logicalAnd( + $this->isInstanceOf('RuntimeException'), + $this->callback(function (\Exception $e) { + return $e->getMessage() === 'Connection to Redis server failed because underlying transport connection failed'; + }) + ) + )); } public function testWillRejectIfTargetIsInvalid() { $promise = $this->factory->createClient('http://invalid target'); - $this->expectPromiseReject($promise); + $promise->then(null, $this->expectCallableOnceWith($this->isInstanceOf('InvalidArgumentException'))); } public function testCancelWillRejectPromise() @@ -173,6 +254,15 @@ public function testCancelWillCancelConnectorWhenConnectionIsPending() $promise = $this->factory->createClient('redis://127.0.0.1:2'); $promise->cancel(); + + $promise->then(null, $this->expectCallableOnceWith( + $this->logicalAnd( + $this->isInstanceOf('RuntimeException'), + $this->callback(function (\Exception $e) { + return $e->getMessage() === 'Connection to Redis server cancelled'; + }) + ) + )); } public function testCancelWillCloseConnectionWhenConnectionWaitsForSelect() @@ -185,6 +275,15 @@ public function testCancelWillCloseConnectionWhenConnectionWaitsForSelect() $promise = $this->factory->createClient('redis://127.0.0.1:2/123'); $promise->cancel(); + + $promise->then(null, $this->expectCallableOnceWith( + $this->logicalAnd( + $this->isInstanceOf('RuntimeException'), + $this->callback(function (\Exception $e) { + return $e->getMessage() === 'Connection to Redis server cancelled'; + }) + ) + )); } public function testCreateClientWithTimeoutParameterWillStartTimerAndRejectOnExplicitTimeout() @@ -205,9 +304,9 @@ public function testCreateClientWithTimeoutParameterWillStartTimerAndRejectOnExp $promise->then(null, $this->expectCallableOnceWith( $this->logicalAnd( - $this->isInstanceOf('Exception'), + $this->isInstanceOf('RuntimeException'), $this->callback(function (\Exception $e) { - return $e->getMessage() === 'Connection to database server timed out after 0 seconds'; + return $e->getMessage() === 'Connection to Redis server timed out after 0 seconds'; }) ) ));