From e81d3435311133526796b7d5e6ee269d880e6345 Mon Sep 17 00:00:00 2001 From: Krzysztof Kubacki Date: Fri, 5 Jan 2018 11:17:34 +0100 Subject: [PATCH] :new: Support multipart request To send files or raw parameters like in form --- composer.json | 4 +- features/bootstrap/fixtures/test-img.jpg | Bin 0 -> 2387 bytes features/send_request.feature | 19 +++++++++ src/Rest/RestApiBrowser.php | 52 ++++++++++++++++++++++- src/RestApiContext.php | 25 +++++++++++ www/index.php | 21 ++++++--- 6 files changed, 112 insertions(+), 9 deletions(-) create mode 100644 features/bootstrap/fixtures/test-img.jpg diff --git a/composer.json b/composer.json index 5f3675c..f0ea52c 100644 --- a/composer.json +++ b/composer.json @@ -36,8 +36,8 @@ "silex/silex": "~2.0", "symfony/process": "~2.1|~3.0", "guzzlehttp/psr7": "^1.3", - "php-http/curl-client": "^1.5", - "php-http/mock-client": "^1.0" + "php-http/mock-client": "^1.0", + "php-http/guzzle6-adapter": "^1.1.1" }, "config": { "optimize-autoloader": true, diff --git a/features/bootstrap/fixtures/test-img.jpg b/features/bootstrap/fixtures/test-img.jpg new file mode 100644 index 0000000000000000000000000000000000000000..670c76c7b893b19f0436aad8cbeebb7280a49530 GIT binary patch literal 2387 zcmaJ=30M=?7M>8mRH7shK*C~}s1zdEAc2G+VgeFtEQY;q4ao#z_5}&c^BH~aC;}oP z_^eu`0#y*Hq9V%T_7pb|L@gqUS|dsowa}VQc(}ap)A!GOckVrN{{NhN?zxsOOD`}f z(kPPx0H5yU2XG2xt1=Ehse~ zWItQ0PaFk;|L>|)S_?2Lt2YRtkNJL9SRa?Efha;quTIyAA=sU0C2QDR9V9ZSb#ZF7 z;(Zlk($ogEK25D5aATQ-uM=e|i8@2?H|j4Cu=y&zL8KBxd|ns`E0AR}2|I!t%;nK% zg+x(VbQ&!(m=zQm5yFb(ajDcuC*1E8aOf!(#e)V zk|>>8Nf_NSTlVE#m|xTz<4V4qOYj%D6gU}*^y5jKD@OV!=ej1hN4TDG}k$ruA$s`7Y zN@Xx+&J6!l!g3nGq5u$Bfkc3S4HkjKA}lRH9)K9bhx>15i?p#vj2kt7Fj^-z2qeZj z*dT4kAtxY4{~>IU0M^#Y&WGk4F(1T!`x3mKfT0tO)e8{Y^%!5F&DJ-Jt9uw zuJWn%i}T#kQL0k>dGh32Nn25(>*BJk1RxrGM{1Joat;2y!UAyC5T%DPHa+C>$!CAQ zUhb}YF{t;=+?>*sJLrnq|F~QjEJ)1v>L{Evz{>u?$WZaep*n> zPQ8w9X&tAE4%lIbb|$sthwp1x`jCWdJ~nRy<3vtd;W%|cbU5bpx@^U)Vrk!tiozt%i10jypxf-> zys*-1cXCpW==gAYMbJ-Ox68JdjvvVU{m8&${LT2mV7vIMm9KA>DRTbU?F>ES#6~j= zdr(fK&5Ng`v}DP-mu3b#M?JgvAbG2u{t#)TBy*#o0#~)2$e8Ry@2O0+cei#f`d!M*%^`Av_46OHf0X=F&q%z zxwI+zQ7#d?HK?s^z}(cbATIaZFZDYMfgd*ZXKMdyOqf?Z@iM+XidKb7I)tX|_1W@U_W6NHZ?>3?!}2*Lv2R~4ELPPV(lt}M69F<{uaYm~bz6W} z9_Q{X@2%u5TyW}eM4d={Wg5@_X;5Q}e)^`$&I6a_Hgoc?UKvcTOz~GGTwEqcRfkv0 z-*u35EjhWIs@#9JiX=x~RNq%^*in3U_63iFJtg%r653eMisEpT-Umv%6sMXKwAI?0 zxod#tif{N8vo%RCXE0Ta2hX}voiCtX^(7q=xNk{%=_YjaXup?3CUde9wRZ^s*V8lQ znr3Z3m=n-1f0W^wnK~Sb-P3m!d;VSyfxc^5W#Q#%(dk)wu(}Cf*XFZ2fidf{( zCDLCum$J~lO*LjmPJL+Jce1G%35Q0i@tMzm(^1j8arLG(x!a$VC%2pVQYq(F$wB_X kp=eah1Z_{vGR5>gu2)0u3AgTx*X{G01Ajj^kSw?U2HEw9zyJUM literal 0 HcmV?d00001 diff --git a/features/send_request.feature b/features/send_request.feature index 31e51d6..e33eb7d 100644 --- a/features/send_request.feature +++ b/features/send_request.feature @@ -40,3 +40,22 @@ Feature: Test send API request Then the response status code should be 200 And the JSON node "headers.header" should have 1 element And the JSON node "headers.header[0]" should be equal to "value2" + + Scenario: Attaching files and sending POST request + Given I add "Content-type" header equal to "application/json" + And I attach the following files: + | name | path | + | json-schema | features/bootstrap/fixtures/json-schema.json | + | test-img | features/bootstrap/fixtures/test-img.jpg | + When I send a POST request to "post-with-files" with parameters: + | username | pablo | + | password | money | + | terms_accepted | 1 | + Then the response status code should be 200 + And the response should be in JSON + And the JSON node "post_files_count" should be equal to "2" + And the JSON node "post_fields.username" should be equal to "pablo" + And the JSON node "post_fields.password" should be equal to "money" + And the JSON node "post_fields.terms_accepted" should be equal to "1" + And the JSON node "content_type_header_value" should not contain "application/json" + And the JSON node "content_type_header_value" should contain "multipart/form-data" diff --git a/src/Rest/RestApiBrowser.php b/src/Rest/RestApiBrowser.php index 17669f9..44a7214 100644 --- a/src/Rest/RestApiBrowser.php +++ b/src/Rest/RestApiBrowser.php @@ -27,6 +27,9 @@ class RestApiBrowser /** @var array */ private $requestHeaders = []; + /** @var array */ + private $requestFiles = []; + /** @var ResponseStorage */ private $responseStorage; @@ -99,6 +102,10 @@ public function sendRequest($method, $uri, $body = null) $uri = rtrim($this->host, '/').'/'.ltrim($uri, '/'); } + if (is_array($body)) { + $body = $this->buildMultipartBody($body); + } + $this->request = $this->messageFactory->createRequest($method, $uri, $this->requestHeaders, $body); $this->response = $this->httpClient->sendRequest($this->request); $this->requestHeaders = []; @@ -138,6 +145,7 @@ public function setRequestHeader($name, $value) */ public function addRequestHeader($name, $value) { + $name = strtolower($name); if (isset($this->requestHeaders[$name])) { $this->requestHeaders[$name] .= ', '.$value; } else { @@ -150,11 +158,53 @@ public function addRequestHeader($name, $value) */ private function removeRequestHeader($headerName) { + $headerName = strtolower($headerName); if (array_key_exists($headerName, $this->requestHeaders)) { unset($this->requestHeaders[$headerName]); } } + /** + * @param string $name + * @param string $path + */ + public function addFileToRequest($name, $path) + { + $this->requestFiles[] = [ + 'name' => $name, + 'path' => $path, + ]; + } + + /** + * @param array $body + * + * @return \GuzzleHttp\Psr7\MultipartStream + */ + private function buildMultipartBody($body) + { + $multiparts = array_merge( + array_map( + function ($key, $value) { + return ['name' => $key, 'contents' => $value]; + }, + array_keys($body), + $body + ), + array_map( + function ($file) { + return ['name' => $file['name'], 'contents' => fopen($file['path'], 'r')]; + }, + $this->requestFiles + ) + ); + + $boundary = sha1(uniqid('', true)); + $this->setRequestHeader('Content-Type', 'multipart/form-data; boundary='.$boundary); + + return new \GuzzleHttp\Psr7\MultipartStream($multiparts, $boundary); + } + /** * @param string $uri * @@ -162,6 +212,6 @@ private function removeRequestHeader($headerName) */ private function hasHost($uri) { - return strpos($uri, '://') !== false; + return false !== strpos($uri, '://'); } } diff --git a/src/RestApiContext.php b/src/RestApiContext.php index 560a79a..3206c12 100644 --- a/src/RestApiContext.php +++ b/src/RestApiContext.php @@ -8,6 +8,7 @@ use Behat\Gherkin\Node\PyStringNode; use Psr\Http\Message\ResponseInterface; use Ubirak\RestApiBehatExtension\Rest\RestApiBrowser; +use Behat\Gherkin\Node\TableNode; class RestApiContext implements Context, SnippetAcceptingContext { @@ -46,6 +47,30 @@ public function iSendARequestWithBody($method, $url, PyStringNode $body) $this->restApiBrowser->sendRequest($method, $url, (string) $body); } + /** + * Sends HTTP request to specific URL with raw body from PyString. + * + * @param string $method request method + * @param string $url relative url + * @param TableNode $parameters + * + * @When /^(?:I )?send a ([A-Z]+) request to "([^"]+)" with parameters:$/ + */ + public function iSendARequestWithParameters($method, $url, TableNode $parameters = null) + { + $this->restApiBrowser->sendRequest($method, $url, $parameters->getRowsHash()); + } + + /** + * @When I attach the following files: + */ + public function iAttachTheFollowingFiles(TableNode $files) + { + foreach ($files as $file) { + $this->restApiBrowser->addFileToRequest($file['name'], $file['path']); + } + } + /** * @param string $code status code * diff --git a/www/index.php b/www/index.php index b3d112d..b1a71af 100644 --- a/www/index.php +++ b/www/index.php @@ -1,20 +1,18 @@ match( 'echo', - function(Request $req) { + function (Request $req) { $ret = array( 'warning' => 'Do not expose this service in production : it is intrinsically unsafe', ); @@ -57,7 +55,7 @@ function(Request $req) { $app->match( 'error_random', function (Request $request) { - $statusCode = time() % 3 <= 0 ? 200 : 502 ; + $statusCode = time() % 3 <= 0 ? 200 : 502; return new JsonResponse([], $statusCode); } @@ -69,4 +67,15 @@ function (Request $request) { } ); +$app->match( + 'post-with-files', + function (Request $request) { + return new JsonResponse([ + 'content_type_header_value' => $request->headers->get('content-type'), + 'post_files_count' => count($request->files), + 'post_fields' => $request->request->all(), + ]); + } +); + $app->run();