Skip to content

Commit

Permalink
Merge pull request #89 from clue-labs/exception
Browse files Browse the repository at this point in the history
Improve Exception messages for connection issues
  • Loading branch information
clue authored Mar 9, 2019
2 parents b8582fa + 20859c2 commit 3a84482
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 10 deletions.
4 changes: 3 additions & 1 deletion examples/cli.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
6 changes: 6 additions & 0 deletions examples/incr.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
8 changes: 7 additions & 1 deletion examples/publish.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
24 changes: 20 additions & 4 deletions src/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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'])) {
Expand All @@ -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
);
}
);
});
Expand All @@ -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
);
}
);
});
Expand All @@ -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;
Expand Down
107 changes: 103 additions & 4 deletions tests/FactoryStreamingClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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';
})
)
));
Expand Down

0 comments on commit 3a84482

Please sign in to comment.