From e1c9e50e48cc4ad37d3391c1affd86639d0fa35a Mon Sep 17 00:00:00 2001 From: Igor Lazarev Date: Sat, 19 Oct 2019 19:35:22 +0300 Subject: [PATCH] #19 feature: support of "text/html" response added --- src/API/Responder.php | 12 ++- .../Value/Primitive/FakerStringGenerator.php | 1 + src/OpenAPI/Parsing/MediaParser.php | 54 +++++++++++++ src/OpenAPI/Parsing/ResponseParser.php | 10 +-- .../Acceptance/Features/html-response.feature | 7 ++ .../swagger-files/html-response.yaml | 10 +++ tests/Unit/API/ResponderTest.php | 24 ++++-- .../Primitive/FakerStringGeneratorTest.php | 1 + .../Unit/OpenAPI/Parsing/MediaParserTest.php | 79 +++++++++++++++++++ .../OpenAPI/Parsing/ResponseParserTest.php | 37 +++++++-- .../Utility/TestCase/ParsingTestCaseTrait.php | 1 + 11 files changed, 219 insertions(+), 17 deletions(-) create mode 100644 src/OpenAPI/Parsing/MediaParser.php create mode 100644 tests/Acceptance/Features/html-response.feature create mode 100644 tests/Resources/swagger-files/html-response.yaml create mode 100644 tests/Unit/OpenAPI/Parsing/MediaParserTest.php diff --git a/src/API/Responder.php b/src/API/Responder.php index 8583635..7c2f2d8 100644 --- a/src/API/Responder.php +++ b/src/API/Responder.php @@ -20,6 +20,11 @@ */ class Responder { + private const RAW_MEDIA_TYPES = [ + '', + 'text/html', + ]; + /** @var EncoderInterface */ private $encoder; @@ -50,7 +55,7 @@ public function createResponse(int $statusCode, string $mediaType, $data): Respo private function encodeDataByMediaType($data, string $mediaType): string { - if (is_string($data) && '' === $mediaType) { + if ($this->isRawMediaType($data, $mediaType)) { $encodedData = $data; } else { $format = $this->guessSerializationFormat($mediaType); @@ -83,4 +88,9 @@ private function createHeaders(string $mediaType): array return $headers; } + + private function isRawMediaType($data, string $mediaType): bool + { + return is_string($data) && in_array($mediaType, self::RAW_MEDIA_TYPES, true); + } } diff --git a/src/Mock/Generation/Value/Primitive/FakerStringGenerator.php b/src/Mock/Generation/Value/Primitive/FakerStringGenerator.php index 396af8a..ec00271 100644 --- a/src/Mock/Generation/Value/Primitive/FakerStringGenerator.php +++ b/src/Mock/Generation/Value/Primitive/FakerStringGenerator.php @@ -31,6 +31,7 @@ class FakerStringGenerator implements ValueGeneratorInterface 'ipv4' => 'ipv4', 'ipv6' => 'ipv6', 'byte' => 'base64', + 'html' => 'randomHtml', ]; /** @var Generator */ diff --git a/src/OpenAPI/Parsing/MediaParser.php b/src/OpenAPI/Parsing/MediaParser.php new file mode 100644 index 0000000..4591a18 --- /dev/null +++ b/src/OpenAPI/Parsing/MediaParser.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace App\OpenAPI\Parsing; + +use App\Mock\Parameters\Schema\Schema; +use App\Mock\Parameters\Schema\Type\Primitive\StringType; +use App\OpenAPI\SpecificationObjectMarkerInterface; +use Psr\Log\LoggerInterface; + +/** + * @author Igor Lazarev + */ +class MediaParser +{ + /** @var ParserInterface */ + private $schemaParser; + + /** @var LoggerInterface */ + private $logger; + + public function __construct(ParserInterface $schemaParser, LoggerInterface $logger) + { + $this->schemaParser = $schemaParser; + $this->logger = $logger; + } + + public function parseMediaScheme( + SpecificationAccessor $specification, + SpecificationPointer $pointer, + string $mediaType + ): SpecificationObjectMarkerInterface { + /** @var Schema $schema */ + $schema = $this->schemaParser->parsePointedSchema($specification, $pointer); + + if ('text/html' === $mediaType) { + if (!$schema->value instanceof StringType) { + $schema->value = new StringType(); + $this->logger->warning('Only string types are supported for media type "text/html".'); + } + + $schema->value->format = 'html'; + } + + return $schema; + } +} diff --git a/src/OpenAPI/Parsing/ResponseParser.php b/src/OpenAPI/Parsing/ResponseParser.php index a7f2146..2f3425e 100644 --- a/src/OpenAPI/Parsing/ResponseParser.php +++ b/src/OpenAPI/Parsing/ResponseParser.php @@ -20,8 +20,8 @@ */ class ResponseParser implements ParserInterface { - /** @var ParserInterface */ - private $schemaParser; + /** @var MediaParser */ + private $mediaParser; /** @var ErrorHandlerInterface */ private $errorHandler; @@ -29,9 +29,9 @@ class ResponseParser implements ParserInterface /** @var LoggerInterface */ private $logger; - public function __construct(ParserInterface $schemaParser, ErrorHandlerInterface $errorHandler, LoggerInterface $logger) + public function __construct(MediaParser $mediaParser, ErrorHandlerInterface $errorHandler, LoggerInterface $logger) { - $this->schemaParser = $schemaParser; + $this->mediaParser = $mediaParser; $this->errorHandler = $errorHandler; $this->logger = $logger; } @@ -45,7 +45,7 @@ public function parsePointedSchema(SpecificationAccessor $specification, Specifi foreach ($mediaTypes as $mediaType) { $mediaTypePointer = $contentPointer->withPathElement($mediaType); - $parsedSchema = $this->schemaParser->parsePointedSchema($specification, $mediaTypePointer); + $parsedSchema = $this->mediaParser->parseMediaScheme($specification, $mediaTypePointer, $mediaType); $response->content->set($mediaType, $parsedSchema); $this->logger->debug( diff --git a/tests/Acceptance/Features/html-response.feature b/tests/Acceptance/Features/html-response.feature new file mode 100644 index 0000000..138bdaa --- /dev/null +++ b/tests/Acceptance/Features/html-response.feature @@ -0,0 +1,7 @@ +Feature: Html response + + Scenario: GET /html | no parameters | 200 html page returned + Given I have OpenAPI specification file "html-response.yaml" + When I send a "GET" request to "/html" + Then the response status code should be 200 + And I should see an "html" element diff --git a/tests/Resources/swagger-files/html-response.yaml b/tests/Resources/swagger-files/html-response.yaml new file mode 100644 index 0000000..6f5f925 --- /dev/null +++ b/tests/Resources/swagger-files/html-response.yaml @@ -0,0 +1,10 @@ +openapi: "3.0.0" +paths: + /html: + get: + responses: + 200: + content: + text/html: + schema: + type: string diff --git a/tests/Unit/API/ResponderTest.php b/tests/Unit/API/ResponderTest.php index 1df5d4c..14db160 100644 --- a/tests/Unit/API/ResponderTest.php +++ b/tests/Unit/API/ResponderTest.php @@ -85,20 +85,32 @@ public function createResponse_statusCodeAndNotSupportedMediaTypeAndData_excepti $this->expectException(NotSupportedException::class); $this->expectExceptionMessage('Not supported media type'); - $responder->createResponse(Response::HTTP_OK, 'text/html', self::DATA); + $responder->createResponse(Response::HTTP_OK, 'application/octet-stream', self::DATA); } - /** @test */ - public function createResponse_statusCodeAndNoMediaTypeAndStringData_rawResponseCreated(): void - { + /** + * @test + * @dataProvider mediaTypeProvider + */ + public function createResponse_statusCodeAndGivenMediaTypeAndStringData_rawResponseCreated( + string $mediaType + ): void { $responder = $this->creteResponder(); $this->givenEncoder_encode_returnsData(self::ENCODED_DATA); - $response = $responder->createResponse(Response::HTTP_NO_CONTENT, '', self::DATA); + $response = $responder->createResponse(Response::HTTP_NO_CONTENT, $mediaType, self::DATA); $this->assertSame(Response::HTTP_NO_CONTENT, $response->getStatusCode()); - $this->assertFalse($response->headers->has('Content-Type')); $this->assertSame(self::DATA, $response->getContent()); + if ('' === $mediaType) { + $this->assertFalse($response->headers->has('Content-Type')); + } + } + + public function mediaTypeProvider(): \Iterator + { + yield 'no media' => ['']; + yield 'text/html' => ['text/html']; } private function assertEncoder_encode_wasCalledOnceWithDataAndFormat(string $data, string $expectedEncodingFormat): void diff --git a/tests/Unit/Mock/Generation/Value/Primitive/FakerStringGeneratorTest.php b/tests/Unit/Mock/Generation/Value/Primitive/FakerStringGeneratorTest.php index eaf2619..c7f1ed4 100644 --- a/tests/Unit/Mock/Generation/Value/Primitive/FakerStringGeneratorTest.php +++ b/tests/Unit/Mock/Generation/Value/Primitive/FakerStringGeneratorTest.php @@ -151,6 +151,7 @@ public function formatAndFakerMethodProvider(): array ['hostname', 'domainName'], ['ipv4', 'ipv4'], ['ipv6', 'ipv6'], + ['html', 'randomHtml'], ]; } diff --git a/tests/Unit/OpenAPI/Parsing/MediaParserTest.php b/tests/Unit/OpenAPI/Parsing/MediaParserTest.php new file mode 100644 index 0000000..cd6c185 --- /dev/null +++ b/tests/Unit/OpenAPI/Parsing/MediaParserTest.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace App\Tests\Unit\OpenAPI\Parsing; + +use App\Mock\Parameters\Schema\Schema; +use App\Mock\Parameters\Schema\Type\Composite\ObjectType; +use App\Mock\Parameters\Schema\Type\Primitive\StringType; +use App\OpenAPI\Parsing\MediaParser; +use App\OpenAPI\Parsing\SpecificationAccessor; +use App\OpenAPI\Parsing\SpecificationPointer; +use App\Tests\Utility\TestCase\ParsingTestCaseTrait; +use PHPUnit\Framework\TestCase; +use Psr\Log\NullLogger; + +/** + * @author Igor Lazarev + */ +class MediaParserTest extends TestCase +{ + use ParsingTestCaseTrait; + + /** @test */ + public function parseMediaScheme_usualScheme_schemeParsedAndReturnedWithoutChanges(): void + { + $parser = new MediaParser($this->internalParser, new NullLogger()); + $specification = new SpecificationAccessor([]); + $pointer = new SpecificationPointer(); + $expectedSchema = $this->givenInternalParser_parsePointedSchema_returnsObject(); + + $schema = $parser->parseMediaScheme($specification, $pointer, ''); + + $this->assertSame($expectedSchema, $schema); + $this->assertInternalParser_parsePointedSchema_wasCalledOnceWithSpecificationAndPointer($specification, $pointer); + } + + /** @test */ + public function parseMediaScheme_htmlSchemeAndStringSchema_htmlFormatIsSet(): void + { + $parser = new MediaParser($this->internalParser, new NullLogger()); + $specification = new SpecificationAccessor([]); + $pointer = new SpecificationPointer(); + $expectedSchema = new Schema(); + $expectedSchema->value = new StringType(); + $this->givenInternalParser_parsePointedSchema_returns($expectedSchema); + + /** @var Schema $schema */ + $schema = $parser->parseMediaScheme($specification, $pointer, 'text/html'); + + $this->assertSame($expectedSchema, $schema); + $this->assertSame('html', $schema->value->format); + $this->assertInternalParser_parsePointedSchema_wasCalledOnceWithSpecificationAndPointer($specification, $pointer); + } + + /** @test */ + public function parseMediaScheme_htmlSchemeAndObjectSchema_stringSchemaWithHtmlFormatIsSet(): void + { + $parser = new MediaParser($this->internalParser, new NullLogger()); + $specification = new SpecificationAccessor([]); + $pointer = new SpecificationPointer(); + $expectedSchema = new Schema(); + $expectedSchema->value = new ObjectType(); + $this->givenInternalParser_parsePointedSchema_returns($expectedSchema); + + /** @var Schema $schema */ + $schema = $parser->parseMediaScheme($specification, $pointer, 'text/html'); + + $this->assertInstanceOf(StringType::class, $schema->value); + $this->assertSame('html', $schema->value->format); + $this->assertInternalParser_parsePointedSchema_wasCalledOnceWithSpecificationAndPointer($specification, $pointer); + } +} diff --git a/tests/Unit/OpenAPI/Parsing/ResponseParserTest.php b/tests/Unit/OpenAPI/Parsing/ResponseParserTest.php index 2b06ec7..1b24fd2 100644 --- a/tests/Unit/OpenAPI/Parsing/ResponseParserTest.php +++ b/tests/Unit/OpenAPI/Parsing/ResponseParserTest.php @@ -12,10 +12,13 @@ use App\Mock\Parameters\MockResponse; use App\Mock\Parameters\Schema\Schema; +use App\OpenAPI\Parsing\MediaParser; use App\OpenAPI\Parsing\ResponseParser; use App\OpenAPI\Parsing\SpecificationAccessor; use App\OpenAPI\Parsing\SpecificationPointer; +use App\OpenAPI\SpecificationObjectMarkerInterface; use App\Tests\Utility\TestCase\ParsingTestCaseTrait; +use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; use Psr\Log\NullLogger; @@ -32,9 +35,12 @@ class ResponseParserTest extends TestCase ]; private const CONTEXT_PATH = ['content', self::MEDIA_TYPE]; + /** @var MediaParser */ + private $mediaParser; + protected function setUp(): void { - $this->setUpParsingContext(); + $this->mediaParser = \Phake::mock(MediaParser::class); } /** @test */ @@ -42,15 +48,16 @@ public function parsePointedSchema_validResponseSpecification_mockResponseCreate { $parser = $this->createResponseParser(); $expectedSchema = new Schema(); - $this->givenInternalParser_parsePointedSchema_returns($expectedSchema); + $this->givenMediaParser_parseMediaScheme_returns($expectedSchema); $specification = new SpecificationAccessor(self::VALID_RESPONSE_SPECIFICATION); /** @var MockResponse $response */ $response = $parser->parsePointedSchema($specification, new SpecificationPointer()); - $this->assertInternalParser_parsePointedSchema_wasCalledOnceWithSpecificationAndPointerPath( + $this->assertMediaParser_parseMediaScheme_wasCalledOnceWithSpecificationAndPointerPathAndMediaType( $specification, - self::CONTEXT_PATH + self::CONTEXT_PATH, + self::MEDIA_TYPE ); $this->assertResponseHasValidContentWithExpectedSchema($response, $expectedSchema); } @@ -91,8 +98,28 @@ private function assertResponseHasValidContentWithExpectedSchema(MockResponse $r $this->assertSame([self::MEDIA_TYPE], $response->content->getKeys()); } + private function assertMediaParser_parseMediaScheme_wasCalledOnceWithSpecificationAndPointerPathAndMediaType( + SpecificationAccessor $specification, + array $path, + string $mediaType + ): void { + /* @var SpecificationPointer $pointer */ + \Phake::verify($this->mediaParser) + ->parseMediaScheme($specification, \Phake::capture($pointer), $mediaType); + Assert::assertSame($path, $pointer->getPathElements()); + } + + private function givenMediaParser_parseMediaScheme_returns(SpecificationObjectMarkerInterface ...$objects): void + { + $parser = \Phake::when($this->mediaParser)->parseMediaScheme(\Phake::anyParameters()); + + foreach ($objects as $object) { + $parser = $parser->thenReturn($object); + } + } + private function createResponseParser(): ResponseParser { - return new ResponseParser($this->internalParser, $this->errorHandler, new NullLogger()); + return new ResponseParser($this->mediaParser, $this->errorHandler, new NullLogger()); } } diff --git a/tests/Utility/TestCase/ParsingTestCaseTrait.php b/tests/Utility/TestCase/ParsingTestCaseTrait.php index 795ecf0..ecb8485 100644 --- a/tests/Utility/TestCase/ParsingTestCaseTrait.php +++ b/tests/Utility/TestCase/ParsingTestCaseTrait.php @@ -46,6 +46,7 @@ trait ParsingTestCaseTrait /** @var UrlMatcherFactory */ protected $urlMatcherFactory; + /** @before */ protected function setUpParsingContext(): void { $this->internalParser = \Phake::mock(ParserInterface::class);