From 72a4a1eba20456324cca4ca09927efae3ec4518a Mon Sep 17 00:00:00 2001 From: David Samblas Date: Sun, 18 Oct 2015 02:58:00 +0200 Subject: [PATCH 1/2] Replace guzzle3 with egeloen/ivory-http-adapter --- behat.yml | 1 + composer.json | 4 +- src/Extension.php | 2 + src/Resources/services.xml | 11 +- src/Rest/RestApiBrowser.php | 150 +++++++++++++++++----------- src/RestApiContext.php | 69 ++++++++++--- tests/Units/Rest/RestApiBrowser.php | 71 +++++++------ 7 files changed, 194 insertions(+), 114 deletions(-) diff --git a/behat.yml b/behat.yml index 965577b..2079192 100644 --- a/behat.yml +++ b/behat.yml @@ -12,3 +12,4 @@ default: rest: base_url: http://localhost:8888 store_response: true + adaptor_name: curl diff --git a/composer.json b/composer.json index 09606e9..de39307 100644 --- a/composer.json +++ b/composer.json @@ -19,9 +19,9 @@ "php": ">=5.3.2", "behat/behat": "~3.0", "atoum/atoum": "~1.0", - "guzzle/http": "~3.9", "symfony/property-access": "~2.4", - "justinrainbow/json-schema": "~1.3" + "justinrainbow/json-schema": "~1.3", + "egeloen/http-adapter": "^0.8.0" }, "require-dev": { "silex/silex": "~1.0", diff --git a/src/Extension.php b/src/Extension.php index a44b43d..ddadc68 100644 --- a/src/Extension.php +++ b/src/Extension.php @@ -15,6 +15,7 @@ class Extension implements ExtensionInterface public function load(ContainerBuilder $container, array $config) { $container->setParameter('rezzza.json_api.rest.base_url', $config['rest']['base_url']); + $container->setParameter('rezzza.json_api.rest.adaptor_name', $config['rest']['adaptor_name']); $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/Resources')); $loader->load('services.xml'); @@ -35,6 +36,7 @@ public function configure(ArrayNodeDefinition $builder) ->scalarNode('base_url')->end() ->booleanNode('store_response') ->defaultTrue()->end() + ->scalarNode('adaptor_name')->end() ->end() ->end() ->end() diff --git a/src/Resources/services.xml b/src/Resources/services.xml index 43b02d7..d4544b0 100644 --- a/src/Resources/services.xml +++ b/src/Resources/services.xml @@ -1,6 +1,6 @@ - @@ -18,12 +18,9 @@ - - %rezzza.json_api.rest.base_url% - - - + %rezzza.json_api.rest.base_url% + %rezzza.json_api.rest.adaptor_name% diff --git a/src/Rest/RestApiBrowser.php b/src/Rest/RestApiBrowser.php index bc45d47..74954c5 100644 --- a/src/Rest/RestApiBrowser.php +++ b/src/Rest/RestApiBrowser.php @@ -2,9 +2,13 @@ namespace Rezzza\RestApiBehatExtension\Rest; -use Guzzle\Http\Exception\BadResponseException; -use Guzzle\Http\ClientInterface as HttpClient; -use Guzzle\Http\Message\Response; +use Ivory\HttpAdapter\HttpAdapterFactory; +use Ivory\HttpAdapter\HttpAdapterInterface as HttpClient; +use Ivory\HttpAdapter\HttpAdapterException; +use Ivory\HttpAdapter\Message\Request; +use Zend\Diactoros\Stream; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; use Behat\Gherkin\Node\PyStringNode; class RestApiBrowser @@ -12,10 +16,10 @@ class RestApiBrowser /** @var HttpClient */ private $httpClient; - /** @var array|\Guzzle\Http\Message\RequestInterface */ + /** @var RequestInterface */ private $request; - /** @var \Guzzle\Http\Message\Response|array */ + /** @var ResponseInterface */ private $response; /** @var array */ @@ -24,21 +28,50 @@ class RestApiBrowser /** @var ResponseStorage */ private $responseStorage; - public function __construct(HttpClient $httpClient) + /** + * @param string $base_url + * @param string|null $adaptor_name + * @throws HttpAdapterException + */ + public function __construct($base_url, $adaptor_name, HttpClient $httpClient = null) { - $this->httpClient = $httpClient; + if (!is_null($httpClient) && $httpClient instanceof HttpClient) { + $this->httpClient = $httpClient; + } else { + if (is_string($adaptor_name) && HttpAdapterFactory::capable($adaptor_name)) { + $this->httpClient = HttpAdapterFactory::create($adaptor_name); + } else { + $this->httpClient = HttpAdapterFactory::guess(); + } + $this->httpClient->getConfiguration()->setBaseUri($base_url); + } } + /** + * @param ResponseStorage $responseStorage + */ public function enableResponseStorage(ResponseStorage $responseStorage) { $this->responseStorage = $responseStorage; } + + /** + * @return ResponseInterface + */ public function getResponse() { return $this->response; } + /** + * @param ResponseInterface $response + */ + public function setResponse(ResponseInterface $response) + { + $this->response = $response; + } + public function getRequest() { return $this->request; @@ -49,20 +82,27 @@ public function getRequestHeaders() return $this->requestHeaders; } + /** + * @return HttpClient + */ + public function getHttpClient() + { + return $this->httpClient; + } + /** * @param string $method * @param string $url - * @param PyStringNode $body - * @param array $options + * @param string $body */ - public function sendRequest($method, $url, $body = null, array $options = array()) + public function sendRequest($method, $url, $body = null) { - $this->createRequest($method, $url, $body, $options); - try { - $this->response = $this->httpClient->send($this->request); - } catch (BadResponseException $e) { - $this->response = $e->getResponse(); + $this->send($method, $url, $body); + } catch (HttpAdapterException $e) { + if ($e->hasResponse()) { + $this->response = $e->getResponse(); + } if (null === $this->response) { throw $e; @@ -70,62 +110,48 @@ public function sendRequest($method, $url, $body = null, array $options = array( } if (null !== $this->responseStorage) { - $this->responseStorage->writeRawContent($this->response->getBody(true)); + $this->responseStorage->writeRawContent($this->response->getBody()->getContents()); } } /** - * @param string $name - * @param string $value + * @param string $method + * @param string $uri With or without host + * @param string|resource|array $body */ - public function addRequestHeader($name, $value) + private function send($method, $uri, $body = null) { - if (isset($this->requestHeaders[$name])) { - if (!is_array($this->requestHeaders[$name])) { - $this->requestHeaders[$name] = array($this->requestHeaders[$name]); - } - $this->requestHeaders[$name][] = $value; - } else { - $this->requestHeaders[$name] = $value; + if (!$this->hasHost($uri)) { + $uri = rtrim($this->httpClient->getConfiguration()->getBaseUri(), '/') . '/' . ltrim($uri, '/'); } + $stream = new Stream('php://memory', 'rw'); + if ($body) { + $stream->write($body); + } + $this->request = new Request($uri, $method, $stream, $this->requestHeaders); + $this->response = $this->httpClient->send($uri, $method, $this->requestHeaders, $body); + // Reset headers used for the HTTP request + $this->requestHeaders = array(); } /** - * @param string $name - * @param string $value - */ - public function setRequestHeader($name, $value) - { - $this->removeRequestHeader($name); - $this->addRequestHeader($name, $value); - } - - /** - * @param Response $response + * @param string $uri + * + * @return bool */ - public function setResponse(Response $response) + private function hasHost($uri) { - $this->response = $response; - if (null !== $this->responseStorage) { - $this->responseStorage->writeRawContent($this->response->getBody(true)); - } + return strpos($uri, '://') !== false; } /** - * @param string $method - * @param string $uri With or without host - * @param string|resource|array $body - * @param array $options + * @param string $name + * @param string $value */ - private function createRequest($method, $uri, $body = null, array $options = array()) + public function setRequestHeader($name, $value) { - if (!$this->hasHost($uri)) { - $uri = rtrim($this->httpClient->getBaseUrl(), '/') . '/' . ltrim($uri, '/'); - } - - $this->request = $this->httpClient->createRequest($method, $uri, $this->requestHeaders, $body, $options); - // Reset headers used for the HTTP request - $this->requestHeaders = array(); + $this->removeRequestHeader($name); + $this->addRequestHeader($name, $value); } private function removeRequestHeader($headerName) @@ -136,12 +162,18 @@ private function removeRequestHeader($headerName) } /** - * @param string $uri - * - * @return bool + * @param string $name + * @param string $value */ - private function hasHost($uri) + public function addRequestHeader($name, $value) { - return strpos($uri, '://') !== false; + if (isset($this->requestHeaders[$name])) { + if (!is_array($this->requestHeaders[$name])) { + $this->requestHeaders[$name] = array($this->requestHeaders[$name]); + } + $this->requestHeaders[$name][] = $value; + } else { + $this->requestHeaders[$name] = $value; + } } } diff --git a/src/RestApiContext.php b/src/RestApiContext.php index a934121..8fccdbf 100644 --- a/src/RestApiContext.php +++ b/src/RestApiContext.php @@ -6,8 +6,8 @@ use Behat\Behat\Context\Context; use Behat\Behat\Context\SnippetAcceptingContext; use Behat\Gherkin\Node\PyStringNode; -use Guzzle\Http\Exception\BadResponseException; -use Guzzle\Http\ClientInterface as HttpClient; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; use Rezzza\RestApiBehatExtension\Rest\RestApiBrowser; class RestApiContext implements Context, SnippetAcceptingContext @@ -41,8 +41,6 @@ public function iSendARequest($method, $url) * @param PyStringNode $body * * @When /^(?:I )?send a ([A-Z]+) request to "([^"]+)" with body:$/ - * @throws BadResponseException - * @throws \Exception */ public function iSendARequestWithBody($method, $url, PyStringNode $body) { @@ -61,6 +59,14 @@ public function theResponseCodeShouldBe($code) $this->asserter->variable($actual)->isEqualTo($expected); } + /** + * @return ResponseInterface + */ + private function getResponse() + { + return $this->restApiBrowser->getResponse(); + } + /** * @Given /^I set "([^"]*)" header equal to "([^"]*)"$/ */ @@ -89,24 +95,33 @@ public function iSetBasicAuthenticationWithAnd($username, $password) } /** - * @Then print response + * @Then print request and response */ - public function printResponse() + public function printRequestAndResponse() { - $request = $this->getRequest(); - $response = $this->getResponse(); + echo "REQUEST:\n"; + $this->printRequest(); + echo "\nRESPONSE:\n"; + $this->printResponse(); + } + /** + * @Then print request + */ + public function printRequest() + { + $request = $this->getRequest(); echo sprintf( "%s %s :\n%s%s\n", $request->getMethod(), - $request->getUrl(), - $response->getRawHeaders(), - $response->getBody() + $request->getUri(), + $this->getRawHeaders($request->getHeaders()), + $request->getBody() ); } /** - * @return array|\Guzzle\Http\Message\RequestInterface + * @return RequestInterface */ private function getRequest() { @@ -114,10 +129,34 @@ private function getRequest() } /** - * @return array|\Guzzle\Http\Message\Response + * @param array $headers + * @return string */ - private function getResponse() + private function getRawHeaders(array $headers) { - return $this->restApiBrowser->getResponse(); + $rawHeaders = ''; + foreach ($headers as $key => $value) { + $rawHeaders .= sprintf("%s: %s\n", $key, is_array($value) ? implode(", ", $value) : $value); + + } + $rawHeaders .= "\n"; + return $rawHeaders; + } + + /** + * @Then print response + */ + public function printResponse() + { + $request = $this->getRequest(); + $response = $this->getResponse(); + + echo sprintf( + "%s %s :\n%s%s\n", + $request->getMethod(), + $request->getUri()->__toString(), + $this->getRawHeaders($response->getHeaders()), + $response->getBody() + ); } } diff --git a/tests/Units/Rest/RestApiBrowser.php b/tests/Units/Rest/RestApiBrowser.php index 48d6bdf..a4471db 100644 --- a/tests/Units/Rest/RestApiBrowser.php +++ b/tests/Units/Rest/RestApiBrowser.php @@ -21,7 +21,7 @@ public function testAddRequestHeader(array $addHeadersSteps, array $expectedHead ->given( $httpClient = $this->mockHttpClient('http://verylastroom.com', 200) ) - ->and($sut = new SUT($httpClient)) + ->and($sut = new SUT(null, null, $httpClient)) ; foreach ($addHeadersSteps as $addHeadersStep) { @@ -35,6 +35,28 @@ public function testAddRequestHeader(array $addHeadersSteps, array $expectedHead ; } + /** + * @param string $baseUrl + * @param int $responseStatusCode + * @param array $headers + * + * @return \Ivory\HttpAdapter\HttpAdapterInterface + */ + private function mockHttpClient($baseUrl, $responseStatusCode, array $headers = array()) + { + $mockHttpClient = new \Ivory\HttpAdapter\MockHttpAdapter(); + $mockHttpClient->getConfiguration()->setBaseUri($baseUrl); + $messageFactory = new \Ivory\HttpAdapter\Message\MessageFactory($baseUrl); + $mockHttpClient->appendResponse( + $messageFactory->createResponse( + $responseStatusCode, + \Ivory\HttpAdapter\Message\RequestInterface::PROTOCOL_VERSION_1_1, + $headers + ) + ); + return $mockHttpClient; + } + public function addHeaderDataProvider() { return array( @@ -54,7 +76,7 @@ public function testSetRequestHeader(array $setHeadersSteps, array $expectedHead ->given( $httpClient = $this->mockHttpClient('http://verylastroom.com', 200) ) - ->and($sut = new SUT($httpClient, null, false)) + ->and($sut = new SUT(null, null, $httpClient)) ; foreach ($setHeadersSteps as $addHeadersStep) { @@ -87,7 +109,7 @@ public function test_get_request($url, array $requestHeaders) // Given $mockHttpClient = $this->mockHttpClient('http://verylastroom.com', 200, array()); - $restApiContext = new SUT($mockHttpClient, null, false); + $restApiContext = new SUT(null, null, $mockHttpClient); foreach ($requestHeaders as $requestHeaderKey => $requestHeaderValue) { $restApiContext->addRequestHeader($requestHeaderKey, $requestHeaderValue); } @@ -97,7 +119,7 @@ public function test_get_request($url, array $requestHeaders) // Then $request = $restApiContext->getRequest(); - $intersect = array_intersect_key($requestHeaders, $request->getHeaders()->toArray()); + $intersect = array_intersect_key($requestHeaders, $request->getHeaders()); $this->array($requestHeaders)->isEqualTo($intersect); } @@ -108,19 +130,22 @@ public function requestDataProvider() array( 'url' => 'http://verylastroom.com/', 'requestHeaders' => array( - array("name" => "value") + "name" => "value" ) ), array( 'url' => 'http://verylastroom.com/', 'requestHeaders' => array( - array("name1" => "value1"), array("name2" => "value2") + "name1" => "value1", + "name2" => "value2" + ) ), array( 'url' => '/?test=a:2', // Without host with weird query string 'requestHeaders' => array( - array("name1" => "value1"), array("name2" => "value2") + "name1" => "value1", + "name2" => "value2" ) ) ); @@ -136,13 +161,14 @@ public function test_create_request_with_slashes_to_clean($baseUrl, $stepUrl, $e { // Given $mockHttpClient = $this->mockHttpClient($baseUrl, 200, array()); - $restApiContext = new SUT($mockHttpClient, null, false); + $restApiContext = new SUT(null, null, $mockHttpClient); // When $restApiContext->sendRequest('GET', $stepUrl); // Then $request = $restApiContext->getRequest(); - $this->string($request->getUrl())->isEqualTo($expectedUrl); + $this->string($request->getUri()->__toString())->isEqualTo($expectedUrl); } + public function urlWithSlashesProvider() { return array( @@ -179,14 +205,14 @@ public function test_get_response($statusCode, array $responseHeaders) // Given $mockHttpClient = $this->mockHttpClient('http://verylastroom.com', $statusCode, $responseHeaders); - $restApiContext = new SUT($mockHttpClient, null, false); + $restApiContext = new SUT(null, null, $mockHttpClient); // When $restApiContext->sendRequest('GET', 'http://verylastroom.com/'); // Then $response = $restApiContext->getResponse(); - $intersect = array_intersect_key($responseHeaders, $response->getHeaders()->toArray()); + $intersect = array_intersect_key($responseHeaders, $response->getHeaders()); $this->array($responseHeaders)->isEqualTo($intersect); } @@ -197,33 +223,16 @@ public function responseDataProvider() array( 'statusCode' => 200, 'requestHeaders' => array( - array("name" => "value") + "name" => "value" ) ), array( 'statusCode' => 400, 'requestHeaders' => array( - array("name1" => "value1"), array("name2" => "value2") + "name1" => "value1", + "name2" => "value2" ) ) ); } - - /** - * @param string $baseUrl - * @param int $responseStatusCode - * @param array $headers - * - * @return \Guzzle\Http\Client - */ - private function mockHttpClient($baseUrl, $responseStatusCode, array $headers = array()) - { - $mockHttpClient = new \mock\Guzzle\Http\Client(); - $mockHttpClient->getMockController()->send = new \Guzzle\Http\Message\Response( - $responseStatusCode, - $headers - ); - $mockHttpClient->getMockController()->getBaseUrl = $baseUrl; - return $mockHttpClient; - } } From 1d908c62aae6c201ae3ebc61513f2d004e16740f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Barray?= Date: Thu, 29 Oct 2015 10:54:50 +0100 Subject: [PATCH 2/2] Add readme note about `adaptor_name` config --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 2772eee..8d405d5 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ default: rest: base_url: http://localhost:8888 store_response: true + adaptor_name: curl # Should be one of these adapters : https://github.com/egeloen/ivory-http-adapter/blob/master/doc/adapters.md#factory suites: default: contexts: @@ -24,6 +25,8 @@ default: - Rezzza\RestApiBehatExtension\Json\JsonContext ``` +Regarding the `adaptor_name` you choose, you will have to install the deps needed on your own. + ## Usage You can use directly the `JsonContext` or `RestApiContext` by loading them in your behat.yml or use the `RestApiBrowser` and `JsonInspector` by adding them in the construct of your own context.