From d36429db4261632e24c2b626f74af08655eb707b Mon Sep 17 00:00:00 2001 From: makedo Date: Sun, 5 Nov 2017 01:18:59 +0200 Subject: [PATCH 1/3] Add phpunit to composer.json require-dev and autoload-dev --- composer.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/composer.json b/composer.json index 7868ce8..0c06e08 100644 --- a/composer.json +++ b/composer.json @@ -13,9 +13,17 @@ "require": { "guzzlehttp/guzzle": "6.*" }, + "require-dev": { + "phpunit/phpunit": "5.*" + }, "autoload": { "psr-4": { "ByJG\\Swagger\\": "src/" } + }, + "autoload-dev": { + "psr-4": { + "Test\\": "tests/" + } } } \ No newline at end of file From 4010642a136b5990e6c2bf6d4d9554a4e485e9ed Mon Sep 17 00:00:00 2001 From: makedo Date: Sun, 5 Nov 2017 01:19:16 +0200 Subject: [PATCH 2/3] Add tests and some refactor of tests --- tests/SwaggerBodyTestCase.php | 30 ++++++++++ tests/SwaggerRequestBodyTest.php | 59 ++++++++---------- tests/SwaggerResponseBodyTest.php | 99 ++++++++++++++++++++----------- tests/SwaggerSchemaTest.php | 13 ++++ 4 files changed, 133 insertions(+), 68 deletions(-) create mode 100644 tests/SwaggerBodyTestCase.php diff --git a/tests/SwaggerBodyTestCase.php b/tests/SwaggerBodyTestCase.php new file mode 100644 index 0000000..6badae1 --- /dev/null +++ b/tests/SwaggerBodyTestCase.php @@ -0,0 +1,30 @@ +object = new SwaggerSchema(file_get_contents(__DIR__ . '/example/swagger.json')); - } - - public function tearDown() - { - $this->object = null; - } - public function testMatchRequestBody() { $body = [ @@ -42,7 +19,7 @@ public function testMatchRequestBody() "status" => 'placed', "complete" => true ]; - $requestParameter = $this->object->getRequestParameters('/v2/store/order', 'post'); + $requestParameter = self::swaggerSchema()->getRequestParameters('/v2/store/order', 'post'); $this->assertTrue($requestParameter->match($body)); } @@ -52,7 +29,7 @@ public function testMatchRequestBody() */ public function testMatchRequiredRequestBodyEmpty() { - $requestParameter = $this->object->getRequestParameters('/v2/store/order', 'post'); + $requestParameter = self::swaggerSchema()->getRequestParameters('/v2/store/order', 'post'); $this->assertTrue($requestParameter->match(null)); } @@ -62,7 +39,7 @@ public function testMatchRequiredRequestBodyEmpty() */ public function testMatchInexistantBodyDefinition() { - $requestParameter = $this->object->getRequestParameters('/v2/pet/1', 'get'); + $requestParameter = self::swaggerSchema()->getRequestParameters('/v2/pet/1', 'get'); $body = [ "id" => "10", "petId" => 50, @@ -80,7 +57,7 @@ public function testMatchInexistantBodyDefinition() */ public function testMatchDataType() { - $this->object->getRequestParameters('/v2/pet/STRING', 'get'); + self::swaggerSchema()->getRequestParameters('/v2/pet/STRING', 'get'); $this->assertTrue(true); } @@ -94,27 +71,39 @@ public function testMatchRequestBodyRequired1() "id" => "10", "status" => "pending", ]; - $requestParameter = $this->object->getRequestParameters('/v2/pet', 'post'); + $requestParameter = self::swaggerSchema()->getRequestParameters('/v2/pet', 'post'); $this->assertTrue($requestParameter->match($body)); } /** - * It is not OK { name: null } + * It is not OK when allowNullValues is false (as by default) { name: null } * https://stackoverflow.com/questions/45575493/what-does-required-in-openapi-really-mean - * @todo Add the "nullable" validation. (have to add the test case also) * * @expectedException \ByJG\Swagger\Exception\NotMatchedException - * @expectedExceptionMessage Required property + * @expectedExceptionMessage Value of property 'name' is null, but should be of type 'string' */ - public function testMatchRequestBodyRequired2() + public function testMatchRequestBodyRequiredNullsNotAllowed() + { + $body = [ + "id" => "10", + "status" => "pending", + "name" => null, + "photoUrls" => ["http://example.com/1", "http://example.com/2"] + ]; + $requestParameter = self::swaggerSchema()->getRequestParameters('/v2/pet', 'post'); + $this->assertTrue($requestParameter->match($body)); + } + + public function testMatchRequestBodyRequiredNullsAllowed() { + $allowNullValues = true; $body = [ "id" => "10", "status" => "pending", "name" => null, "photoUrls" => ["http://example.com/1", "http://example.com/2"] ]; - $requestParameter = $this->object->getRequestParameters('/v2/pet', 'post'); + $requestParameter = self::swaggerSchema($allowNullValues)->getRequestParameters('/v2/pet', 'post'); $this->assertTrue($requestParameter->match($body)); } @@ -130,7 +119,7 @@ public function testMatchRequestBodyRequired3() "name" => "", "photoUrls" => ["http://example.com/1", "http://example.com/2"] ]; - $requestParameter = $this->object->getRequestParameters('/v2/pet', 'post'); + $requestParameter = self::swaggerSchema()->getRequestParameters('/v2/pet', 'post'); $this->assertTrue($requestParameter->match($body)); } } diff --git a/tests/SwaggerResponseBodyTest.php b/tests/SwaggerResponseBodyTest.php index 65e9110..804a6dc 100644 --- a/tests/SwaggerResponseBodyTest.php +++ b/tests/SwaggerResponseBodyTest.php @@ -7,33 +7,13 @@ namespace Test; -use ByJG\Swagger\SwaggerSchema; -use PHPUnit\Framework\TestCase; -// backward compatibility -if (!class_exists('\PHPUnit\Framework\TestCase')) { - class_alias('\PHPUnit_Framework_TestCase', '\PHPUnit\Framework\TestCase'); -} - -class SwaggerResponseBodyTest extends TestCase +class SwaggerResponseBodyTest extends SwaggerBodyTestCase { - /** - * @var SwaggerSchema - */ - protected $object; - - public function setUp() - { - $this->object = new SwaggerSchema(file_get_contents(__DIR__ . '/example/swagger.json')); - } - - public function tearDown() - { - $this->object = null; - } - public function testMatchResponseBody() { + $schema = self::swaggerSchema(); + $body = [ "id" => 10, "petId" => 50, @@ -42,7 +22,7 @@ public function testMatchResponseBody() "status" => 'placed', "complete" => true ]; - $responseParameter = $this->object->getResponseParameters('/v2/store/order', 'post', 200); + $responseParameter = $schema->getResponseParameters('/v2/store/order', 'post', 200); $this->assertTrue($responseParameter->match($body)); // Default @@ -53,7 +33,7 @@ public function testMatchResponseBody() "shipDate" => '2010-10-20', "status" => 'placed' ]; - $responseParameter = $this->object->getResponseParameters('/v2/store/order', 'post', 200); + $responseParameter = $schema->getResponseParameters('/v2/store/order', 'post', 200); $this->assertTrue($responseParameter->match($body)); // Number as string @@ -65,7 +45,7 @@ public function testMatchResponseBody() "status" => 'placed', "complete" => true ]; - $responseParameter = $this->object->getResponseParameters('/v2/store/order', 'post', 200); + $responseParameter = $schema->getResponseParameters('/v2/store/order', 'post', 200); $this->assertTrue($responseParameter->match($body)); } @@ -83,7 +63,7 @@ public function testMatchResponseBodyEnumError() "status" => 'notfound', "complete" => true ]; - $responseParameter = $this->object->getResponseParameters('/v2/store/order', 'post', 200); + $responseParameter = self::swaggerSchema()->getResponseParameters('/v2/store/order', 'post', 200); $this->assertTrue($responseParameter->match($body)); } @@ -101,7 +81,7 @@ public function testMatchResponseBodyWrongNumber() "status" => 'placed', "complete" => true ]; - $responseParameter = $this->object->getResponseParameters('/v2/store/order', 'post', 200); + $responseParameter = self::swaggerSchema()->getResponseParameters('/v2/store/order', 'post', 200); $this->assertTrue($responseParameter->match($body)); } @@ -120,7 +100,7 @@ public function testMatchResponseBodyMoreThanExpected() "complete" => true, "more" => "value" ]; - $responseParameter = $this->object->getResponseParameters('/v2/store/order', 'post', 200); + $responseParameter = self::swaggerSchema()->getResponseParameters('/v2/store/order', 'post', 200); $this->assertTrue($responseParameter->match($body)); } @@ -131,14 +111,41 @@ public function testMatchResponseBodyLessFields() "status" => 'placed', "complete" => true ]; - $responseParameter = $this->object->getResponseParameters('/v2/store/order', 'post', 200); + $responseParameter = self::swaggerSchema()->getResponseParameters('/v2/store/order', 'post', 200); + $this->assertTrue($responseParameter->match($body)); + } + + public function testMatchResponseBodyAllowNullValues() + { + $allowNullValues = true; + $body = [ + "id" => 10, + "status" => 'placed', + "complete" => null + ]; + $responseParameter = self::swaggerSchema($allowNullValues)->getResponseParameters('/v2/store/order', 'post', 200); $this->assertTrue($responseParameter->match($body)); } + /** + * @expectedException \ByJG\Swagger\Exception\NotMatchedException + * @expectedExceptionMessage Value of property 'complete' is null, but should be of type 'boolean' + */ + public function testMatchResponseBodyNotAllowNullValues() + { + $body = [ + "id" => 10, + "status" => 'placed', + "complete" => null + ]; + $responseParameter = self::swaggerSchema()->getResponseParameters('/v2/store/order', 'post', 200); + $responseParameter->match($body); + } + public function testMatchResponseBodyEmpty() { $body = null; - $responseParameter = $this->object->getResponseParameters('/v2/pet/10', 'get', 400); + $responseParameter = self::swaggerSchema()->getResponseParameters('/v2/pet/10', 'get', 400); $this->assertTrue($responseParameter->match($body)); } @@ -149,7 +156,7 @@ public function testMatchResponseBodyEmpty() public function testMatchResponseBodyNotEmpty() { $body = ['suppose'=>'not here']; - $responseParameter = $this->object->getResponseParameters('/v2/pet/10', 'get', 400); + $responseParameter = self::swaggerSchema()->getResponseParameters('/v2/pet/10', 'get', 400); $this->assertTrue($responseParameter->match($body)); } @@ -177,7 +184,33 @@ public function testMatchResponseBodyComplex() ], "status" => 'available' ]; - $responseParameter = $this->object->getResponseParameters('/v2/pet/10', 'get', 200); + $responseParameter = self::swaggerSchema()->getResponseParameters('/v2/pet/10', 'get', 200); + $this->assertTrue($responseParameter->match($body)); + } + + public function testMatchResponseBodyWhenValueWithNestedPropertiesIsNullAndNullsAreAllowed() + { + $allowNullValues = true; + $body = [ + "id" => 10, + "category" => null, + "name" => "Spike", + "photoUrls" => [ + 'url1', + 'url2' + ], + "tags" => [ + [ + 'id' => '10', + 'name' => 'cute' + ], + [ + 'name' => 'priceless' + ] + ], + "status" => 'available' + ]; + $responseParameter = self::swaggerSchema($allowNullValues)->getResponseParameters('/v2/pet/10', 'get', 200); $this->assertTrue($responseParameter->match($body)); } } diff --git a/tests/SwaggerSchemaTest.php b/tests/SwaggerSchemaTest.php index 230657a..a6039c7 100644 --- a/tests/SwaggerSchemaTest.php +++ b/tests/SwaggerSchemaTest.php @@ -484,4 +484,17 @@ public function testGetDefinition() $order ); } + + public function testItNotAllowsNullValuesByDefault() + { + $schema = new SwaggerSchema('{}'); + $this->assertFalse($schema->isAllowNullValues()); + } + + public function testItAllowsNullValues() + { + $allowNullValues = true; + $schema = new SwaggerSchema('{}', $allowNullValues); + $this->assertTrue($schema->isAllowNullValues()); + } } From dfcd76c6683c16f84b4e018ba055c75b2c49b881 Mon Sep 17 00:00:00 2001 From: makedo Date: Sun, 5 Nov 2017 01:19:44 +0200 Subject: [PATCH 3/3] Add and use allowNullValues property on SwaggerSchema --- src/SwaggerBody.php | 49 ++++++++++++++++++++++++++++++++++++------- src/SwaggerSchema.php | 15 ++++++++++++- 2 files changed, 56 insertions(+), 8 deletions(-) diff --git a/src/SwaggerBody.php b/src/SwaggerBody.php index 0e9d4bb..613d7dc 100644 --- a/src/SwaggerBody.php +++ b/src/SwaggerBody.php @@ -15,14 +15,23 @@ abstract class SwaggerBody protected $name; + /** + * OpenApi 2.0 does not describe null values, so this flag defines, + * if match is ok when one of property, which has type, is null + * + * @var bool + */ + protected $allowNullValues; + /** * SwaggerRequestBody constructor. * * @param \ByJG\Swagger\SwaggerSchema $swaggerSchema * @param string $name * @param array $structure + * @param bool $allowNullValues */ - public function __construct(SwaggerSchema $swaggerSchema, $name, $structure) + public function __construct(SwaggerSchema $swaggerSchema, $name, $structure, $allowNullValues = false) { $this->swaggerSchema = $swaggerSchema; $this->name = $name; @@ -30,6 +39,7 @@ public function __construct(SwaggerSchema $swaggerSchema, $name, $structure) throw new \InvalidArgumentException('I expected the structure to be an array'); } $this->structure = $structure; + $this->allowNullValues = $allowNullValues; } abstract public function match($body); @@ -85,19 +95,25 @@ protected function matchArray($name, $schema, $body) protected function matchSchema($name, $schema, $body) { if (isset($schema['type'])) { - if ($schema['type'] == 'string') { + + $type = $schema['type']; + if (is_null($body)) { + return $this->matchNull($name, $type); + } + + if ($type == 'string') { return $this->matchString($name, $schema, $body); } - if ($schema['type'] == 'integer' || $schema['type'] == 'float' || $schema['type'] == 'number') { + if ($type == 'integer' || $type == 'float' || $schema['type'] == 'number') { return $this->matchNumber($name, $body); } - if ($schema['type'] == 'bool' || $schema['type'] == 'boolean') { + if ($type == 'bool' || $schema['type'] == 'boolean') { return $this->matchBool($name, $body); } - if ($schema['type'] == 'array') { + if ($type == 'array') { return $this->matchArray($name, $schema, $body); } } @@ -113,14 +129,15 @@ protected function matchSchema($name, $schema, $body) } foreach ($schema['properties'] as $prop => $def) { $required = array_search($prop, $schema['required']); - // if (!array_key_exists($prop, $body)) { - if (!isset($body[$prop])) { + + if (!array_key_exists($prop, $body)) { if ($required !== false) { throw new NotMatchedException("Required property '$prop' in '$name' not found in object"); } unset($body[$prop]); continue; } + $this->matchSchema($prop, $def, $body[$prop]); unset($schema['properties'][$prop]); if ($required !== false) { @@ -151,4 +168,22 @@ protected function matchSchema($name, $schema, $body) throw new \Exception("Not all cases are defined. Please open an issue about this. Schema: $name"); } + + /** + * @param $name + * @param $type + * @return bool + * @throws NotMatchedException + */ + protected function matchNull($name, $type) + { + if (false === $this->swaggerSchema->isAllowNullValues()) { + throw new NotMatchedException( + "Value of property '$name' is null, but should be of type '$type'", + $this->structure + ); + } + + return true; + } } diff --git a/src/SwaggerSchema.php b/src/SwaggerSchema.php index 8b20914..30edaa5 100644 --- a/src/SwaggerSchema.php +++ b/src/SwaggerSchema.php @@ -16,10 +16,12 @@ class SwaggerSchema { protected $jsonFile; + protected $allowNullValues; - public function __construct($jsonFile) + public function __construct($jsonFile, $allowNullValues = false) { $this->jsonFile = json_decode($jsonFile, true); + $this->allowNullValues = (bool) $allowNullValues; } public function getHttpSchema() @@ -123,4 +125,15 @@ public function getResponseParameters($path, $method, $status) return new SwaggerResponseBody($this, "$method $status $path", $structure['responses'][$status]); } + + /** + * OpenApi 2.0 doesn't describe null values, so this flag defines, + * if match is ok when one of property + * + * @return bool + */ + public function isAllowNullValues() + { + return $this->allowNullValues; + } }