Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start to support php-http #91

Merged
merged 2 commits into from
Feb 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion behat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ default:
contexts:
- FeatureContext:
- "@kernel"
- "@app.api.client"
- "@app.my_buffered_operation_runner"
- AopContext:
- "@app.api.client"
- GuzzleContext:
- "@app.guzzle.middleware.hookable_factory"
- "@app.api.guzzle_client"
Expand All @@ -18,6 +19,12 @@ default:
- TracerContext:
- "@tolerance.tracer.in_memory"

php_http:
filters: { tags: "php_http"}
paths: [ %paths.base%/features/symfony ]
contexts:
- PhpHttpContext

extensions:
Behat\Symfony2Extension:
kernel:
Expand Down
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
"php-amqplib/rabbitmq-bundle": "^1.8",
"beberlei/metrics": "^2.1",
"simple-bus/rabbitmq-bundle-bridge": "^3.0",
"guzzlehttp/promises": "^1.3"
"guzzlehttp/promises": "^1.3",
"php-http/client-common": "^1.4",
"php-http/mock-client": "^0.3.3"
},

"autoload": {
Expand All @@ -37,7 +39,7 @@
"Tolerance\\": "tests/"
}
},

"replace": {
"sroze/tolerance": "self.version"
}
Expand Down
38 changes: 38 additions & 0 deletions docs/bridges/php-http/intro.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
php-http
===============

The Tolerance library comes with a `php-http <http://docs.php-http.org/en/latest/>`_ plugin to support retry operation through Tolerance:

.. code-block:: php

use Http\Client\Common\PluginClient;
use Tolerance\Bridge\PhpHttp\RetryPlugin;
use Tolerance\Waiter\SleepWaiter;
use Tolerance\Waiter\CountLimited;

$this->httpClient = new PluginClient(
$this->delegateHttpClient,
[
new RetryPlugin(new CountLimited(new SleepWaiter(), 3)), // will retry 3 times with 1 second tempo
]
);

By default, Tolerance will retry every request that lead to response status code >= 500. If you want to customize the http status code list you should use

.. code-block:: php

use Http\Client\Common\PluginClient;
use Tolerance\Bridge\PhpHttp\RetryPlugin;
use Tolerance\Bridge\PhpHttp\StatusCodeVoter;
use Tolerance\Waiter\SleepWaiter;
use Tolerance\Waiter\CountLimited;

$this->httpClient = new PluginClient(
$this->delegateHttpClient,
[
new RetryPlugin(
new CountLimited(new SleepWaiter(), 3),
new StatusCodeVoter([502, 504]) // will only retry 502 and 504 response
),
]
);
71 changes: 71 additions & 0 deletions features/symfony/bootstrap/AopContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

use Behat\Behat\Context\Context;
use Tolerance\Bridge\Symfony\Bundle\AppBundle\ThirdParty\ApiClient;
use Tolerance\Bridge\Symfony\Bundle\AppBundle\ThirdParty\ApiException;
use Tolerance\Bridge\Symfony\Bundle\AppBundle\ThirdParty\StepByStepHookApiClient;

class AopContext implements Context
{
/**
* @var StepByStepHookApiClient
*/
private $stepByStepHookApiClient;

/**
* @var ApiClient
*/
private $client;

/**
* @var mixed
*/
private $response;

/**
* @param ApiClient $client
*/
public function __construct(ApiClient $client)
{
$this->stepByStepHookApiClient = $client;
$this->client = $client;
}

/**
* @Given the 3rd party API will fail at the 1st run
*/
public function theRdPartyApiWillFailAtTheStRun()
{
$this->stepByStepHookApiClient->registerStepHook(0, function () {
throw new ApiException('That first step that fails');
});
}

/**
* @Given the 3rd party API will succeed at the 2nd run
*/
public function theRdPartyApiWillSucceedAtTheNdRun()
{
$this->stepByStepHookApiClient->registerStepHook(1, function () {
return 'The good API answer';
});
}

/**
* @When I call my local client service
*/
public function iCallMyLocalClientService()
{
$this->response = $this->client->get();
}

/**
* @Then I should see the call as successful
*/
public function iShouldSeeTheCallAsSuccessful()
{
if ($this->response != 'The good API answer') {
throw new \RuntimeException('The found answer is not matching the expected one');
}
}
}
60 changes: 3 additions & 57 deletions features/symfony/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,11 @@
use Behat\Behat\Context\Context;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Kernel;
use Tolerance\Bridge\Symfony\Bundle\AppBundle\ThirdParty\ApiClient;
use Tolerance\Bridge\Symfony\Bundle\AppBundle\ThirdParty\ApiException;
use Tolerance\Bridge\Symfony\Bundle\AppBundle\ThirdParty\StepByStepHookApiClient;
use Tolerance\Operation\Callback;
use Tolerance\Operation\Runner\BufferedOperationRunner;

class FeatureContext implements Context
{
/**
* @var StepByStepHookApiClient
*/
private $stepByStepHookApiClient;

/**
* @var ApiClient
*/
private $client;

/**
* @var Kernel
*/
Expand All @@ -37,56 +24,25 @@ class FeatureContext implements Context
private $response;

/**
* @param Kernel $kernel
* @param ApiClient $client
* @param Kernel $kernel
* @param BufferedOperationRunner $bufferedOperationRunner
*/
public function __construct(Kernel $kernel, ApiClient $client, BufferedOperationRunner $bufferedOperationRunner)
public function __construct(Kernel $kernel, BufferedOperationRunner $bufferedOperationRunner)
{
$this->stepByStepHookApiClient = $client;
$this->client = $client;
$this->kernel = $kernel;
$this->bufferedOperationRunner = $bufferedOperationRunner;
}

/**
* @Given the 3rd party API will fail at the 1st run
*/
public function theRdPartyApiWillFailAtTheStRun()
{
$this->stepByStepHookApiClient->registerStepHook(0, function () {
throw new ApiException('That first step that fails');
});
}

/**
* @Given the 3rd party API will succeed at the 2nd run
*/
public function theRdPartyApiWillSucceedAtTheNdRun()
{
$this->stepByStepHookApiClient->registerStepHook(1, function () {
return 'The good API answer';
});
}

/**
* @Given there is an operation in a buffered runner
*/
public function thereIsAnOperationInABufferedRunner()
{
$this->bufferedOperationRunner->run(new Callback(function() {
$this->bufferedOperationRunner->run(new Callback(function () {
$this->response = 'Buffered operation ran!';
}));
}

/**
* @When I call my local client service
*/
public function iCallMyLocalClientService()
{
$this->response = $this->client->get();
}

/**
* @When I send a request
*/
Expand Down Expand Up @@ -134,14 +90,4 @@ public function theBufferedOperationShouldNotHaveBeenRun()
throw new \RuntimeException('The buffered operation seems to be not run');
}
}

/**
* @Then I should see the call as successful
*/
public function iShouldSeeTheCallAsSuccessful()
{
if ($this->response != 'The good API answer') {
throw new \RuntimeException('The found answer is not matching the expected one');
}
}
}
65 changes: 65 additions & 0 deletions features/symfony/bootstrap/PhpHttpContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

use Behat\Behat\Context\Context;
use Http\Mock\Client as MockClient;
use Http\Client\Common\PluginClient;
use Tolerance\Bridge\PhpHttp\RetryPlugin;
use Tolerance\Waiter\SleepWaiter;
use Http\Message\MessageFactory\GuzzleMessageFactory;
use function GuzzleHttp\Psr7\copy_to_string;

class PhpHttpContext implements Context
{
private $httpClient;

private $mockClient;

private $response;

public function __construct()
{
$this->mockClient = new MockClient();
$this->httpClient = new PluginClient(
$this->mockClient,
[
new RetryPlugin(new SleepWaiter()),
new \Http\Client\Common\Plugin\ErrorPlugin(),
]
);
$this->messageFactory = new GuzzleMessageFactory();
}

/**
* @Given the 3rd party API will fail at the 1st run
*/
public function theRdPartyApiWillFailAtTheStRun()
{
$this->mockClient->addResponse($this->messageFactory->createResponse(500, 'Internal Error'));
}

/**
* @Given the 3rd party API will succeed at the 2nd run
*/
public function theRdPartyApiWillSucceedAtTheNdRun()
{
$this->mockClient->addResponse($this->messageFactory->createResponse(200, 'ok', [], 'bingo !'));
}

/**
* @When I call my local client service
*/
public function iCallMyLocalClientService()
{
$this->response = $this->httpClient->sendRequest($this->messageFactory->createRequest('GET', '/'));
}

/**
* @Then I should see the call as successful
*/
public function iShouldSeeTheCallAsSuccessful()
{
if (copy_to_string($this->response->getBody()) != 'bingo !') {
throw new \RuntimeException('The found answer is not matching the expected one');
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Feature:
As a developer
I want to be able to only add a tag in the Symfony DIC

@aop @php_http
Scenario: Uses a retry operation runner
Given the 3rd party API will fail at the 1st run
And the 3rd party API will succeed at the 2nd run
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function it_should_catch_promise_exception_if_value_not_instance_of_response()
$this->callOnWrappedObject('shouldCatchThrowable', [new PromiseException('value', true)])->shouldReturn(true);
}

function it_should_catch_promise_exception_if_vaule_is_a_response_and_server_error()
function it_should_catch_promise_exception_if_value_is_a_response_and_server_error()
{
$this->callOnWrappedObject('shouldCatchThrowable', [new PromiseException(new Response(500), true)])->shouldReturn(true);
}
Expand Down
53 changes: 53 additions & 0 deletions spec/Tolerance/Bridge/PhpHttp/RequestServerErrorVoterSpec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace spec\Tolerance\Bridge\PhpHttp;

use PhpSpec\ObjectBehavior;
use Psr\Http\Message\ResponseInterface;
use Tolerance\Operation\Exception\PromiseException;

class RequestServerErrorVoterSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType('Tolerance\Bridge\PhpHttp\RequestServerErrorVoter');
}

function it_should_catch_if_not_promise_exception()
{
$this->callOnWrappedObject('shouldCatchThrowable', [new \RuntimeException()])->shouldReturn(true);
$this->callOnWrappedObject('shouldCatchThrowable', [new \Exception()])->shouldReturn(true);
$this->callOnWrappedObject('shouldCatchThrowable', [new \InvalidArgumentException()])->shouldReturn(true);

// only test this on php >= 7.0
if (70000 <= PHP_VERSION_ID) {
$this->callOnWrappedObject('shouldCatchThrowable', [new \Error()])->shouldReturn(true);
}
}

function it_should_catch_promise_exception_if_rejected(ResponseInterface $response)
{
$this->callOnWrappedObject('shouldCatchThrowable', [new PromiseException($response, false)])->shouldReturn(true);
}

function it_should_catch_promise_exception_if_value_not_instance_of_response()
{
$this->callOnWrappedObject('shouldCatchThrowable', [new PromiseException('value', true)])->shouldReturn(true);
}

function it_should_catch_promise_exception_if_value_is_a_response_and_server_error(PromiseException $exception, ResponseInterface $response)
{
$response->getStatusCode()->willReturn(500);
$exception->getValue()->willReturn($response);
$exception->isRejected()->willReturn(false);
$this->callOnWrappedObject('shouldCatchThrowable', [$exception])->shouldReturn(true);
}

function it_should_not_catch_promise_exception_if_value_is_a_response_and_not_server_error(PromiseException $exception, ResponseInterface $response)
{
$response->getStatusCode()->willReturn(400);
$exception->getValue()->willReturn($response);
$exception->isRejected()->willReturn(false);
$this->callOnWrappedObject('shouldCatchThrowable', [$exception])->shouldReturn(false);
}
}
Loading