Skip to content

Commit 2cd9901

Browse files
authored
Merge pull request #54 from Codeception/changes
Url and Params validation, OPTIONS doesn't send body
2 parents 4b1f599 + 7e84df3 commit 2cd9901

File tree

3 files changed

+106
-34
lines changed

3 files changed

+106
-34
lines changed

src/Codeception/Module/REST.php

+41-30
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383
*/
8484
class REST extends CodeceptionModule implements DependsOnModule, PartedModule, API, ConflictsWithModule
8585
{
86-
const QUERY_PARAMS_AWARE_METHODS = ['GET', 'HEAD', 'DELETE'];
86+
const QUERY_PARAMS_AWARE_METHODS = ['GET', 'HEAD', 'DELETE', 'OPTIONS'];
8787

8888
protected $config = [
8989
'url' => '',
@@ -400,7 +400,7 @@ public function amNTLMAuthenticated($username, $password)
400400
* ?>
401401
* ```
402402
* @param array $additionalAWSConfig
403-
* @throws ModuleException
403+
* @throws ConfigurationException
404404
*/
405405
public function amAWSAuthenticated($additionalAWSConfig = [])
406406
{
@@ -453,7 +453,7 @@ public function amAWSAuthenticated($additionalAWSConfig = [])
453453
* ```
454454
*
455455
* @param $url
456-
* @param array|\JsonSerializable $params
456+
* @param array|string|\JsonSerializable $params
457457
* @param array $files A list of filenames or "mocks" of $_FILES (each entry being an array with the following
458458
* keys: name, type, error, size, tmp_name (pointing to the real file path). Each key works
459459
* as the "name" attribute of a file input field.
@@ -511,7 +511,7 @@ public function sendGet($url, $params = [])
511511
* Sends PUT request to given uri.
512512
*
513513
* @param $url
514-
* @param array $params
514+
* @param array|string|\JsonSerializable $params
515515
* @param array $files
516516
* @part json
517517
* @part xml
@@ -525,7 +525,7 @@ public function sendPut($url, $params = [], $files = [])
525525
* Sends PATCH request to given uri.
526526
*
527527
* @param $url
528-
* @param array $params
528+
* @param array|string|\JsonSerializable $params
529529
* @param array $files
530530
* @part json
531531
* @part xml
@@ -554,14 +554,14 @@ public function sendDelete($url, $params = [], $files = [])
554554
*
555555
* @param $method
556556
* @param $url
557-
* @param array|\JsonSerializable $params
557+
* @param array|string|\JsonSerializable $params
558558
* @param array $files
559559
* @part json
560560
* @part xml
561561
*/
562562
public function send($method, $url, $params = [], $files = [])
563563
{
564-
$this->execute($method, $url, $params, $files);
564+
$this->execute(strtoupper($method), $url, $params, $files);
565565
}
566566

567567
/**
@@ -632,25 +632,34 @@ protected function execute($method, $url, $parameters = [], $files = [])
632632
// allow full url to be requested
633633
if (!$url) {
634634
$url = $this->config['url'];
635+
} elseif (!is_string($url)) {
636+
throw new ModuleException(__CLASS__, 'URL must be string');
635637
} elseif (strpos($url, '://') === false && $this->config['url']) {
636638
$url = rtrim($this->config['url'], '/') . '/' . ltrim($url, '/');
637639
}
638640

639641
$this->params = $parameters;
640642

641-
$parameters = $this->encodeApplicationJson($method, $parameters);
642643
$isQueryParamsAwareMethod = in_array($method, self::QUERY_PARAMS_AWARE_METHODS, true);
643644

644-
if (is_array($parameters) || $isQueryParamsAwareMethod) {
645-
if (!empty($parameters) && $isQueryParamsAwareMethod) {
646-
if (strpos($url, '?') !== false) {
647-
$url .= '&';
648-
} else {
649-
$url .= '?';
650-
}
651-
$url .= http_build_query($parameters);
645+
if ($isQueryParamsAwareMethod) {
646+
if (!is_array($parameters)) {
647+
throw new ModuleException(__CLASS__, $method . ' parameters must be passed in array format');
652648
}
649+
} else {
650+
$parameters = $this->encodeApplicationJson($method, $parameters);
651+
}
652+
653+
if (is_array($parameters) || $isQueryParamsAwareMethod) {
653654
if ($isQueryParamsAwareMethod) {
655+
if (!empty($parameters)) {
656+
if (strpos($url, '?') !== false) {
657+
$url .= '&';
658+
} else {
659+
$url .= '?';
660+
}
661+
$url .= http_build_query($parameters);
662+
}
654663
$this->debugSection("Request", "$method $url");
655664
$files = [];
656665
} else {
@@ -707,7 +716,6 @@ protected function encodeApplicationJson($method, $parameters)
707716
{
708717
if (
709718
array_key_exists('Content-Type', $this->connectionModule->headers)
710-
&& !in_array($method, self::QUERY_PARAMS_AWARE_METHODS, true)
711719
&& ($this->connectionModule->headers['Content-Type'] === 'application/json'
712720
|| preg_match('!^application/.+\+json$!', $this->connectionModule->headers['Content-Type'])
713721
)
@@ -720,6 +728,15 @@ protected function encodeApplicationJson($method, $parameters)
720728
return json_encode($parameters);
721729
}
722730
}
731+
732+
if ($parameters instanceof \JsonSerializable) {
733+
throw new ModuleException(__CLASS__, $method . ' parameters is JsonSerializable object, but Content-Type header is not set to application/json');
734+
}
735+
736+
if (!is_string($parameters) && !is_array($parameters)) {
737+
throw new ModuleException(__CLASS__, $method . ' parameters must be array, string or object implementing JsonSerializable interface');
738+
}
739+
723740
return $parameters;
724741
}
725742

@@ -982,7 +999,6 @@ protected function decodeAndValidateJson($jsonString, $errorFormat="Invalid json
982999
* @return string
9831000
* @part json
9841001
* @part xml
985-
* @version 1.1
9861002
*/
9871003
public function grabResponse()
9881004
{
@@ -1006,7 +1022,6 @@ public function grabResponse()
10061022
* @return array Array of matching items
10071023
* @throws \Exception
10081024
* @part json
1009-
* @version 2.0.9
10101025
*/
10111026
public function grabDataFromResponseByJsonPath($jsonPath)
10121027
{
@@ -1052,7 +1067,6 @@ public function grabDataFromResponseByJsonPath($jsonPath)
10521067
* ```
10531068
* @param string $xpath
10541069
* @part json
1055-
* @version 2.0.9
10561070
*/
10571071
public function seeResponseJsonMatchesXpath($xpath)
10581072
{
@@ -1119,7 +1133,6 @@ public function dontSeeResponseJsonMatchesXpath($xpath)
11191133
*
11201134
* @param string $jsonPath
11211135
* @part json
1122-
* @version 2.0.9
11231136
*/
11241137
public function seeResponseJsonMatchesJsonPath($jsonPath)
11251138
{
@@ -1244,7 +1257,6 @@ public function dontSeeResponseContainsJson($json = [])
12441257
* @param array $jsonType
12451258
* @param string $jsonPath
12461259
* @see JsonType
1247-
* @version 2.1.3
12481260
*/
12491261
public function seeResponseMatchesJsonType(array $jsonType, $jsonPath = null)
12501262
{
@@ -1260,12 +1272,11 @@ public function seeResponseMatchesJsonType(array $jsonType, $jsonPath = null)
12601272
* Opposite to `seeResponseMatchesJsonType`.
12611273
*
12621274
* @part json
1263-
* @param $jsonType jsonType structure
1264-
* @param null $jsonPath optionally set specific path to structure with JsonPath
1275+
* @param array $jsonType JsonType structure
1276+
* @param string $jsonPath
12651277
* @see seeResponseMatchesJsonType
1266-
* @version 2.1.3
12671278
*/
1268-
public function dontSeeResponseMatchesJsonType($jsonType, $jsonPath = null)
1279+
public function dontSeeResponseMatchesJsonType(array $jsonType, $jsonPath = null)
12691280
{
12701281
$jsonArray = new JsonArray($this->connectionModule->_getResponseContent());
12711282
if ($jsonPath) {
@@ -1566,8 +1577,8 @@ public function dontSeeXmlResponseIncludes($xml)
15661577
* ?>
15671578
* ```
15681579
*
1569-
* @param $hash the hashed data response expected
1570-
* @param $algo the hash algorithm to use. Default md5.
1580+
* @param string $hash the hashed data response expected
1581+
* @param string $algo the hash algorithm to use. Default md5.
15711582
* @part json
15721583
* @part xml
15731584
*/
@@ -1587,8 +1598,8 @@ public function seeBinaryResponseEquals($hash, $algo = 'md5')
15871598
* ```
15881599
* Opposite to `seeBinaryResponseEquals`
15891600
*
1590-
* @param $hash the hashed data response expected
1591-
* @param $algo the hash algorithm to use. Default md5.
1601+
* @param string $hash the hashed data response expected
1602+
* @param string $algo the hash algorithm to use. Default md5.
15921603
* @part json
15931604
* @part xml
15941605
*/

src/Codeception/Util/JsonType.php

+2
Original file line numberDiff line numberDiff line change
@@ -240,5 +240,7 @@ protected function matchFilter($filter, $value)
240240
if (preg_match('~^<(-?[\d\.]+)$~', $filter, $matches)) {
241241
return (float)$value < (float)$matches[1];
242242
}
243+
244+
return false;
243245
}
244246
}

tests/unit/Codeception/Module/RestTest.php

+63-4
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,13 @@ public function testPut()
8787
$this->module->dontSeeResponseContainsJson(['name' => 'john']);
8888
}
8989

90+
public function testSend()
91+
{
92+
$this->module->send('POST','/rest/user/', ['name' => 'john']);
93+
$this->module->seeResponseContains('john');
94+
$this->module->seeResponseContainsJson(['name' => 'john']);
95+
}
96+
9097
public function testGrabDataFromResponseByJsonPath()
9198
{
9299
$this->module->sendGET('/rest/user/');
@@ -213,9 +220,8 @@ public function testApplicationJsonIncludesObjectSerialized()
213220
*/
214221
public function testGetApplicationJsonNotIncludesJsonAsContent($method)
215222
{
216-
$method = 'send' . $method;
217223
$this->module->haveHttpHeader('Content-Type', 'application/json');
218-
$this->module->$method('/', ['name' => 'john']);
224+
$this->module->send($method, '/', ['name' => 'john']);
219225
/** @var $request \Symfony\Component\BrowserKit\Request **/
220226
$request = $this->module->client->getRequest();
221227
$this->assertNull($request->getContent());
@@ -225,11 +231,64 @@ public function testGetApplicationJsonNotIncludesJsonAsContent($method)
225231
public function queryParamsAwareMethods()
226232
{
227233
return [
228-
['Get'],
229-
['Head'],
234+
'GET' => ['GET'],
235+
'HEAD' => ['HEAD'],
236+
'DELETE' => ['DELETE'],
237+
'OPTIONS' => ['OPTIONS'],
238+
];
239+
}
240+
241+
/**
242+
* @dataProvider queryParamsAwareMethods
243+
*/
244+
public function testThrowsExceptionIfParametersIsString($method)
245+
{
246+
$this->expectExceptionMessage($method . ' parameters must be passed in array format');
247+
$this->module->send($method, '/', 'string');
248+
}
249+
250+
/**
251+
* @dataProvider invalidParameterTypes
252+
*/
253+
public function testThrowsExceptionIfParametersIsOfUnexpectedType($parameters)
254+
{
255+
$this->expectExceptionMessage('POST parameters must be array, string or object implementing JsonSerializable interface');
256+
$this->module->sendPOST('/', $parameters);
257+
}
258+
259+
public function invalidParameterTypes()
260+
{
261+
return [
262+
'boolean' => [true],
263+
'resource' => [STDERR],
264+
'integer' => [5],
265+
'float' => [6.6],
266+
'object' => [new \LogicException('test')],
230267
];
231268
}
232269

270+
public function testThrowsExceptionIfUrlIsNotString()
271+
{
272+
$this->expectExceptionMessage('URL must be string');
273+
$this->module->sendPOST([1]);
274+
}
275+
276+
public function testThrowsExceptionIfParametersIsJsonSerializableButContentTypeIsNotSet()
277+
{
278+
$this->expectExceptionMessage("parameters is JsonSerializable object, but Content-Type header is not set to application/json");
279+
$parameters = new \Codeception\Util\Maybe(['foo']);
280+
$this->module->sendPOST('/', $parameters);
281+
}
282+
283+
public function testDoesntThrowExceptionIfParametersIsJsonSerializableAndContentTypeIsSet()
284+
{
285+
$parameters = new \Codeception\Util\Maybe(['foo']);
286+
287+
$this->module->haveHttpHeader('Content-Type', 'application/json');
288+
$this->module->sendPOST('/', $parameters);
289+
$this->assertTrue(true, 'this test fails by throwing exception');
290+
}
291+
233292
public function testUrlIsFull()
234293
{
235294
$this->module->sendGET('/api/v1/users');

0 commit comments

Comments
 (0)