Skip to content

Commit

Permalink
Allow HTTP PATCH (and other methods) with form parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
Tartare2240 authored and christeredvartsen committed Apr 9, 2017
1 parent ba50d04 commit 18b5a42
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 16 deletions.
6 changes: 6 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Changelog for Behat API Extension
=================================

v2.0.1
------
__N/A__

* #48: Allow HTTP PATCH (and other HTTP methods) with form parameters

v2.0.0
------
__2017-04-01__
Expand Down
4 changes: 3 additions & 1 deletion docs/guide/setup-request.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,12 @@ This step can be used to set form parameters (as if the request is a ``<form>``
| bar | foo |
| bar | bar |
The first row in the table must contain two values: ``name`` and ``value``. The rows that follows are the fields / values you want to send. This step sets the HTTP method to ``POST`` and the ``Content-Type`` request header to ``application/x-www-form-urlencoded``, unless the step is combined with :ref:`given-i-attach-path-to-the-request-as-partname`, in which case the ``Content-Type`` request header will be set to ``multipart/form-data`` and all the specified fields will be sent as parts in the multipart request.
The first row in the table must contain two values: ``name`` and ``value``. The rows that follows are the fields / values you want to send. This step sets the HTTP method to ``POST`` by default and the ``Content-Type`` request header to ``application/x-www-form-urlencoded``, unless the step is combined with :ref:`given-i-attach-path-to-the-request-as-partname`, in which case the ``Content-Type`` request header will be set to ``multipart/form-data`` and all the specified fields will be sent as parts in the multipart request.

This step can not be used when sending requests with a request body. Doing so results in an ``InvalidArgumentException`` exception.

To use a different HTTP method, simply specify the wanted method in the :ref:`when-i-request-path-using-http-method` step.

Given the request body is: ``<PyStringNode>``
---------------------------------------------

Expand Down
9 changes: 7 additions & 2 deletions features/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -172,18 +172,23 @@ public function assertCommandOutputMatches(PyStringNode $content) {
public function assertCommandResult($result) {
$exitCode = $this->getExitCode();

// Escape % as the callback will pass this value to sprintf() if the assertion fails, and
// sprintf might complain about too few arguments as the output might contain stuff like %s
// or %d.
$output = str_replace('%', '%%', $this->getOutput());

if ($result === 'fail') {
$callback = 'notEq';
$errorMessage = sprintf(
'Invalid exit code, did not expect 0. Command output: %s',
$this->getOutput()
$output
);
} else {
$callback = 'eq';
$errorMessage = sprintf(
'Expected exit code 0, got %d. Command output: %s',
$exitCode,
$this->getOutput()
$output
);
}

Expand Down
12 changes: 9 additions & 3 deletions features/bootstrap/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,16 @@
});

/**
* Return information about $_POST and $_FILES vars
* Return information about the request
*/
$app->post('/formData', function(Request $request) {
return new JsonResponse(['_POST' => $_POST, '_FILES' => $_FILES]);
$app->match('/requestInfo', function(Request $request) {
return new JsonResponse([
'_GET' => $_GET,
'_POST' => $_POST,
'_FILES' => $_FILES,
'_SERVER' => $_SERVER,
'requestBody' => $request->getContent(),
]);
});

/**
Expand Down
46 changes: 42 additions & 4 deletions features/form-data.feature
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Feature: Test form-data handling
contexts: ['Imbo\BehatApiExtension\Context\ApiContext']
"""

Scenario: Attach form data to the request
Scenario: Attach form data to the request with no HTTP method specified
Given a file named "features/attach-form-data.feature" with:
"""
Feature: Set up the request
Expand All @@ -29,15 +29,18 @@ Feature: Test form-data handling
| foo | bar |
| bar | foo |
| bar | bar |
When I request "/formData"
When I request "/requestInfo"
Then the response body contains JSON:
'''
{
"_POST": {
"foo": "bar",
"bar": ["foo", "bar"]
},
"_FILES": "@arrayLength(0)"
"_FILES": "@arrayLength(0)",
"_SERVER": {
"REQUEST_METHOD": "POST"
}
}
'''
Expand All @@ -51,6 +54,38 @@ Feature: Test form-data handling
3 steps (3 passed)
"""

Scenario: Attach form data to the request with custom HTTP method
Given a file named "features/attach-form-data-http-patch.feature" with:
"""
Feature: Set up the request
Scenario: Use the Given step to attach form-data
Given the following form parameters are set:
| name | value |
| foo | bar |
| bar | foo |
| bar | bar |
When I request "/requestInfo" using HTTP PATCH
Then the response body contains JSON:
'''
{
"_POST": "@arrayLength(0)",
"_SERVER": {
"REQUEST_METHOD": "PATCH"
},
"requestBody": "foo=bar&bar%5B0%5D=foo&bar%5B1%5D=bar"
}
'''
"""
When I run "behat features/attach-form-data-http-patch.feature"
Then it should pass with:
"""
...
1 scenario (1 passed)
3 steps (3 passed)
"""

Scenario: Attach form data and files to the request
Given a file named "features/attach-form-data-and-files.feature" with:
"""
Expand All @@ -62,7 +97,7 @@ Feature: Test form-data handling
| bar | foo |
| bar | bar |
And I attach "behat.yml" to the request as file
When I request "/formData"
When I request "/requestInfo"
Then the response body contains JSON:
'''
{
Expand All @@ -78,6 +113,9 @@ Feature: Test form-data handling
"error": 0,
"size": "@regExp(/[0-9]+/)"
}
},
"_SERVER": {
"REQUEST_METHOD": "POST"
}
}
'''
Expand Down
23 changes: 17 additions & 6 deletions src/Context/ApiContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ class ApiContext implements ApiClientAwareContext, ArrayContainsComparatorAwareC
*/
protected $arrayContainsComparator;

/**
* Does HTTP method has been manually set
*
* @var bool
*/
protected $httpMethodSpecified = false;

/**
* {@inheritdoc}
*/
Expand Down Expand Up @@ -256,11 +263,14 @@ public function setRequestBodyToFileResource($path) {
* @When I request :path
* @When I request :path using HTTP :method
*/
public function requestPath($path, $method = 'GET') {
return $this
->setRequestPath($path)
->setRequestMethod($method)
->sendRequest();
public function requestPath($path, $method = null) {
$this->setRequestPath($path);

if (null !== $method) {
$this->setRequestMethod($method);
}

return $this->sendRequest();
}

/**
Expand Down Expand Up @@ -914,7 +924,7 @@ public function assertResponseBodyContainsJson(PyStringNode $contains) {
* @return self
*/
protected function sendRequest() {
if (!empty($this->requestOptions['form_params'])) {
if (!empty($this->requestOptions['form_params']) && !$this->httpMethodSpecified) {
$this->setRequestMethod('POST');
}

Expand Down Expand Up @@ -1076,6 +1086,7 @@ protected function setRequestPath($path) {
*/
protected function setRequestMethod($method) {
$this->request = $this->request->withMethod($method);
$this->httpMethodSpecified = true;

return $this;
}
Expand Down
43 changes: 43 additions & 0 deletions tests/Context/ApiContextTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ public function getHttpMethods() {
['method' => 'HEAD'],
['method' => 'OPTIONS'],
['method' => 'DELETE'],
['method' => 'PATCH'],
];
}

Expand Down Expand Up @@ -556,6 +557,48 @@ public function testCanSetFormParametersInTheRequest() {
$this->assertSame('foo=bar&bar%5B0%5D=foo&bar%5B1%5D=bar', (string) $request->getBody());
}

/**
* Data provider
*
* @return array[]
*/
public function getHttpMethodsForFormParametersTest() {
return [
['httpMethod' => 'PUT'],
['httpMethod' => 'POST'],
['httpMethod' => 'PATCH'],
['httpMethod' => 'DELETE'],
];
}

/**
* @dataProvider getHttpMethodsForFormParametersTest
* @covers ::setRequestFormParams
* @covers ::sendRequest
* @group setup
*
* @param string $httpMethod The HTTP method
*/
public function testCanSetFormParametersInTheRequestWithCustomMethod($httpMethod) {
$this->mockHandler->append(new Response(200));
$this->assertSame($this->context, $this->context->setRequestFormParams(new TableNode([
['name', 'value'],
['foo', 'bar'],
['bar', 'foo'],
['bar', 'bar'],
])));
$this->context->requestPath('/some/path', $httpMethod);

$this->assertSame(1, count($this->historyContainer));

$request = $this->historyContainer[0]['request'];

$this->assertSame($httpMethod, $request->getMethod());
$this->assertSame('application/x-www-form-urlencoded', $request->getHeaderLine('Content-Type'));
$this->assertSame(37, (int) $request->getHeaderLine('Content-Length'));
$this->assertSame('foo=bar&bar%5B0%5D=foo&bar%5B1%5D=bar', (string) $request->getBody());
}

/**
* @covers ::sendRequest
* @group setup
Expand Down

0 comments on commit 18b5a42

Please sign in to comment.