Skip to content

Commit

Permalink
Add response validator with openapi context
Browse files Browse the repository at this point in the history
  • Loading branch information
AaronBernabeu committed Feb 16, 2020
1 parent 34fac20 commit c5a3011
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 18 deletions.
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@
"symfony/yaml": "^4.0",
"symfony/messenger": "^4.0",
"justinrainbow/json-schema": "^5.2",
"behat/behat": "^3.5"
"behat/behat": "^3.5",
"behat/mink": "@dev",
"behat/mink-extension": "^2.3"
},
"require-dev": {
"pccomponentes/ganchudo": "^1.0",
"phpunit/phpunit": "^7.3",
"squizlabs/php_codesniffer": "^3.3",
"symfony/var-dumper": "^4.0"
}
}
}
18 changes: 9 additions & 9 deletions src/Behat/MessageValidatorOpenApiContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use Behat\Behat\Context\Context;
use Pccomponentes\OpenApiMessagingContext\Messaging\SpyMiddleware;
use Pccomponentes\OpenApiMessagingContext\OpenApi\JsonSchema;
use Pccomponentes\OpenApiMessagingContext\OpenApi\OpenApiParser;
use Pccomponentes\OpenApiMessagingContext\OpenApi\AsyncApiParser;
use Symfony\Component\Yaml\Yaml;

final class MessageValidatorOpenApiContext implements Context
Expand All @@ -22,38 +22,38 @@ public function __construct(string $rootPath, SpyMiddleware $spyMiddleware)
/**
* @BeforeScenario
*/
public function bootstrapEnvironment()
public function bootstrapEnvironment(): void
{
$this->spyMiddleware->reset();
}

/**
* @Then the published message :name should be valid according to swagger :dumpPath
*/
public function theMessageShouldBeValidAccordingToTheSwagger($name, $dumpPath)
public function theMessageShouldBeValidAccordingToTheSwagger($name, $dumpPath): void
{
$path = realpath($this->rootPath . '/' . $dumpPath);
$this->checkSchemaFile($path);

$eventJson = $this->spyMiddleware->getMessage($name);

$allSpec = Yaml::parse(file_get_contents($path));
$schema = (new OpenApiParser($allSpec))->parse($name);
$schema = (new AsyncApiParser($allSpec))->parse($name);

$this->validate($eventJson, new JsonSchema(\json_decode(\json_encode($schema))));
$this->validate($eventJson, new JsonSchema(\json_decode(\json_encode($schema), false)));
}

/**
* @Then the message :name should be dispatched
*/
public function theMessageShouldBeDispatched(string $name)
public function theMessageShouldBeDispatched(string $name): void
{
if (false === $this->spyMiddleware->hasMessage($name)) {
throw new \Exception(sprintf('Message %s not dispatched', $name));
}
}

private function checkSchemaFile($filename)
private function checkSchemaFile($filename): void
{
if (false === is_file($filename)) {
throw new \RuntimeException(
Expand All @@ -62,13 +62,13 @@ private function checkSchemaFile($filename)
}
}

private function validate(string $json, JsonSchema $schema)
private function validate(string $json, JsonSchema $schema): bool
{
$validator = new \JsonSchema\Validator();

$resolver = new \JsonSchema\SchemaStorage(new \JsonSchema\Uri\UriRetriever, new \JsonSchema\Uri\UriResolver);
$schema->resolve($resolver);

return $schema->validate(\json_decode($json), $validator);
return $schema->validate(\json_decode($json, false), $validator);
}
}
64 changes: 64 additions & 0 deletions src/Behat/ResponseValidatorOpenApiContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php declare(strict_types=1);

namespace Pccomponentes\OpenApiMessagingContext\Behat;

use Behat\Behat\Context\Context;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behat\MinkExtension\Context\MinkContext;
use Pccomponentes\OpenApiMessagingContext\OpenApi\JsonSchema;
use Pccomponentes\OpenApiMessagingContext\OpenApi\OpenApiSchemaParser;
use Symfony\Component\Yaml\Yaml;

final class ResponseValidatorOpenApiContext implements Context
{
/** @var MinkContext */
private $minkContext;
private $rootPath;

public function __construct(string $rootPath)
{
$this->rootPath = $rootPath;
}
/**
* @BeforeScenario
*/
public function bootstrapEnvironment(BeforeScenarioScope $scope): void
{
$this->minkContext = $scope->getEnvironment()->getContext(MinkContext::class);
}

/**
* @Then the JSON response should be valid according to OpenApi :dumpPath schema :schema
*/
public function theJsonResponseShouldBeValidAccordingToOpenApiSchema($dumpPath, $schema): void
{
$path = realpath($this->rootPath . '/' . $dumpPath);
$this->checkSchemaFile($path);

$responseJson = $this->minkContext->getSession()->getPage()->getContent();

$allSpec = Yaml::parse(file_get_contents($path));
$schemaSpec = (new OpenApiSchemaParser($allSpec))->parse($schema);

$this->validate($responseJson, new JsonSchema(\json_decode(\json_encode($schemaSpec), false)));
}

private function checkSchemaFile($filename): void
{
if (false === is_file($filename)) {
throw new \RuntimeException(
'The JSON schema doesn\'t exist'
);
}
}

private function validate(string $json, JsonSchema $schema): bool
{
$validator = new \JsonSchema\Validator();

$resolver = new \JsonSchema\SchemaStorage(new \JsonSchema\Uri\UriRetriever(), new \JsonSchema\Uri\UriResolver());
$schema->resolve($resolver);

return $schema->validate(\json_decode($json, false), $validator);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Pccomponentes\OpenApiMessagingContext\OpenApi;

final class OpenApiParser
final class AsyncApiParser
{
private $originalContent;

Expand Down
52 changes: 52 additions & 0 deletions src/OpenApi/OpenApiSchemaParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php declare(strict_types=1);

namespace Pccomponentes\OpenApiMessagingContext\OpenApi;

final class OpenApiSchemaParser
{
private $originalContent;

public function __construct(array $originalContent)
{
$this->originalContent = $originalContent;
}

public function parse($name): array
{
$schemaSpec = $this->originalContent['components']['schemas'][$name];
if (null === $schemaSpec) {
throw new \InvalidArgumentException(\sprintf('%s schema not found', $name));
}
return $this->extractData($schemaSpec);
}

private function extractData(array $data): array
{
$aux = [];
foreach ($data as $key => $elem) {
if ($key === '$ref') {
$aux = $this->findDefinition($elem);
continue;
}
if (\is_array($elem)) {
$aux[$key] = $this->extractData($elem);
continue;
}

$aux[$key] = $elem;
}

return $aux;
}

private function findDefinition(string $def): array
{
$cleanDef = \preg_replace('/^\#\//', '', $def);
$explodedDef = \explode('/', $cleanDef);
$foundDef = \array_reduce($explodedDef, function ($last, $elem) {
return (null === $last) ? $this->originalContent[$elem] : $last[$elem];
});

return $this->extractData(\array_key_exists('payload', $foundDef) ? $foundDef['payload'] : $foundDef);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@

namespace Pccomponentes\OpenApiMessagingContext\Tests\OpenApi;

use Pccomponentes\OpenApiMessagingContext\OpenApi\OpenApiParser;
use Pccomponentes\OpenApiMessagingContext\OpenApi\AsyncApiParser;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Yaml\Yaml;

final class OpenApiParserTest extends TestCase
final class AsyncApiParserTest extends TestCase
{
/** @test */
public function given_valid_schema_when_parse_then_get_parsed_schema()
{
$allSpec = Yaml::parse(file_get_contents(__DIR__ . '/valid-spec.yaml'));
$schema = (new OpenApiParser($allSpec))->parse('pccomponentes.test.testtopic');
$allSpec = Yaml::parse(file_get_contents(__DIR__ . '/valid-asyncapi-spec.yaml'));
$schema = (new AsyncApiParser($allSpec))->parse('pccomponentes.test.testtopic');
$jsonCompleted = '{"type":"object","required":["message_id","type"],"properties":{"message_id":{"type":"string"},"type":{"type":"string"},"attributes":{"type":"object","required":["some_attribute"],"properties":{"some_attribute":{"type":"string"}}}}}';
$this->assertJsonStringEqualsJsonString(\json_encode($schema), $jsonCompleted);
}
Expand All @@ -22,7 +22,7 @@ public function given_valid_schema_when_parse_non_existent_topic_then_exception(
{
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Topic with name <non.existent.topic> not found');
$allSpec = Yaml::parse(file_get_contents(__DIR__ . '/valid-spec.yaml'));
(new OpenApiParser($allSpec))->parse('non.existent.topic');
$allSpec = Yaml::parse(file_get_contents(__DIR__ . '/valid-asyncapi-spec.yaml'));
(new AsyncApiParser($allSpec))->parse('non.existent.topic');
}
}
File renamed without changes.

0 comments on commit c5a3011

Please sign in to comment.