Skip to content

Commit

Permalink
Merge pull request #22 from sunrise-php/release/v1.9.0
Browse files Browse the repository at this point in the history
v1.9.0
  • Loading branch information
fenric authored Feb 6, 2021
2 parents ce2b085 + cbe5aa0 commit b4d0244
Show file tree
Hide file tree
Showing 8 changed files with 317 additions and 11 deletions.
3 changes: 0 additions & 3 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,5 @@ indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

[*.yml]
indent_size = 2
16 changes: 8 additions & 8 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
.php_cs.cache
.phpunit.result.cache
composer.lock
coverage.xml
phpbench.json
phpcs.xml
phpunit.xml
vendor/
/.php_cs.cache
/.phpunit.result.cache
/composer.lock
/coverage.xml
/phpbench.json
/phpcs.xml
/phpunit.xml
/vendor/
21 changes: 21 additions & 0 deletions .scrutinizer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
build:
environment:
php:
version: '8.0'
pecl_extensions:
- apcu
ini:
'apc.enable_cli': '1'
'xdebug.mode': 'coverage'
nodes:
analysis:
tests:
override:
- php-scrutinizer-run
coverage:
tests:
override:
- command: php vendor/bin/phpunit --coverage-clover coverage.xml
coverage:
file: coverage.xml
format: clover
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"require-dev": {
"phpunit/phpunit": "7.5.20||9.5.0",
"sunrise/coding-standard": "1.0.0",
"sunrise/http-factory": "1.1.0",
"justinrainbow/json-schema": "5.2.10"
},
"autoload": {
Expand Down
78 changes: 78 additions & 0 deletions src/Test/OpenApiAssertKitTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php declare(strict_types=1);

/**
* It's free open-source software released under the MIT License.
*
* @author Anatoly Fenric <[email protected]>
* @copyright Copyright (c) 2019, Anatoly Fenric
* @license https://github.com/sunrise-php/http-router-openapi/blob/master/LICENSE
* @link https://github.com/sunrise-php/http-router-openapi
*/

namespace Sunrise\Http\Router\OpenApi\Test;

/**
* Import classes
*/
use JsonSchema\Validator as JsonSchemaValidator;
use Psr\Http\Message\ResponseInterface;
use Sunrise\Http\Router\OpenApi\Utility\JsonSchemaBuilder;
use Sunrise\Http\Router\RouteInterface;
use ReflectionClass;

/**
* Import functions
*/
use function json_decode;
use function json_encode;
use function json_last_error;
use function json_last_error_msg;

/**
* Import constants
*/
use const JSON_ERROR_NONE;
use const JSON_PRETTY_PRINT;
use const JSON_UNESCAPED_SLASHES;
use const JSON_UNESCAPED_UNICODE;

/**
* OpenApiAssertKitTrait
*/
trait OpenApiAssertKitTrait
{

/**
* @param RouteInterface $route
* @param ResponseInterface $response
*
* @return void
*/
protected function assertResponseBodyMatchesDescription(RouteInterface $route, ResponseInterface $response) : void
{
$body = (string) $response->getBody();
if ('' === $body) {
$this->fail('Response body MUST be non-empty.');
}

$data = json_decode($body);
if (JSON_ERROR_NONE !== json_last_error()) {
$this->fail('Response body MUST contain valid JSON: ' . json_last_error_msg());
}

$jsonSchemaBuilder = new JsonSchemaBuilder(new ReflectionClass($route->getRequestHandler()));
$jsonSchema = $jsonSchemaBuilder->forResponseBody($response->getStatusCode(), 'application/json');
if (null === $jsonSchema) {
$this->fail('No JSON schema found.');
}

$jsonSchemaValidator = new JsonSchemaValidator();
$jsonSchemaValidator->validate($data, $jsonSchema);
if (false === $jsonSchemaValidator->isValid()) {
$flags = JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE;
$this->fail('Invalid body: ' . json_encode($jsonSchemaValidator->getErrors(), $flags));
}

$this->assertTrue(true);
}
}
32 changes: 32 additions & 0 deletions src/Utility/JsonSchemaBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
/**
* Import functions
*/
use function is_array;
use function array_keys;
use function array_walk;
use function array_walk_recursive;
Expand Down Expand Up @@ -213,6 +214,7 @@ public function forRequestBody(string $mediaType) : ?array
}

$jsonSchema += $requestBody->content[$mediaType]->schema->toArray();
$jsonSchema = $this->fixNullable($jsonSchema);

return $this->fixReferences($jsonSchema);
}
Expand Down Expand Up @@ -251,6 +253,7 @@ public function forResponseBody($statusCode, string $mediaType) : ?array
}

$jsonSchema += $response->content[$mediaType]->schema->toArray();
$jsonSchema = $this->fixNullable($jsonSchema);

return $this->fixReferences($jsonSchema);
}
Expand Down Expand Up @@ -314,6 +317,8 @@ private function forRequestParams(string $name) : ?array
$schema = $schema->toArray();
});

$jsonSchema = $this->fixNullable($jsonSchema);

return $this->fixReferences($jsonSchema);
}

Expand All @@ -332,4 +337,31 @@ private function fixReferences(array $jsonSchema) : array

return $jsonSchema;
}

/**
* @param array $jsonSchema
*
* @return array
*/
private function fixNullable(array $jsonSchema) : array
{
$fixer = function (array &$array) use (&$fixer) {
foreach ($array as $key => &$value) {
if ('nullable' === $key && true === $value) {
$array['type'] = [$array['type'], 'null'];
unset($array[$key]);
continue;
}

if (is_array($value)) {
$fixer($value);
continue;
}
}
};

$fixer($jsonSchema);

return $jsonSchema;
}
}
129 changes: 129 additions & 0 deletions tests/Test/OpenApiAssertKitTraitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?php declare(strict_types=1);

namespace Sunrise\Http\Router\OpenApi\Tests\Test;

/**
* Import classes
*/
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Sunrise\Http\Factory\ResponseFactory;
use Sunrise\Http\Router\OpenApi\Test\OpenApiAssertKitTrait;
use Sunrise\Http\Router\Route;
use RuntimeException;

/**
* OpenApiAssertKitTraitTest
*/
class OpenApiAssertKitTraitTest extends TestCase
{
use OpenApiAssertKitTrait;

/**
* @return void
*/
public function testAssertResponseBodyMatchesDescription() : void
{
/**
* @OpenApi\Operation(
* responses={
* 200=@OpenApi\Response(
* description="OK",
* content={
* "application/json"=@OpenApi\MediaType(
* schema=@OpenApi\Schema(
* type="object",
* properties={
* "foo"=@OpenApi\Schema(
* type="string",
* ),
* "bar"=@OpenApi\Schema(
* type="string",
* nullable=true,
* ),
* },
* ),
* ),
* },
* ),
* },
* )
*/
$rh1 = new class implements RequestHandlerInterface
{
public function handle(ServerRequestInterface $request) : ResponseInterface
{
throw new RuntimeException();
}
};

$rh2 = new class implements RequestHandlerInterface
{
public function handle(ServerRequestInterface $request) : ResponseInterface
{
throw new RuntimeException();
}
};

$route = new Route('foo', '/foo', ['GET'], $rh1);
$response = (new ResponseFactory)->createResponse(200);
$response->getBody()->write('');
try {
$this->assertResponseBodyMatchesDescription($route, $response);
} catch (AssertionFailedError $e) {
$this->assertTrue(true);
$this->assertSame('Response body MUST be non-empty.', $e->getMessage());
}

$route = new Route('foo', '/foo', ['GET'], $rh1);
$response = (new ResponseFactory)->createResponse(200);
$response->getBody()->write('!');
try {
$this->assertResponseBodyMatchesDescription($route, $response);
} catch (AssertionFailedError $e) {
$this->assertTrue(true);
$this->assertSame('Response body MUST contain valid JSON: Syntax error', $e->getMessage());
}

$route = new Route('foo', '/foo', ['GET'], $rh2);
$response = (new ResponseFactory)->createResponse(200);
$response->getBody()->write(json_encode([]));
try {
$this->assertResponseBodyMatchesDescription($route, $response);
} catch (AssertionFailedError $e) {
$this->assertTrue(true);
$this->assertSame('No JSON schema found.', $e->getMessage());
}

$route = new Route('foo', '/foo', ['GET'], $rh1);
$response = (new ResponseFactory)->createResponse(200);
$response->getBody()->write(json_encode(['foo', 'foo', 'bar' => 1]));
try {
$this->assertResponseBodyMatchesDescription($route, $response);
} catch (AssertionFailedError $e) {
$this->assertTrue(true);
$this->assertSame('Invalid body: [
{
"property": "bar",
"pointer": "/bar",
"message": "Integer value found, but a string or a null is required",
"constraint": "type",
"context": 1
}
]', $e->getMessage());
}

$route = new Route('foo', '/foo', ['GET'], $rh1);
$response = (new ResponseFactory)->createResponse(200);
$response->getBody()->write(json_encode(['foo', 'foo', 'bar' => 'bar']));
$this->assertResponseBodyMatchesDescription($route, $response);

$route = new Route('foo', '/foo', ['GET'], $rh1);
$response = (new ResponseFactory)->createResponse(200);
$response->getBody()->write(json_encode(['foo', 'foo', 'bar' => null]));
$this->assertResponseBodyMatchesDescription($route, $response);
}
}
48 changes: 48 additions & 0 deletions tests/Utility/JsonSchemaBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -733,4 +733,52 @@ public function testBuildJsonSchemaForResponseBodyWhenMediaTypeUnsupported() : v

$this->assertNull($jsonSchema);
}

/**
* @return void
*/
public function testNullable() : void
{
/**
* @OpenApi\Operation(
* requestBody=@OpenApi\RequestBody(
* content={
* "application/json"=@OpenApi\MediaType(
* schema=@OpenApi\Schema(
* type="object",
* properties={
* "foo"=@OpenApi\Schema(
* type="string",
* nullable=true,
* ),
* },
* ),
* ),
* },
* ),
* responses={
* 200=@OpenApi\Response(
* description="OK",
* ),
* },
* )
*/
$class = new class
{
};

$classReflection = new ReflectionClass($class);
$jsonSchemaBuilder = new JsonSchemaBuilder($classReflection);
$jsonSchema = $jsonSchemaBuilder->forRequestBody('application/json');

$this->assertSame([
'$schema' => 'http://json-schema.org/draft-00/schema#',
'properties' => [
'foo' => [
'type' => ['string', 'null'],
],
],
'type' => 'object',
], $jsonSchema);
}
}

0 comments on commit b4d0244

Please sign in to comment.