From f3d16f2346be602ed5b97bbd89b341c4c2f34c30 Mon Sep 17 00:00:00 2001 From: Yannick Chenot Date: Thu, 23 Mar 2023 17:35:44 +0000 Subject: [PATCH 01/19] OpenAPI 3.1 support (#26) Drops support for PHP 7.2 --- .github/workflows/ci.yml | 1 - README.md | 2 +- composer.json | 8 ++++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 77ad419..497879a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,6 @@ jobs: fail-fast: false matrix: php-version: - - "7.2" - "7.3" - "7.4" - "8.0" diff --git a/README.md b/README.md index 4055153..dc28b24 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![License](https://poser.pugx.org/osteel/openapi-httpfoundation-testing/license)](//packagist.org/packages/osteel/openapi-httpfoundation-testing) [![Downloads](http://poser.pugx.org/osteel/openapi-httpfoundation-testing/downloads)](//packagist.org/packages/osteel/openapi-httpfoundation-testing) -Validate HttpFoundation requests and responses against OpenAPI (3.0.x) definitions. +Validate HttpFoundation requests and responses against OpenAPI (3+) definitions. See [this post](https://tech.osteel.me/posts/openapi-backed-api-testing-in-php-projects-a-laravel-example "OpenAPI-backed API testing in PHP projects – a Laravel example") for more details and [this repository](https://github.com/osteel/openapi-httpfoundation-testing-laravel-example) for an example use in a Laravel project. diff --git a/composer.json b/composer.json index b2ebc2c..b61a1de 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "osteel/openapi-httpfoundation-testing", "type": "library", - "description": "Validate HttpFoundation requests and responses against OpenAPI (3.0.x) definitions", + "description": "Validate HttpFoundation requests and responses against OpenAPI (3+) definitions", "keywords": [ "openapi", "httpfoundation", @@ -23,16 +23,16 @@ } ], "require": { - "php": "^7.2|^8.0", + "php": "^7.3|^8.0", "ext-json": "*", - "league/openapi-psr7-validator": "^0.17", + "league/openapi-psr7-validator": "^0.19", "nyholm/psr7": "^1.0", "psr/http-message": "^1.0", "symfony/http-foundation": "^4.0 || ^5.0 || ^6.0", "symfony/psr-http-message-bridge": "^2.0" }, "require-dev": { - "phpunit/phpunit": ">=8.5.23", + "phpunit/phpunit": "^9.0", "squizlabs/php_codesniffer": "^3.5" }, "autoload": { From 55b88863caee9abe2b646bb38d54bc86b9123588 Mon Sep 17 00:00:00 2001 From: Yannick Chenot Date: Thu, 15 Jun 2023 16:49:35 +0100 Subject: [PATCH 02/19] dropped support for PHP 7 (#29) --- .../CODE_OF_CONDUCT.md | 0 CONTRIBUTING.md => .github/CONTRIBUTING.md | 0 .../ISSUE_TEMPLATE.md | 3 ++ .../PULL_REQUEST_TEMPLATE.md | 0 .github/workflows/ci.yml | 21 ++++++------ composer.json | 7 ++-- rector.php | 14 ++++++++ src/Adapters/HttpFoundationAdapter.php | 2 +- src/Exceptions/ValidationException.php | 1 - src/Validator.php | 32 ++----------------- src/ValidatorBuilder.php | 19 ++--------- 11 files changed, 39 insertions(+), 60 deletions(-) rename CODE_OF_CONDUCT.md => .github/CODE_OF_CONDUCT.md (100%) rename CONTRIBUTING.md => .github/CONTRIBUTING.md (100%) rename ISSUE_TEMPLATE.md => .github/ISSUE_TEMPLATE.md (73%) rename PULL_REQUEST_TEMPLATE.md => .github/PULL_REQUEST_TEMPLATE.md (100%) create mode 100644 rector.php diff --git a/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md similarity index 100% rename from CODE_OF_CONDUCT.md rename to .github/CODE_OF_CONDUCT.md diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to .github/CONTRIBUTING.md diff --git a/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md similarity index 73% rename from ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE.md index 5b48c57..17930c1 100644 --- a/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,5 +1,8 @@ +> **Warning** +> Are you sure your issue is caused by this package? Almost all reported issues come from a misunderstanding of the [OpenAPI specification](https://swagger.io/specification/). Please make sure that your answer isn't there before posting. + ## Detailed description Provide a detailed description of the change or addition you are proposing. diff --git a/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md similarity index 100% rename from PULL_REQUEST_TEMPLATE.md rename to .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 497879a..fcacff8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,27 +8,30 @@ on: jobs: - phpcs: - name: PHP_CodeSniffer + style: + name: Style runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + tools: cs2pr, phpcs - - name: Run PHP_CodeSniffer - uses: chekalsky/phpcs-action@v1 + - name: Run phpcs + run: phpcs -q --report=checkstyle | cs2pr - phpunit: - name: PHPUnit + tests: + name: Tests runs-on: ubuntu-latest strategy: fail-fast: false matrix: php-version: - - "7.3" - - "7.4" - "8.0" - "8.1" - "8.2" diff --git a/composer.json b/composer.json index b61a1de..3200039 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ } ], "require": { - "php": "^7.3|^8.0", + "php": "^8.0", "ext-json": "*", "league/openapi-psr7-validator": "^0.19", "nyholm/psr7": "^1.0", @@ -33,6 +33,7 @@ }, "require-dev": { "phpunit/phpunit": "^9.0", + "rector/rector": "^0.17.1", "squizlabs/php_codesniffer": "^3.5" }, "autoload": { @@ -47,8 +48,8 @@ }, "scripts": { "test": "phpunit", - "check-style": "phpcs src tests", - "fix-style": "phpcbf src tests" + "fix": "phpcbf src tests", + "refactor": "rector process" }, "extra": { "branch-alias": { diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..eca49a8 --- /dev/null +++ b/rector.php @@ -0,0 +1,14 @@ +paths([ + __DIR__ . '/src', + ]); + + $rectorConfig->sets([LevelSetList::UP_TO_PHP_80]); +}; diff --git a/src/Adapters/HttpFoundationAdapter.php b/src/Adapters/HttpFoundationAdapter.php index 5a09366..87d01f4 100644 --- a/src/Adapters/HttpFoundationAdapter.php +++ b/src/Adapters/HttpFoundationAdapter.php @@ -39,6 +39,6 @@ public function convert(object $message): MessageInterface return $psrHttpFactory->createRequest($message); } - throw new InvalidArgumentException(sprintf('Unsupported %s object received', get_class($message))); + throw new InvalidArgumentException(sprintf('Unsupported %s object received', $message::class)); } } diff --git a/src/Exceptions/ValidationException.php b/src/Exceptions/ValidationException.php index ee786c8..7418b0a 100644 --- a/src/Exceptions/ValidationException.php +++ b/src/Exceptions/ValidationException.php @@ -13,7 +13,6 @@ class ValidationException extends Exception /** * Build a new exception from a ValidationFailed exception. * - * @param ValidationFailed $exception * @return ValidationException */ public static function fromValidationFailed(ValidationFailed $exception): ValidationException diff --git a/src/Validator.php b/src/Validator.php index d4b5a86..c410938 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -15,37 +15,11 @@ final class Validator implements ValidatorInterface { - /** - * @var RoutedServerRequestValidator - */ - private $requestValidator; - - /** - * @var ResponseValidator - */ - private $responseValidator; - - /** - * @var AdapterInterface - */ - private $adapter; - - /** - * Constructor. - * - * @param RoutedServerRequestValidator $requestValidator - * @param ResponseValidator $responseValidator - * @param AdapterInterface $adapter - * @return void - */ public function __construct( - RoutedServerRequestValidator $requestValidator, - ResponseValidator $responseValidator, - AdapterInterface $adapter + private RoutedServerRequestValidator $requestValidator, + private ResponseValidator $responseValidator, + private AdapterInterface $adapter ) { - $this->requestValidator = $requestValidator; - $this->responseValidator = $responseValidator; - $this->adapter = $adapter; } /** diff --git a/src/ValidatorBuilder.php b/src/ValidatorBuilder.php index 8622fad..9759068 100644 --- a/src/ValidatorBuilder.php +++ b/src/ValidatorBuilder.php @@ -14,25 +14,10 @@ */ final class ValidatorBuilder implements ValidatorBuilderInterface { - /** - * @var string - */ - private $adapter = HttpFoundationAdapter::class; - - /** - * @var BaseValidatorBuilder - */ - private $validatorBuilder; + private string $adapter = HttpFoundationAdapter::class; - /** - * Constructor. - * - * @param BaseValidatorBuilder $builder - * @return void - */ - public function __construct(BaseValidatorBuilder $builder) + public function __construct(private BaseValidatorBuilder $validatorBuilder) { - $this->validatorBuilder = $builder; } /** From d602cc4656a2de87e9e9f46ed0bad0fd1dc491e8 Mon Sep 17 00:00:00 2001 From: Yannick Chenot Date: Thu, 15 Jun 2023 18:10:39 +0100 Subject: [PATCH 03/19] updated dependencies (#30) --- .github/workflows/ci.yml | 4 ++-- composer.json | 2 +- rector.php | 1 + tests/Adapters/HttpFoundationAdapterTest.php | 5 +---- tests/TestCase.php | 16 ++++------------ tests/ValidatorBuilderTest.php | 2 +- tests/ValidatorTest.php | 11 ++++++----- 7 files changed, 16 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fcacff8..4cb5b25 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,10 +19,10 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - tools: cs2pr, phpcs + tools: phpcs - name: Run phpcs - run: phpcs -q --report=checkstyle | cs2pr + run: phpcs tests: name: Tests diff --git a/composer.json b/composer.json index 3200039..4f6b9e3 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "require": { "php": "^8.0", "ext-json": "*", - "league/openapi-psr7-validator": "^0.19", + "league/openapi-psr7-validator": "^0.21", "nyholm/psr7": "^1.0", "psr/http-message": "^1.0", "symfony/http-foundation": "^4.0 || ^5.0 || ^6.0", diff --git a/rector.php b/rector.php index eca49a8..52a4828 100644 --- a/rector.php +++ b/rector.php @@ -8,6 +8,7 @@ return static function (RectorConfig $rectorConfig): void { $rectorConfig->paths([ __DIR__ . '/src', + __DIR__ . '/tests', ]); $rectorConfig->sets([LevelSetList::UP_TO_PHP_80]); diff --git a/tests/Adapters/HttpFoundationAdapterTest.php b/tests/Adapters/HttpFoundationAdapterTest.php index ff7e46e..1d46dd8 100644 --- a/tests/Adapters/HttpFoundationAdapterTest.php +++ b/tests/Adapters/HttpFoundationAdapterTest.php @@ -12,10 +12,7 @@ class HttpFoundationAdapterTest extends TestCase { - /** - * @var HttpFoundationAdapter - */ - private $sut; + private HttpFoundationAdapter $sut; protected function setUp(): void { diff --git a/tests/TestCase.php b/tests/TestCase.php index 69929ab..de0a3da 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -28,13 +28,12 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase /** * Return a HttpFoundation response with the provided content. * - * @param array $content * @return Response */ protected function httpFoundationResponse(array $content = null): Response { return new Response( - $content ? json_encode($content) : '', + $content ? json_encode($content, JSON_THROW_ON_ERROR) : '', $content ? Response::HTTP_OK : Response::HTTP_NO_CONTENT, $content ? ['Content-Type' => 'application/json'] : [] ); @@ -43,7 +42,6 @@ protected function httpFoundationResponse(array $content = null): Response /** * Return a PSR-7 response with the provided content. * - * @param array $content * @return ResponseInterface */ protected function psr7Response(array $content = null): ResponseInterface @@ -55,7 +53,7 @@ protected function psr7Response(array $content = null): ResponseInterface return $response; } - $response->method('getBody')->willReturn(json_encode($content)); + $response->method('getBody')->willReturn(json_encode($content, JSON_THROW_ON_ERROR)); $response->method('getStatusCode')->willReturn(Response::HTTP_OK); $response->method('getHeader')->willReturn(['application/json']); @@ -65,9 +63,6 @@ protected function psr7Response(array $content = null): ResponseInterface /** * Return a HttpFoundation request with the provided content. * - * @param string $uri - * @param string $method - * @param array $content * @return Request */ protected function httpFoundationRequest(string $uri, string $method, array $content = null): Request @@ -79,16 +74,13 @@ protected function httpFoundationRequest(string $uri, string $method, array $con [], [], $content ? ['CONTENT_TYPE' => 'application/json'] : [], - $content ? json_encode($content) : '' + $content ? json_encode($content, JSON_THROW_ON_ERROR) : '' ); } /** * Return a PSR-7 request with the provided content. * - * @param string $uri - * @param string $method - * @param array $content * @return ServerRequestInterface */ protected function psr7Request( @@ -98,7 +90,7 @@ protected function psr7Request( ): ServerRequestInterface { $psr17Factory = new Psr17Factory(); $uri = $psr17Factory->createUri(self::BASE_URI . $uri); - $stream = $psr17Factory->createStream(json_encode($content)); + $stream = $psr17Factory->createStream(json_encode($content, JSON_THROW_ON_ERROR)); $request = $psr17Factory->createServerRequest($method, $uri); if ($content) { diff --git a/tests/ValidatorBuilderTest.php b/tests/ValidatorBuilderTest.php index 92ddb49..45cc36d 100644 --- a/tests/ValidatorBuilderTest.php +++ b/tests/ValidatorBuilderTest.php @@ -58,7 +58,7 @@ public function testItDoesNotSetTheAdapterBecauseItsTypeIsInvalid() public function testItSetsTheAdapter() { ValidatorBuilder::fromYaml(self::$yamlDefinition) - ->setAdapter(get_class($this->createMock(AdapterInterface::class))); + ->setAdapter($this->createMock(AdapterInterface::class)::class); // No exception means the test was successful. $this->assertTrue(true); diff --git a/tests/ValidatorTest.php b/tests/ValidatorTest.php index ce49d2a..9eaa7f7 100644 --- a/tests/ValidatorTest.php +++ b/tests/ValidatorTest.php @@ -6,14 +6,12 @@ use Osteel\OpenApi\Testing\Exceptions\ValidationException; use Osteel\OpenApi\Testing\Tests\TestCase; +use Osteel\OpenApi\Testing\Validator; use Osteel\OpenApi\Testing\ValidatorBuilder; class ValidatorTest extends TestCase { - /** - * @var Validator - */ - private $sut; + private Validator $sut; protected function setUp(): void { @@ -42,10 +40,11 @@ public function requestTypeProvider(): array public function testItDoesNotValidateTheRequestWithoutPayload(string $method) { $this->expectException(ValidationException::class); + $this->expectExceptionMessage('OpenAPI spec contains no such operation [/test,foo]'); $request = $this->$method(static::PATH, 'delete'); - $this->sut->validate($request, static::PATH, $method); + $this->sut->validate($request, static::PATH, 'foo'); } /** @@ -65,6 +64,7 @@ public function testItValidatesTheRequestWithoutPayload(string $method) public function testItDoesNotValidateTheRequestWithPayload(string $method) { $this->expectException(ValidationException::class); + $this->expectExceptionMessage('Body does not match schema for content-type "application/json" for Request [post /test]: Keyword validation failed: Required property \'foo\' must be present in the object Field: foo'); $request = $this->$method(static::PATH, 'post', ['baz' => 'bar']); @@ -144,6 +144,7 @@ public function responseTypeProvider(): array public function testItDoesNotValidateTheResponse(string $method) { $this->expectException(ValidationException::class); + $this->expectExceptionMessage('Body does not match schema for content-type "application/json" for Response [get /test 200]: Keyword validation failed: Required property \'foo\' must be present in the object Field: foo'); $response = $this->$method(['baz' => 'bar']); From 2fa5049d37bf7eeddce4143a91913cd76aef3c2a Mon Sep 17 00:00:00 2001 From: Yannick Chenot Date: Fri, 16 Jun 2023 13:17:58 +0100 Subject: [PATCH 04/19] replaced PHP_CodeSniffer with PHP CS Fixer (#31) --- .github/workflows/ci.yml | 9 +- .gitignore | 3 +- .php-cs-fixer.dist.php | 238 +++++++++++++++++++ composer.json | 6 +- phpcs.xml.dist | 18 -- src/Adapters/AdapterInterface.php | 9 +- src/Adapters/HttpFoundationAdapter.php | 17 +- src/Exceptions/ValidationException.php | 8 +- src/Validator.php | 109 ++++----- src/ValidatorBuilder.php | 59 ++--- src/ValidatorBuilderInterface.php | 24 +- src/ValidatorInterface.php | 78 +++--- tests/Adapters/HttpFoundationAdapterTest.php | 21 +- tests/Exceptions/ValidationExceptionTest.php | 8 +- tests/TestCase.php | 49 ++-- tests/ValidatorBuilderTest.php | 13 +- tests/ValidatorTest.php | 93 +++----- 17 files changed, 455 insertions(+), 307 deletions(-) create mode 100644 .php-cs-fixer.dist.php delete mode 100644 phpcs.xml.dist diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4cb5b25..f970c7b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,11 +18,12 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 - with: - tools: phpcs - - name: Run phpcs - run: phpcs + - name: Install composer dependencies + uses: ramsey/composer-install@v2 + + - name: Check coding style + run: vendor/bin/php-cs-fixer fix --dry-run tests: name: Tests diff --git a/.gitignore b/.gitignore index 41fcb16..d86755b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ composer.lock vendor phpcs.xml phpunit.xml -.phpunit.result.cache \ No newline at end of file +.phpunit.result.cache +.php-cs-fixer.cache \ No newline at end of file diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..fdb2201 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,238 @@ +in(__DIR__ . '/src') + ->in(__DIR__ . '/tests'); +; + +$config = new PhpCsFixer\Config(); +return $config->setRules([ + '@PSR12' => true, + 'align_multiline_comment' => [ + 'comment_type' => 'all_multiline', + ], + 'array_indentation' => true, + 'assign_null_coalescing_to_coalesce_equal' => true, + 'binary_operator_spaces' => [ + 'default' => 'single_space', + ], + 'cast_spaces' => [ + 'space' => 'single', + ], + 'class_attributes_separation' => [ + 'elements' => ['const' => 'one', 'method' => 'one', 'property' => 'one', 'trait_import' => 'none', 'case' => 'none'], + ], + 'class_reference_name_casing' => true, + 'clean_namespace' => true, + 'combine_consecutive_issets' => true, + 'concat_space' => [ + 'spacing' => 'one', + ], + 'declare_parentheses' => true, + 'explicit_indirect_variable' => true, + 'explicit_string_variable' => true, + 'fully_qualified_strict_types' => true, + 'function_typehint_space' => true, + 'general_phpdoc_tag_rename' => [ + 'replacements' => ['inheritDocs' => 'inheritDoc'], + ], + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'heredoc_indentation' => [ + 'indentation' => 'same_as_start', + ], + 'include' => true, + 'integer_literal_case' => true, + 'lambda_not_used_import' => true, + 'linebreak_after_opening_tag' => true, + 'list_syntax' => [ + 'syntax' => 'short', + ], + 'magic_constant_casing' => true, + 'magic_method_casing' => true, + 'method_chaining_indentation' => true, + 'multiline_comment_opening_closing' => true, + 'multiline_whitespace_before_semicolons' => [ + 'strategy' => 'no_multi_line', + ], + 'native_function_casing' => true, + 'native_function_type_declaration_casing' => true, + 'no_alias_language_construct_call' => true, + 'no_alternative_syntax' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_empty_comment' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_extra_blank_lines' => [ + 'tokens' => ['extra'], + ], + 'no_leading_namespace_whitespace' => true, + 'no_multiline_whitespace_around_double_arrow' => true, + 'no_null_property_initialization' => true, + 'no_short_bool_cast' => true, + 'no_singleline_whitespace_before_semicolons' => true, + 'no_spaces_around_offset' => true, + 'no_superfluous_phpdoc_tags' => [ + 'allow_mixed' => false, + 'allow_unused_params' => false, + 'remove_inheritdoc' => false, + ], + 'no_trailing_comma_in_singleline' => [ + 'elements' => ['arguments', 'array_destructuring', 'array', 'group_import'], + ], + 'no_unneeded_control_parentheses' => [ + 'statements' => [ + 'break', + 'clone', + 'continue', + 'echo_print', + 'negative_instanceof', + 'others', 'return', + 'switch_case', + 'yield', + 'yield_from', + ], + ], + 'no_unneeded_curly_braces' => [ + 'namespaces' => false, + ], + 'no_unneeded_import_alias' => true, + 'no_unset_cast' => true, + 'no_unused_imports' => true, + 'no_useless_concat_operator' => true, + 'no_useless_else' => true, + 'no_useless_nullsafe_operator' => true, + 'no_useless_return' => true, + 'no_whitespace_before_comma_in_array' => [ + 'after_heredoc' => false, + ], + 'normalize_index_brace' => true, + 'not_operator_with_successor_space' => true, + 'nullable_type_declaration_for_default_null_value' => [ + 'use_nullable_type_declaration' => true, + ], + 'object_operator_without_whitespace' => true, + 'operator_linebreak' => [ + 'only_booleans' => false, + 'position' => 'beginning', + ], + 'ordered_interfaces' => [ + 'direction' => 'ascend', + 'order' => 'alpha', + ], + 'php_unit_method_casing' => [ + 'case' => 'snake_case', + ], + 'phpdoc_align' => [ + 'align' => 'vertical', + 'tags' => ['method', 'param', 'property', 'property-read', 'property-write', 'return', 'throws', 'type', 'var'], + ], + 'phpdoc_annotation_without_dot' => true, + 'phpdoc_indent' => true, + 'phpdoc_inline_tag_normalizer' => [ + 'tags' => ['example', 'id', 'internal', 'inheritdoc', 'inheritdocs', 'link', 'source', 'toc', 'tutorial'], + ], + 'phpdoc_line_span' => [ + 'const' => 'single', + 'method' => 'single', + 'property' => 'single', + ], + 'phpdoc_no_alias_tag' => [ + 'replacements' => ['type' => 'var', 'link' => 'see'], + ], + 'phpdoc_no_useless_inheritdoc' => true, + 'phpdoc_order' => [ + 'order' => ['param', 'return', 'throws'], + ], + 'phpdoc_order_by_value' => [ + 'annotations' => [ + 'author', + 'covers', + 'coversNothing', + 'dataProvider', + 'depends', + 'group', + 'internal', + 'method', + 'mixin', + 'property', + 'property-read', + 'property-write', + 'requires', + 'throws', + 'uses', + ], + ], + 'phpdoc_return_self_reference' => [ + 'replacements' => [ + 'this' => 'self', + '@this' => 'self', + '$self' => 'self', + '@self' => 'self', + '$static' => 'static', + '@static' => 'static', + ], + ], + 'phpdoc_scalar' => [ + 'types' => ['boolean', 'callback', 'double', 'integer', 'real', 'str'], + ], + 'phpdoc_separation' => true, + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_summary' => true, + 'phpdoc_tag_casing' => [ + 'tags' => ['inheritDoc'], + ], + 'phpdoc_tag_type' => true, + 'phpdoc_to_comment' => [ + 'ignored_tags' => ['var'], + ], + 'phpdoc_trim' => true, + 'phpdoc_trim_consecutive_blank_line_separation' => true, + 'phpdoc_types' => [ + 'groups' => ['alias', 'meta', 'simple'], + ], + 'phpdoc_var_annotation_correct_order' => true, + 'phpdoc_var_without_name' => true, + 'protected_to_private' => true, + 'return_assignment' => true, + 'self_static_accessor' => true, + 'semicolon_after_instruction' => true, + 'simple_to_complex_string_variable' => true, + 'simplified_if_return' => true, + 'simplified_null_return' => true, + 'single_line_comment_spacing' => true, + 'single_line_comment_style' => [ + 'comment_types' => ['asterisk', 'hash'], + ], + 'single_quote' => [ + 'strings_containing_single_quote_chars' => false, + ], + 'single_space_around_construct' => true, + 'space_after_semicolon' => [ + 'remove_in_empty_for_expressions' => true, + ], + 'standardize_increment' => true, + 'standardize_not_equals' => true, + 'switch_case_semicolon_to_colon' => true, + 'switch_continue_to_break' => true, + 'ternary_to_null_coalescing' => true, + 'trailing_comma_in_multiline' => true, + 'trim_array_spaces' => true, + 'types_spaces' => [ + 'space' => 'none', + 'space_multiple_catch' => null, + ], + 'unary_operator_spaces' => true, + 'whitespace_after_comma_in_array' => true, + 'yoda_style' => [ + 'always_move_variable' => false, + 'equal' => false, + 'identical' => false, + 'less_and_greater' => false, + ], + ]) + ->setFinder($finder) +; diff --git a/composer.json b/composer.json index 4f6b9e3..c835b14 100644 --- a/composer.json +++ b/composer.json @@ -32,9 +32,9 @@ "symfony/psr-http-message-bridge": "^2.0" }, "require-dev": { + "friendsofphp/php-cs-fixer": "^3.17", "phpunit/phpunit": "^9.0", - "rector/rector": "^0.17.1", - "squizlabs/php_codesniffer": "^3.5" + "rector/rector": "^0.17.1" }, "autoload": { "psr-4": { @@ -48,7 +48,7 @@ }, "scripts": { "test": "phpunit", - "fix": "phpcbf src tests", + "fix": "php-cs-fixer fix -v", "refactor": "rector process" }, "extra": { diff --git a/phpcs.xml.dist b/phpcs.xml.dist deleted file mode 100644 index 111de29..0000000 --- a/phpcs.xml.dist +++ /dev/null @@ -1,18 +0,0 @@ - - - The coding standard of openapi-httpfoundation-testing package - - - - - - - - - - - - - src - tests - \ No newline at end of file diff --git a/src/Adapters/AdapterInterface.php b/src/Adapters/AdapterInterface.php index f20355b..58289bb 100644 --- a/src/Adapters/AdapterInterface.php +++ b/src/Adapters/AdapterInterface.php @@ -5,14 +5,17 @@ namespace Osteel\OpenApi\Testing\Adapters; use Psr\Http\Message\MessageInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; interface AdapterInterface { /** * Convert a HTTP message to a PSR-7 HTTP message. * - * @param object $message The HTTP message to convert. - * @return MessageInterface + * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to convert */ - public function convert(object $message): MessageInterface; + public function convert(Request|Response|ResponseInterface|ServerRequestInterface $message): MessageInterface; } diff --git a/src/Adapters/HttpFoundationAdapter.php b/src/Adapters/HttpFoundationAdapter.php index 87d01f4..87b1c45 100644 --- a/src/Adapters/HttpFoundationAdapter.php +++ b/src/Adapters/HttpFoundationAdapter.php @@ -4,7 +4,6 @@ namespace Osteel\OpenApi\Testing\Adapters; -use InvalidArgumentException; use Nyholm\Psr7\Factory\Psr17Factory; use Psr\Http\Message\MessageInterface; use Psr\Http\Message\ResponseInterface; @@ -16,29 +15,23 @@ final class HttpFoundationAdapter implements AdapterInterface { /** - * {@inheritDoc} + * @inheritDoc * - * @param object $message The HTTP message to convert. - * @return MessageInterface - * @throws InvalidArgumentException + * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to convert */ - public function convert(object $message): MessageInterface + public function convert(Request|Response|ResponseInterface|ServerRequestInterface $message): MessageInterface { if ($message instanceof ResponseInterface || $message instanceof ServerRequestInterface) { return $message; } - $psr17Factory = new Psr17Factory(); + $psr17Factory = new Psr17Factory(); $psrHttpFactory = new PsrHttpFactory($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); if ($message instanceof Response) { return $psrHttpFactory->createResponse($message); } - if ($message instanceof Request) { - return $psrHttpFactory->createRequest($message); - } - - throw new InvalidArgumentException(sprintf('Unsupported %s object received', $message::class)); + return $psrHttpFactory->createRequest($message); } } diff --git a/src/Exceptions/ValidationException.php b/src/Exceptions/ValidationException.php index 7418b0a..4923cbe 100644 --- a/src/Exceptions/ValidationException.php +++ b/src/Exceptions/ValidationException.php @@ -10,15 +10,11 @@ class ValidationException extends Exception { - /** - * Build a new exception from a ValidationFailed exception. - * - * @return ValidationException - */ + /** Build a new exception from a ValidationFailed exception. */ public static function fromValidationFailed(ValidationFailed $exception): ValidationException { $previous = $exception; - $message = $exception->getMessage(); + $message = $exception->getMessage(); while ($exception = $exception->getPrevious()) { $message .= sprintf(': %s', $exception->getMessage()); diff --git a/src/Validator.php b/src/Validator.php index c410938..b6e83fc 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -4,7 +4,6 @@ namespace Osteel\OpenApi\Testing; -use InvalidArgumentException; use League\OpenAPIValidation\PSR7\Exception\ValidationFailed; use League\OpenAPIValidation\PSR7\OperationAddress; use League\OpenAPIValidation\PSR7\ResponseValidator; @@ -12,6 +11,9 @@ use Osteel\OpenApi\Testing\Adapters\AdapterInterface; use Osteel\OpenApi\Testing\Exceptions\ValidationException; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; final class Validator implements ValidatorInterface { @@ -23,18 +25,20 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritDoc + * + * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate + * @param string $path the OpenAPI path + * @param string $method the HTTP method * - * @param object $message The HTTP message to validate. - * @param string $path The OpenAPI path. - * @param string $method The HTTP method. - * @return bool - * @throws InvalidArgumentException * @throws ValidationException */ - public function validate(object $message, string $path, string $method): bool - { - $message = $this->adapter->convert($message); + public function validate( + Request|Response|ResponseInterface|ServerRequestInterface $message, + string $path, + string $method, + ): bool { + $message = $this->adapter->convert($message); $operation = $this->getOperationAddress($path, $method); $validator = $message instanceof ResponseInterface ? $this->responseValidator : $this->requestValidator; @@ -50,9 +54,8 @@ public function validate(object $message, string $path, string $method): bool /** * Build and return an OperationAddress object from the path and method. * - * @param string $path The OpenAPI path. - * @param string $method The HTTP method. - * @return OperationAddress + * @param string $path the OpenAPI path + * @param string $method the HTTP method */ private function getOperationAddress(string $path, string $method): OperationAddress { @@ -63,105 +66,105 @@ private function getOperationAddress(string $path, string $method): OperationAdd } /** - * {@inheritdoc} + * @inheritDoc + * + * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate + * @param string $path the OpenAPI path * - * @param object $message The HTTP message to validate. - * @param string $path The OpenAPI path. - * @return bool * @throws ValidationFailed */ - public function delete(object $message, string $path): bool + public function delete(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool { return $this->validate($message, $path, 'delete'); } /** - * {@inheritdoc} + * @inheritDoc + * + * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate + * @param string $path the OpenAPI path * - * @param object $message The HTTP message to validate. - * @param string $path The OpenAPI path. - * @return bool * @throws ValidationFailed */ - public function get(object $message, string $path): bool + public function get(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool { return $this->validate($message, $path, 'get'); } /** - * {@inheritdoc} + * @inheritDoc + * + * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate + * @param string $path the OpenAPI path * - * @param object $message The HTTP message to validate. - * @param string $path The OpenAPI path. - * @return bool * @throws ValidationFailed */ - public function head(object $message, string $path): bool + public function head(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool { return $this->validate($message, $path, 'head'); } /** - * {@inheritdoc} + * @inheritDoc + * + * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate + * @param string $path the OpenAPI path * - * @param object $message The HTTP message to validate. - * @param string $path The OpenAPI path. - * @return bool * @throws ValidationFailed */ - public function options(object $message, string $path): bool + public function options(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool { return $this->validate($message, $path, 'options'); } /** - * {@inheritdoc} + * @inheritDoc + * + * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate + * @param string $path the OpenAPI path * - * @param object $message The HTTP message to validate. - * @param string $path The OpenAPI path. - * @return bool * @throws ValidationFailed */ - public function patch(object $message, string $path): bool + public function patch(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool { return $this->validate($message, $path, 'patch'); } /** - * {@inheritdoc} + * @inheritDoc + * + * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate + * @param string $path the OpenAPI path * - * @param object $message The HTTP message to validate. - * @param string $path The OpenAPI path. - * @return bool * @throws ValidationFailed */ - public function post(object $message, string $path): bool + public function post(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool { return $this->validate($message, $path, 'post'); } /** - * {@inheritdoc} + * @inheritDoc + * + * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate + * @param string $path the OpenAPI path * - * @param object $message The HTTP message to validate. - * @param string $path The OpenAPI path. - * @return bool * @throws ValidationFailed */ - public function put(object $message, string $path): bool + public function put(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool { return $this->validate($message, $path, 'put'); } /** - * {@inheritdoc} + * @inheritDoc + * + * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate + * @param string $path the OpenAPI path * - * @param object $message The HTTP message to validate. - * @param string $path The OpenAPI path. - * @return bool * @throws ValidationFailed */ - public function trace(object $message, string $path): bool + public function trace(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool { return $this->validate($message, $path, 'trace'); } diff --git a/src/ValidatorBuilder.php b/src/ValidatorBuilder.php index 9759068..8f53109 100644 --- a/src/ValidatorBuilder.php +++ b/src/ValidatorBuilder.php @@ -21,94 +21,83 @@ public function __construct(private BaseValidatorBuilder $validatorBuilder) } /** - * {@inheritdoc} + * @inheritDoc * - * @param string $definition The OpenAPI definition. - * @return ValidatorBuilderInterface + * @param string $definition the OpenAPI definition */ public static function fromYaml(string $definition): ValidatorBuilderInterface { $method = is_file($definition) ? 'fromYamlFile' : 'fromYaml'; - return static::fromMethod($method, $definition); + return self::fromMethod($method, $definition); } /** - * {@inheritdoc} + * @inheritDoc * - * @param string $definition The OpenAPI definition. - * @return ValidatorBuilderInterface + * @param string $definition the OpenAPI definition */ public static function fromJson(string $definition): ValidatorBuilderInterface { $method = is_file($definition) ? 'fromJsonFile' : 'fromJson'; - return static::fromMethod($method, $definition); + return self::fromMethod($method, $definition); } /** - * {@inheritdoc} + * @inheritDoc * - * @param string $definition The OpenAPI definition's file. - * @return ValidatorBuilderInterface + * @param string $definition the OpenAPI definition's file */ public static function fromYamlFile(string $definition): ValidatorBuilderInterface { - return static::fromMethod('fromYamlFile', $definition); + return self::fromMethod('fromYamlFile', $definition); } /** - * {@inheritdoc} + * @inheritDoc * - * @param string $definition The OpenAPI definition's file. - * @return ValidatorBuilderInterface + * @param string $definition the OpenAPI definition's file */ public static function fromJsonFile(string $definition): ValidatorBuilderInterface { - return static::fromMethod('fromJsonFile', $definition); + return self::fromMethod('fromJsonFile', $definition); } /** - * {@inheritdoc} + * @inheritDoc * - * @param string $definition The OpenAPI definition as YAML text. - * @return ValidatorBuilderInterface + * @param string $definition the OpenAPI definition as YAML text */ public static function fromYamlString(string $definition): ValidatorBuilderInterface { - return static::fromMethod('fromYaml', $definition); + return self::fromMethod('fromYaml', $definition); } /** - * {@inheritdoc} + * @inheritDoc * - * @param string $definition The OpenAPI definition as JSON text. - * @return ValidatorBuilderInterface + * @param string $definition the OpenAPI definition as JSON text */ public static function fromJsonString(string $definition): ValidatorBuilderInterface { - return static::fromMethod('fromJson', $definition); + return self::fromMethod('fromJson', $definition); } /** * Create a Validator object based on an OpenAPI definition. * - * @param string $method The ValidatorBuilder object's method to use. - * @param string $definition The OpenAPI definition. - * @return ValidatorBuilderInterface + * @param string $method the ValidatorBuilder object's method to use + * @param string $definition the OpenAPI definition */ private static function fromMethod(string $method, string $definition): ValidatorBuilderInterface { - $builder = (new BaseValidatorBuilder())->$method($definition); + $builder = (new BaseValidatorBuilder())->{$method}($definition); return new ValidatorBuilder($builder); } - /** - * {@inheritdoc} - * - * @return ValidatorInterface - */ + /** @inheritDoc */ public function getValidator(): ValidatorInterface { return new Validator( @@ -122,8 +111,8 @@ public function getValidator(): ValidatorInterface * Change the adapter to use. The provided class must implement * \Osteel\OpenApi\Testing\Adapters\AdapterInterface. * - * @param string $class The adapter's class. - * @return ValidatorBuilder + * @param string $class the adapter's class + * * @throws InvalidArgumentException */ public function setAdapter(string $class): ValidatorBuilder diff --git a/src/ValidatorBuilderInterface.php b/src/ValidatorBuilderInterface.php index c078b44..04a2036 100644 --- a/src/ValidatorBuilderInterface.php +++ b/src/ValidatorBuilderInterface.php @@ -9,55 +9,45 @@ interface ValidatorBuilderInterface /** * Create a ValidatorBuilderInterface object based on a YAML OpenAPI definition. * - * @param string $definition The OpenAPI definition. - * @return ValidatorBuilderInterface + * @param string $definition the OpenAPI definition */ public static function fromYaml(string $definition): ValidatorBuilderInterface; /** * Create a ValidatorBuilderInterface object based on a JSON OpenAPI definition. * - * @param string $definition The OpenAPI definition. - * @return ValidatorBuilderInterface + * @param string $definition the OpenAPI definition */ public static function fromJson(string $definition): ValidatorBuilderInterface; /** * Create a ValidatorBuilderInterface object based on a YAML OpenAPI definition. * - * @param string $file The OpenAPI definition's file. - * @return ValidatorBuilderInterface + * @param string $file the OpenAPI definition's file */ public static function fromYamlFile(string $file): ValidatorBuilderInterface; /** * Create a ValidatorBuilderInterface object based on a JSON OpenAPI definition. * - * @param string $file The OpenAPI definition's file. - * @return ValidatorBuilderInterface + * @param string $file the OpenAPI definition's file */ public static function fromJsonFile(string $file): ValidatorBuilderInterface; /** * Create a ValidatorBuilderInterface object based on a YAML OpenAPI definition. * - * @param string $definition The OpenAPI definition as YAML text. - * @return ValidatorBuilderInterface + * @param string $definition the OpenAPI definition as YAML text */ public static function fromYamlString(string $definition): ValidatorBuilderInterface; /** * Create a ValidatorBuilderInterface object based on a JSON OpenAPI definition. * - * @param string $definition The OpenAPI definition as JSON text. - * @return ValidatorBuilderInterface + * @param string $definition the OpenAPI definition as JSON text */ public static function fromJsonString(string $definition): ValidatorBuilderInterface; - /** - * Return the validator. - * - * @return ValidatorInterface - */ + /** Return the validator. */ public function getValidator(): ValidatorInterface; } diff --git a/src/ValidatorInterface.php b/src/ValidatorInterface.php index 11b265b..98b7fda 100644 --- a/src/ValidatorInterface.php +++ b/src/ValidatorInterface.php @@ -6,97 +6,101 @@ use League\OpenAPIValidation\PSR7\Exception\ValidationFailed; use Osteel\OpenApi\Testing\Exceptions\ValidationException; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; interface ValidatorInterface { /** * Validate a HTTP message against the specified OpenAPI definition. * - * @param object $message The HTTP message to validate. - * @param string $path The OpenAPI path. - * @param string $method The HTTP method. - * @return bool + * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate + * @param string $path the OpenAPI path + * @param string $method the HTTP method + * * @throws ValidationException */ - public function validate(object $message, string $path, string $method): bool; + public function validate(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path, string $method): bool; /** * Validate a HTTP message for a DELETE operation on the provided OpenAPI definition path. * - * @param object $message The HTTP message to validate. - * @param string $path The OpenAPI path. - * @return bool + * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate + * @param string $path the OpenAPI path + * * @throws ValidationFailed */ - public function delete(object $message, string $path): bool; + public function delete(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool; /** * Validate a HTTP message for a GET operation on the provided OpenAPI definition path. * - * @param object $message The HTTP message to validate. - * @param string $path The OpenAPI path. - * @return bool + * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate + * @param string $path the OpenAPI path + * * @throws ValidationFailed */ - public function get(object $message, string $path): bool; + public function get(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool; /** * Validate a HTTP message for a HEAD operation on the provided OpenAPI definition path. * - * @param object $message The HTTP message to validate. - * @param string $path The OpenAPI path. - * @return bool + * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate + * @param string $path the OpenAPI path + * * @throws ValidationFailed */ - public function head(object $message, string $path): bool; + public function head(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool; /** * Validate a HTTP message for a OPTIONS operation on the provided OpenAPI definition path. * - * @param object $message The HTTP message to validate. - * @param string $path The OpenAPI path. - * @return bool + * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate + * @param string $path the OpenAPI path + * * @throws ValidationFailed */ - public function options(object $message, string $path): bool; + public function options(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool; /** * Validate a HTTP message for a PATCH operation on the provided OpenAPI definition path. * - * @param object $message The HTTP message to validate. - * @param string $path The OpenAPI path. - * @return bool + * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate + * @param string $path the OpenAPI path + * * @throws ValidationFailed */ - public function patch(object $message, string $path): bool; + public function patch(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool; /** * Validate a HTTP message for a POST operation on the provided OpenAPI definition path. * - * @param object $message The HTTP message to validate. - * @param string $path The OpenAPI path. - * @return bool + * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate + * @param string $path the OpenAPI path + * * @throws ValidationFailed */ - public function post(object $message, string $path): bool; + public function post(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool; /** * Validate a HTTP message for a PUT operation on the provided OpenAPI definition path. * - * @param object $message The HTTP message to validate. - * @param string $path The OpenAPI path. - * @return bool + * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate + * @param string $path the OpenAPI path + * * @throws ValidationFailed */ - public function put(object $message, string $path): bool; + public function put(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool; /** * Validate a HTTP message for a TRACE operation on the provided OpenAPI definition path. * - * @param object $message The HTTP message to validate. - * @param string $path The OpenAPI path. - * @return bool + * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate + * @param string $path the OpenAPI path + * * @throws ValidationFailed */ - public function trace(object $message, string $path): bool; + public function trace(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool; } diff --git a/tests/Adapters/HttpFoundationAdapterTest.php b/tests/Adapters/HttpFoundationAdapterTest.php index 1d46dd8..00e8c26 100644 --- a/tests/Adapters/HttpFoundationAdapterTest.php +++ b/tests/Adapters/HttpFoundationAdapterTest.php @@ -2,7 +2,6 @@ namespace Osteel\OpenApi\Testing\Tests\Adapters; -use InvalidArgumentException; use Osteel\OpenApi\Testing\Adapters\HttpFoundationAdapter; use Osteel\OpenApi\Testing\Tests\TestCase; use Psr\Http\Message\ResponseInterface; @@ -21,40 +20,32 @@ protected function setUp(): void $this->sut = new HttpFoundationAdapter(); } - public function testItDoesNotConvertTheMessageBecauseTheTypeIsNotSupported() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Unsupported InvalidArgumentException object received'); - - $this->sut->convert(new InvalidArgumentException()); - } - - public function testItConvertsTheHttpFoundationRequest() + public function test_it_converts_the_http_foundation_request() { $result = $this->sut->convert(Request::create('/foo')); $this->assertInstanceOf(ServerRequestInterface::class, $result); } - public function testItLeavesTheRequestInterfaceUntouched() + public function test_it_leaves_the_request_interface_untouched() { $request = $this->createMock(ServerRequestInterface::class); - $result = $this->sut->convert($request); + $result = $this->sut->convert($request); $this->assertEquals($request, $result); } - public function testItConvertsAHttpFoundationResponse() + public function test_it_converts_a_http_foundation_response() { $result = $this->sut->convert(new Response()); $this->assertInstanceOf(ResponseInterface::class, $result); } - public function testItLeavesTheResponseInterfaceUntouched() + public function test_it_leaves_the_response_interface_untouched() { $response = $this->createMock(ResponseInterface::class); - $result = $this->sut->convert($response); + $result = $this->sut->convert($response); $this->assertEquals($response, $result); } diff --git a/tests/Exceptions/ValidationExceptionTest.php b/tests/Exceptions/ValidationExceptionTest.php index 418a26d..76da908 100644 --- a/tests/Exceptions/ValidationExceptionTest.php +++ b/tests/Exceptions/ValidationExceptionTest.php @@ -11,12 +11,12 @@ class ValidationExceptionTest extends TestCase { - public function testItCreatesAnExceptionFromAValidationFailedException() + public function test_it_creates_an_exception_from_a_validation_failed_exception() { $breadCrumb = new BreadCrumb('qux'); - $previous = (new SchemaMismatch('baz'))->withBreadCrumb($breadCrumb); - $exception = new ValidationFailed('foo', 0, new Exception('bar', 0, $previous)); - $sut = ValidationException::fromValidationFailed($exception); + $previous = (new SchemaMismatch('baz'))->withBreadCrumb($breadCrumb); + $exception = new ValidationFailed('foo', 0, new Exception('bar', 0, $previous)); + $sut = ValidationException::fromValidationFailed($exception); $this->assertEquals('foo: bar: baz Field: qux', $sut->getMessage()); $this->assertEquals($exception, $sut->getPrevious()); diff --git a/tests/TestCase.php b/tests/TestCase.php index de0a3da..2aaece9 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -13,24 +13,17 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase { private const BASE_URI = 'http://localhost:8000/api'; - protected const PATH = '/test'; - /** - * @var string - */ + protected const PATH = '/test'; + + /** @var string */ protected static $yamlDefinition = __DIR__ . '/stubs/example.yaml'; - /** - * @var string - */ + /** @var string */ protected static $jsonDefinition = __DIR__ . '/stubs/example.json'; - /** - * Return a HttpFoundation response with the provided content. - * - * @return Response - */ - protected function httpFoundationResponse(array $content = null): Response + /** Return a HttpFoundation response with the provided content. */ + protected function httpFoundationResponse(?array $content = null): Response { return new Response( $content ? json_encode($content, JSON_THROW_ON_ERROR) : '', @@ -39,12 +32,8 @@ protected function httpFoundationResponse(array $content = null): Response ); } - /** - * Return a PSR-7 response with the provided content. - * - * @return ResponseInterface - */ - protected function psr7Response(array $content = null): ResponseInterface + /** Return a PSR-7 response with the provided content. */ + protected function psr7Response(?array $content = null): ResponseInterface { $response = $this->createMock(ResponseInterface::class); @@ -60,12 +49,8 @@ protected function psr7Response(array $content = null): ResponseInterface return $response; } - /** - * Return a HttpFoundation request with the provided content. - * - * @return Request - */ - protected function httpFoundationRequest(string $uri, string $method, array $content = null): Request + /** Return a HttpFoundation request with the provided content. */ + protected function httpFoundationRequest(string $uri, string $method, ?array $content = null): Request { return Request::create( self::BASE_URI . $uri, @@ -78,20 +63,16 @@ protected function httpFoundationRequest(string $uri, string $method, array $con ); } - /** - * Return a PSR-7 request with the provided content. - * - * @return ServerRequestInterface - */ + /** Return a PSR-7 request with the provided content. */ protected function psr7Request( string $uri, string $method, - array $content = null + ?array $content = null ): ServerRequestInterface { $psr17Factory = new Psr17Factory(); - $uri = $psr17Factory->createUri(self::BASE_URI . $uri); - $stream = $psr17Factory->createStream(json_encode($content, JSON_THROW_ON_ERROR)); - $request = $psr17Factory->createServerRequest($method, $uri); + $uri = $psr17Factory->createUri(self::BASE_URI . $uri); + $stream = $psr17Factory->createStream(json_encode($content, JSON_THROW_ON_ERROR)); + $request = $psr17Factory->createServerRequest($method, $uri); if ($content) { $request = $request->withHeader('Content-Type', 'application/json'); diff --git a/tests/ValidatorBuilderTest.php b/tests/ValidatorBuilderTest.php index 45cc36d..2737acf 100644 --- a/tests/ValidatorBuilderTest.php +++ b/tests/ValidatorBuilderTest.php @@ -6,7 +6,6 @@ use InvalidArgumentException; use Osteel\OpenApi\Testing\Adapters\AdapterInterface; -use Osteel\OpenApi\Testing\Tests\TestCase; use Osteel\OpenApi\Testing\Validator; use Osteel\OpenApi\Testing\ValidatorBuilder; @@ -26,16 +25,14 @@ public function definitionProvider(): array ]; } - /** - * @dataProvider definitionProvider - */ - public function testItBuildsAValidator(string $method, string $definition) + /** @dataProvider definitionProvider */ + public function test_it_builds_a_validator(string $method, string $definition) { $result = ValidatorBuilder::$method($definition)->getValidator(); $this->assertInstanceOf(Validator::class, $result); - $request = $this->httpFoundationRequest(static::PATH, 'get', ['foo' => 'bar']); + $request = $this->httpFoundationRequest(static::PATH, 'get', ['foo' => 'bar']); $response = $this->httpFoundationResponse(['foo' => 'bar']); // Validate a request and a response to make sure the definition was correctly parsed. @@ -43,7 +40,7 @@ public function testItBuildsAValidator(string $method, string $definition) $this->assertTrue($result->get($response, static::PATH)); } - public function testItDoesNotSetTheAdapterBecauseItsTypeIsInvalid() + public function test_it_does_not_set_the_adapter_because_its_type_is_invalid() { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage(sprintf( @@ -55,7 +52,7 @@ public function testItDoesNotSetTheAdapterBecauseItsTypeIsInvalid() ValidatorBuilder::fromYaml(self::$yamlDefinition)->setAdapter(InvalidArgumentException::class); } - public function testItSetsTheAdapter() + public function test_it_sets_the_adapter() { ValidatorBuilder::fromYaml(self::$yamlDefinition) ->setAdapter($this->createMock(AdapterInterface::class)::class); diff --git a/tests/ValidatorTest.php b/tests/ValidatorTest.php index 9eaa7f7..6b8c164 100644 --- a/tests/ValidatorTest.php +++ b/tests/ValidatorTest.php @@ -5,7 +5,6 @@ namespace Osteel\OpenApi\Testing\Tests; use Osteel\OpenApi\Testing\Exceptions\ValidationException; -use Osteel\OpenApi\Testing\Tests\TestCase; use Osteel\OpenApi\Testing\Validator; use Osteel\OpenApi\Testing\ValidatorBuilder; @@ -24,7 +23,7 @@ protected function setUp(): void |-------------------------------------------------------------------------- | Requests |-------------------------------------------------------------------------- - */ + */ public function requestTypeProvider(): array { @@ -34,50 +33,42 @@ public function requestTypeProvider(): array ]; } - /** - * @dataProvider requestTypeProvider - */ - public function testItDoesNotValidateTheRequestWithoutPayload(string $method) + /** @dataProvider requestTypeProvider */ + public function test_it_does_not_validate_the_request_without_payload(string $method) { $this->expectException(ValidationException::class); $this->expectExceptionMessage('OpenAPI spec contains no such operation [/test,foo]'); - $request = $this->$method(static::PATH, 'delete'); + $request = $this->{$method}(static::PATH, 'delete'); $this->sut->validate($request, static::PATH, 'foo'); } - /** - * @dataProvider requestTypeProvider - */ - public function testItValidatesTheRequestWithoutPayload(string $method) + /** @dataProvider requestTypeProvider */ + public function test_it_validates_the_request_without_payload(string $method) { - $request = $this->$method(static::PATH, $method); - $result = $this->sut->validate($request, static::PATH, 'delete'); + $request = $this->{$method}(static::PATH, $method); + $result = $this->sut->validate($request, static::PATH, 'delete'); $this->assertTrue($result); } - /** - * @dataProvider requestTypeProvider - */ - public function testItDoesNotValidateTheRequestWithPayload(string $method) + /** @dataProvider requestTypeProvider */ + public function test_it_does_not_validate_the_request_with_payload(string $method) { $this->expectException(ValidationException::class); $this->expectExceptionMessage('Body does not match schema for content-type "application/json" for Request [post /test]: Keyword validation failed: Required property \'foo\' must be present in the object Field: foo'); - $request = $this->$method(static::PATH, 'post', ['baz' => 'bar']); + $request = $this->{$method}(static::PATH, 'post', ['baz' => 'bar']); $this->sut->validate($request, static::PATH, 'post'); } - /** - * @dataProvider requestTypeProvider - */ - public function testItValidatesTheRequestWithPayload(string $method) + /** @dataProvider requestTypeProvider */ + public function test_it_validates_the_request_with_payload(string $method) { - $request = $this->$method(static::PATH, 'post', ['foo' => 'bar']); - $result = $this->sut->validate($request, static::PATH, 'post'); + $request = $this->{$method}(static::PATH, 'post', ['foo' => 'bar']); + $result = $this->sut->validate($request, static::PATH, 'post'); $this->assertTrue($result); } @@ -93,13 +84,11 @@ public function bodylessRequestMethodProvider(): array ]; } - /** - * @dataProvider bodylessRequestMethodProvider - */ - public function testItValidatesTheRequestWithoutPayloadUsingMethodShortcuts(string $method) + /** @dataProvider bodylessRequestMethodProvider */ + public function test_it_validates_the_request_without_payload_using_method_shortcuts(string $method) { $request = $this->httpFoundationRequest(static::PATH, $method); - $result = $this->sut->validate($request, static::PATH, $method); + $result = $this->sut->validate($request, static::PATH, $method); $this->assertTrue($result); } @@ -113,13 +102,11 @@ public function requestMethodProvider(): array ]; } - /** - * @dataProvider requestMethodProvider - */ - public function testItValidatesTheRequestWithPaylodUsingShortcuts(string $method) + /** @dataProvider requestMethodProvider */ + public function test_it_validates_the_request_with_paylod_using_shortcuts(string $method) { $request = $this->httpFoundationRequest(static::PATH, $method, ['foo' => 'bar']); - $result = $this->sut->validate($request, static::PATH, $method); + $result = $this->sut->validate($request, static::PATH, $method); $this->assertTrue($result); } @@ -128,7 +115,7 @@ public function testItValidatesTheRequestWithPaylodUsingShortcuts(string $method |-------------------------------------------------------------------------- | Responses |-------------------------------------------------------------------------- - */ + */ public function responseTypeProvider(): array { @@ -138,26 +125,22 @@ public function responseTypeProvider(): array ]; } - /** - * @dataProvider responseTypeProvider - */ - public function testItDoesNotValidateTheResponse(string $method) + /** @dataProvider responseTypeProvider */ + public function test_it_does_not_validate_the_response(string $method) { $this->expectException(ValidationException::class); $this->expectExceptionMessage('Body does not match schema for content-type "application/json" for Response [get /test 200]: Keyword validation failed: Required property \'foo\' must be present in the object Field: foo'); - $response = $this->$method(['baz' => 'bar']); + $response = $this->{$method}(['baz' => 'bar']); $this->sut->validate($response, static::PATH, 'get'); } - /** - * @dataProvider responseTypeProvider - */ - public function testItValidatesTheResponse(string $method) + /** @dataProvider responseTypeProvider */ + public function test_it_validates_the_response(string $method) { - $response = $this->$method(['foo' => 'bar']); - $result = $this->sut->validate($response, static::PATH, 'get'); + $response = $this->{$method}(['foo' => 'bar']); + $result = $this->sut->validate($response, static::PATH, 'get'); $this->assertTrue($result); } @@ -175,12 +158,10 @@ public function responseMethodProvider(): array ]; } - /** - * @dataProvider responseMethodProvider - */ - public function testItValidatesTheResponseUsingMethodShortcuts(string $method) + /** @dataProvider responseMethodProvider */ + public function test_it_validates_the_response_using_method_shortcuts(string $method) { - $result = $this->sut->$method($this->httpFoundationResponse(), static::PATH); + $result = $this->sut->{$method}($this->httpFoundationResponse(), static::PATH); $this->assertTrue($result); } @@ -189,7 +170,7 @@ public function testItValidatesTheResponseUsingMethodShortcuts(string $method) |-------------------------------------------------------------------------- | Misc |-------------------------------------------------------------------------- - */ + */ public function pathProvider(): array { @@ -199,13 +180,11 @@ public function pathProvider(): array ]; } - /** - * @dataProvider pathProvider - */ - public function testItFixesThePath(string $path) + /** @dataProvider pathProvider */ + public function test_it_fixes_the_path(string $path) { $response = $this->httpFoundationResponse(['foo' => 'bar']); - $result = $this->sut->validate($response, $path, 'get'); + $result = $this->sut->validate($response, $path, 'get'); $this->assertTrue($result); } From 6233585dd3954fb8b38dd6b08e060c76223b7a08 Mon Sep 17 00:00:00 2001 From: Yannick Chenot Date: Fri, 16 Jun 2023 13:27:26 +0100 Subject: [PATCH 05/19] Update issue templates --- .github/ISSUE_TEMPLATE/bug-report.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug-report.md diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 0000000..4aa75db --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,28 @@ +--- +name: Bug report +about: Report something that is broken +title: '' +labels: '' +assignees: '' + +--- + + + +**Package version** +[e.g. 0.1] + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behaviour: +1. Go to '...' +2. Run '...' +3. '...' + +**Expected behaviour** +A clear and concise description of what you expected to happen. + +**Additional context** +Add any other context about the problem here. From ff5c3fb2f544ca8368bdc5a1b0cfb52186f59ae3 Mon Sep 17 00:00:00 2001 From: osteel Date: Fri, 16 Jun 2023 13:43:27 +0100 Subject: [PATCH 06/19] updated contributing --- .github/CODE_OF_CONDUCT.md | 74 -------------------------------------- .github/CONTRIBUTING.md | 13 +++---- README.md | 13 +++---- 3 files changed, 10 insertions(+), 90 deletions(-) delete mode 100644 .github/CODE_OF_CONDUCT.md diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md deleted file mode 100644 index 4f13cfb..0000000 --- a/.github/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,74 +0,0 @@ -# Contributor Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to make participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, gender identity and expression, level of experience, -nationality, personal appearance, race, religion, or sexual identity and -orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or -advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at `hello@yannickchenot.com`. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at [http://contributor-covenant.org/version/1/4][version] - -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 88f8f15..255163c 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -2,12 +2,11 @@ Contributions are **welcome** and will be fully **credited**. -We accept contributions via Pull Requests on [Github](https://github.com/osteel/openapi-httpfoundation-testing). - +We accept contributions via Pull Requests on Github. ## Pull Requests -- **[PSR-12 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-12-extended-coding-style-guide.md)** - Check the code style with ``$ composer check-style`` and fix it with ``$ composer fix-style``. +- **[PSR-12 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-12-extended-coding-style-guide.md)** - Fix the code style with `composer fix`. - **Add tests!** - Your patch won't be accepted if it doesn't have tests. @@ -19,14 +18,12 @@ We accept contributions via Pull Requests on [Github](https://github.com/osteel/ - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. -- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. - +- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. -## Running Tests +## Testing -``` bash +```bash $ composer test ``` - **Happy coding**! diff --git a/README.md b/README.md index dc28b24..a06bf4e 100644 --- a/README.md +++ b/README.md @@ -93,23 +93,20 @@ The `validate` method returns `true` in case of success, and throws `\Osteel\Ope ## Change log -Please see the [Releases section](../../releases) for more information on what has changed recently. - -## Testing - -```bash -$ composer test -``` +Please see the [Releases section](../../releases) for details. ## Contributing -Please see [CONTRIBUTING](CONTRIBUTING.md) and [CODE_OF_CONDUCT](CODE_OF_CONDUCT.md) for details. +Please see [CONTRIBUTING](/.github/CONTRIBUTING.md) for details. ## Credits **People** - [Yannick Chenot](https://github.com/osteel) +- [Patrick Rodacker](https://github.com/lordrhodos) +- [Johnathan Michael Dell](https://github.com/johnathanmdell) +- [Paul Mitchum](https://github.com/paul-m) - [All Contributors](../../contributors) Special thanks to [Pavel Batanov](https://github.com/scaytrase) for his advice on structuring the package. From db981ee8319812c1949216a5d98ecb9dbcfd38c2 Mon Sep 17 00:00:00 2001 From: osteel Date: Fri, 16 Jun 2023 13:48:03 +0100 Subject: [PATCH 07/19] updated PR template --- .github/PULL_REQUEST_TEMPLATE.md | 47 +++++--------------------------- 1 file changed, 7 insertions(+), 40 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 86246b3..6c7a674 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,43 +1,10 @@ - +## Summary -## Description +## Explanation -Describe your changes in detail. +## Checklist -## Motivation and context - -Why is this change required? What problem does it solve? - -If it fixes an open issue, please link to the issue here (if you write `fixes #num` -or `closes #num`, the issue will be automatically closed when the pull is accepted.) - -## How has this been tested? - -Please describe in detail how you tested your changes. - -Include details of your testing environment, and the tests you ran to -see how your change affects other areas of the code, etc. - -## Screenshots (if appropriate) - -## Types of changes - -What types of changes does your code introduce? Put an `x` in all the boxes that apply: -- [ ] Bug fix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Breaking change (fix or feature that would cause existing functionality to change) - -## Checklist: - -Go over all the following points, and put an `x` in all the boxes that apply. - -Please, please, please, don't send your pull request until all of the boxes are ticked. Once your pull request is created, it will trigger a build on our [continuous integration](http://www.phptherightway.com/#continuous-integration) server to make sure your [tests and code style pass](https://help.github.com/articles/about-required-status-checks/). - -- [ ] I have read the **[CONTRIBUTING](CONTRIBUTING.md)** document. -- [ ] My pull request addresses exactly one patch/feature. -- [ ] I have created a branch for this patch/feature. -- [ ] Each individual commit in the pull request is meaningful. -- [ ] I have added tests to cover my changes. -- [ ] If my change requires a change to the documentation, I have updated it accordingly. - -If you're unsure about any of these, don't hesitate to ask. We're here to help! +- [ ] I have provided a summary and an explanation +- [ ] I have reviewed the PR myself and left comments to provide context +- [ ] I have covered the changes with tests as appropriate +- [ ] I have made sure static analysis and other checks are successful From fa26238afafdaaf907932b0919d097fafb800122 Mon Sep 17 00:00:00 2001 From: osteel Date: Fri, 16 Jun 2023 13:55:17 +0100 Subject: [PATCH 08/19] updated .gitattributes --- .gitattributes | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/.gitattributes b/.gitattributes index 3cabf6a..921af42 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,17 +1,14 @@ -# Path-based git attributes -# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html - * text=auto eol=lf -# Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore -/.gitattributes export-ignore -/.gitignore export-ignore -/.scrutinizer.yml export-ignore -/.travis.yml export-ignore -/PULL_REQUEST_TEMPLATE.md export-ignore -/ISSUE_TEMPLATE.md export-ignore -/phpcs.xml.dist export-ignore -/phpunit.xml.dist export-ignore -/tests export-ignore -/docs export-ignore +/.github export-ignore +/src export-ignore +/tests export-ignore +/.editorconfig export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.php-cs-fixer.cache export-ignore +/.php-cs-fixer.dist.php export-ignore +/.phpunit.result.cache export-ignore +/composer.lock export-ignore +/phpunit.xml.dist export-ignore +rector.php export-ignore \ No newline at end of file From 98bbeb77967f86e8b02147086f8cdce8eb598264 Mon Sep 17 00:00:00 2001 From: Yannick Chenot Date: Fri, 16 Jun 2023 17:22:52 +0100 Subject: [PATCH 09/19] reverted use of union types (#32) --- src/Adapters/AdapterInterface.php | 8 +-- src/Adapters/HttpFoundationAdapter.php | 11 +++- src/Validator.php | 64 +++++++++----------- src/ValidatorInterface.php | 60 +++++++++--------- tests/Adapters/HttpFoundationAdapterTest.php | 9 +++ 5 files changed, 76 insertions(+), 76 deletions(-) diff --git a/src/Adapters/AdapterInterface.php b/src/Adapters/AdapterInterface.php index 58289bb..a9eae07 100644 --- a/src/Adapters/AdapterInterface.php +++ b/src/Adapters/AdapterInterface.php @@ -5,17 +5,13 @@ namespace Osteel\OpenApi\Testing\Adapters; use Psr\Http\Message\MessageInterface; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; interface AdapterInterface { /** * Convert a HTTP message to a PSR-7 HTTP message. * - * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to convert + * @param object $message the HTTP message to convert */ - public function convert(Request|Response|ResponseInterface|ServerRequestInterface $message): MessageInterface; + public function convert(object $message): MessageInterface; } diff --git a/src/Adapters/HttpFoundationAdapter.php b/src/Adapters/HttpFoundationAdapter.php index 87b1c45..1d750de 100644 --- a/src/Adapters/HttpFoundationAdapter.php +++ b/src/Adapters/HttpFoundationAdapter.php @@ -4,6 +4,7 @@ namespace Osteel\OpenApi\Testing\Adapters; +use InvalidArgumentException; use Nyholm\Psr7\Factory\Psr17Factory; use Psr\Http\Message\MessageInterface; use Psr\Http\Message\ResponseInterface; @@ -17,9 +18,9 @@ final class HttpFoundationAdapter implements AdapterInterface /** * @inheritDoc * - * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to convert + * @param object $message the HTTP message to convert */ - public function convert(Request|Response|ResponseInterface|ServerRequestInterface $message): MessageInterface + public function convert(object $message): MessageInterface { if ($message instanceof ResponseInterface || $message instanceof ServerRequestInterface) { return $message; @@ -32,6 +33,10 @@ public function convert(Request|Response|ResponseInterface|ServerRequestInterfac return $psrHttpFactory->createResponse($message); } - return $psrHttpFactory->createRequest($message); + if ($message instanceof Request) { + return $psrHttpFactory->createRequest($message); + } + + throw new InvalidArgumentException(sprintf('Unsupported %s object received', $message::class)); } } diff --git a/src/Validator.php b/src/Validator.php index b6e83fc..cfeecd7 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -11,9 +11,6 @@ use Osteel\OpenApi\Testing\Adapters\AdapterInterface; use Osteel\OpenApi\Testing\Exceptions\ValidationException; use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; final class Validator implements ValidatorInterface { @@ -27,17 +24,14 @@ public function __construct( /** * @inheritDoc * - * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate - * @param string $path the OpenAPI path - * @param string $method the HTTP method + * @param object $message the HTTP message to validate + * @param string $path the OpenAPI path + * @param string $method the HTTP method * * @throws ValidationException */ - public function validate( - Request|Response|ResponseInterface|ServerRequestInterface $message, - string $path, - string $method, - ): bool { + public function validate(object $message, string $path, string $method): bool + { $message = $this->adapter->convert($message); $operation = $this->getOperationAddress($path, $method); $validator = $message instanceof ResponseInterface ? $this->responseValidator : $this->requestValidator; @@ -68,12 +62,12 @@ private function getOperationAddress(string $path, string $method): OperationAdd /** * @inheritDoc * - * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate - * @param string $path the OpenAPI path + * @param object $message the HTTP message to validate + * @param string $path the OpenAPI path * * @throws ValidationFailed */ - public function delete(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool + public function delete(object $message, string $path): bool { return $this->validate($message, $path, 'delete'); } @@ -81,12 +75,12 @@ public function delete(Request|Response|ResponseInterface|ServerRequestInterface /** * @inheritDoc * - * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate - * @param string $path the OpenAPI path + * @param object $message the HTTP message to validate + * @param string $path the OpenAPI path * * @throws ValidationFailed */ - public function get(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool + public function get(object $message, string $path): bool { return $this->validate($message, $path, 'get'); } @@ -94,12 +88,12 @@ public function get(Request|Response|ResponseInterface|ServerRequestInterface $m /** * @inheritDoc * - * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate - * @param string $path the OpenAPI path + * @param object $message the HTTP message to validate + * @param string $path the OpenAPI path * * @throws ValidationFailed */ - public function head(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool + public function head(object $message, string $path): bool { return $this->validate($message, $path, 'head'); } @@ -107,12 +101,12 @@ public function head(Request|Response|ResponseInterface|ServerRequestInterface $ /** * @inheritDoc * - * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate - * @param string $path the OpenAPI path + * @param object $message the HTTP message to validate + * @param string $path the OpenAPI path * * @throws ValidationFailed */ - public function options(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool + public function options(object $message, string $path): bool { return $this->validate($message, $path, 'options'); } @@ -120,12 +114,12 @@ public function options(Request|Response|ResponseInterface|ServerRequestInterfac /** * @inheritDoc * - * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate - * @param string $path the OpenAPI path + * @param object $message the HTTP message to validate + * @param string $path the OpenAPI path * * @throws ValidationFailed */ - public function patch(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool + public function patch(object $message, string $path): bool { return $this->validate($message, $path, 'patch'); } @@ -133,12 +127,12 @@ public function patch(Request|Response|ResponseInterface|ServerRequestInterface /** * @inheritDoc * - * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate - * @param string $path the OpenAPI path + * @param object $message the HTTP message to validate + * @param string $path the OpenAPI path * * @throws ValidationFailed */ - public function post(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool + public function post(object $message, string $path): bool { return $this->validate($message, $path, 'post'); } @@ -146,12 +140,12 @@ public function post(Request|Response|ResponseInterface|ServerRequestInterface $ /** * @inheritDoc * - * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate - * @param string $path the OpenAPI path + * @param object $message the HTTP message to validate + * @param string $path the OpenAPI path * * @throws ValidationFailed */ - public function put(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool + public function put(object $message, string $path): bool { return $this->validate($message, $path, 'put'); } @@ -159,12 +153,12 @@ public function put(Request|Response|ResponseInterface|ServerRequestInterface $m /** * @inheritDoc * - * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate - * @param string $path the OpenAPI path + * @param object $message the HTTP message to validate + * @param string $path the OpenAPI path * * @throws ValidationFailed */ - public function trace(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool + public function trace(object $message, string $path): bool { return $this->validate($message, $path, 'trace'); } diff --git a/src/ValidatorInterface.php b/src/ValidatorInterface.php index 98b7fda..179e16f 100644 --- a/src/ValidatorInterface.php +++ b/src/ValidatorInterface.php @@ -6,101 +6,97 @@ use League\OpenAPIValidation\PSR7\Exception\ValidationFailed; use Osteel\OpenApi\Testing\Exceptions\ValidationException; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; interface ValidatorInterface { /** * Validate a HTTP message against the specified OpenAPI definition. * - * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate - * @param string $path the OpenAPI path - * @param string $method the HTTP method + * @param object $message the HTTP message to validate + * @param string $path the OpenAPI path + * @param string $method the HTTP method * * @throws ValidationException */ - public function validate(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path, string $method): bool; + public function validate(object $message, string $path, string $method): bool; /** * Validate a HTTP message for a DELETE operation on the provided OpenAPI definition path. * - * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate - * @param string $path the OpenAPI path + * @param object $message the HTTP message to validate + * @param string $path the OpenAPI path * * @throws ValidationFailed */ - public function delete(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool; + public function delete(object $message, string $path): bool; /** * Validate a HTTP message for a GET operation on the provided OpenAPI definition path. * - * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate - * @param string $path the OpenAPI path + * @param object $message the HTTP message to validate + * @param string $path the OpenAPI path * * @throws ValidationFailed */ - public function get(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool; + public function get(object $message, string $path): bool; /** * Validate a HTTP message for a HEAD operation on the provided OpenAPI definition path. * - * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate - * @param string $path the OpenAPI path + * @param object $message the HTTP message to validate + * @param string $path the OpenAPI path * * @throws ValidationFailed */ - public function head(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool; + public function head(object $message, string $path): bool; /** * Validate a HTTP message for a OPTIONS operation on the provided OpenAPI definition path. * - * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate - * @param string $path the OpenAPI path + * @param object $message the HTTP message to validate + * @param string $path the OpenAPI path * * @throws ValidationFailed */ - public function options(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool; + public function options(object $message, string $path): bool; /** * Validate a HTTP message for a PATCH operation on the provided OpenAPI definition path. * - * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate - * @param string $path the OpenAPI path + * @param object $message the HTTP message to validate + * @param string $path the OpenAPI path * * @throws ValidationFailed */ - public function patch(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool; + public function patch(object $message, string $path): bool; /** * Validate a HTTP message for a POST operation on the provided OpenAPI definition path. * - * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate - * @param string $path the OpenAPI path + * @param object $message the HTTP message to validate + * @param string $path the OpenAPI path * * @throws ValidationFailed */ - public function post(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool; + public function post(object $message, string $path): bool; /** * Validate a HTTP message for a PUT operation on the provided OpenAPI definition path. * - * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate - * @param string $path the OpenAPI path + * @param object $message the HTTP message to validate + * @param string $path the OpenAPI path * * @throws ValidationFailed */ - public function put(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool; + public function put(object $message, string $path): bool; /** * Validate a HTTP message for a TRACE operation on the provided OpenAPI definition path. * - * @param Request|Response|ResponseInterface|ServerRequestInterface $message the HTTP message to validate - * @param string $path the OpenAPI path + * @param object $message the HTTP message to validate + * @param string $path the OpenAPI path * * @throws ValidationFailed */ - public function trace(Request|Response|ResponseInterface|ServerRequestInterface $message, string $path): bool; + public function trace(object $message, string $path): bool; } diff --git a/tests/Adapters/HttpFoundationAdapterTest.php b/tests/Adapters/HttpFoundationAdapterTest.php index 00e8c26..41c0f48 100644 --- a/tests/Adapters/HttpFoundationAdapterTest.php +++ b/tests/Adapters/HttpFoundationAdapterTest.php @@ -2,6 +2,7 @@ namespace Osteel\OpenApi\Testing\Tests\Adapters; +use InvalidArgumentException; use Osteel\OpenApi\Testing\Adapters\HttpFoundationAdapter; use Osteel\OpenApi\Testing\Tests\TestCase; use Psr\Http\Message\ResponseInterface; @@ -20,6 +21,14 @@ protected function setUp(): void $this->sut = new HttpFoundationAdapter(); } + public function test_it_does_not_convert_the_message_because_the_type_is_not_supported() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Unsupported InvalidArgumentException object received'); + + $this->sut->convert(new InvalidArgumentException()); + } + public function test_it_converts_the_http_foundation_request() { $result = $this->sut->convert(Request::create('/foo')); From bf6c52fa7e69f023662962b6b876688beaf7e4c8 Mon Sep 17 00:00:00 2001 From: Yannick Chenot Date: Mon, 19 Jun 2023 14:00:14 +0100 Subject: [PATCH 10/19] cache support (#33) --- .gitattributes | 1 - README.md | 63 ++++++++++++++++--- composer.json | 11 +++- src/Adapters/HttpFoundationAdapter.php | 4 +- ...erface.php => MessageAdapterInterface.php} | 2 +- src/Cache/CacheAdapterInterface.php | 17 +++++ src/Cache/Psr16Adapter.php | 31 +++++++++ src/Validator.php | 4 +- src/ValidatorBuilder.php | 55 ++++++++++++---- src/ValidatorBuilderInterface.php | 3 + tests/Adapters/HttpFoundationAdapterTest.php | 5 +- tests/Cache/Psr16AdapterTest.php | 45 +++++++++++++ tests/ValidatorBuilderTest.php | 35 ++++++++--- 13 files changed, 240 insertions(+), 36 deletions(-) rename src/Adapters/{AdapterInterface.php => MessageAdapterInterface.php} (90%) create mode 100644 src/Cache/CacheAdapterInterface.php create mode 100644 src/Cache/Psr16Adapter.php create mode 100644 tests/Cache/Psr16AdapterTest.php diff --git a/.gitattributes b/.gitattributes index 921af42..c211397 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,7 +1,6 @@ * text=auto eol=lf /.github export-ignore -/src export-ignore /tests export-ignore /.editorconfig export-ignore /.gitattributes export-ignore diff --git a/README.md b/README.md index a06bf4e..f56836b 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ See [this post](https://tech.osteel.me/posts/openapi-backed-api-testing-in-php-p ## Why? -[OpenAPI](https://swagger.io/specification/) is a specification intended to describe RESTful APIs in a way that is understood by humans and machines alike. +[OpenAPI](https://swagger.io/specification/) is a specification intended to describe RESTful APIs in a way that can be understood by both humans and machines. By validating an API's requests and responses against the OpenAPI definition that describes it, we guarantee that the API is used correctly and behaves in accordance with the documentation we provide, thus making the OpenAPI definition the single source of truth. @@ -22,21 +22,21 @@ The [HttpFoundation component](https://symfony.com/doc/current/components/http_f ## How does it work? -This package is built upon the [OpenAPI PSR-7 Message Validator](https://github.com/thephpleague/openapi-psr7-validator) one, which validates [PSR-7 messages](https://www.php-fig.org/psr/psr-7/) against OpenAPI definitions. +This package is built on top of [OpenAPI PSR-7 Message Validator](https://github.com/thephpleague/openapi-psr7-validator), which validates [PSR-7 messages](https://www.php-fig.org/psr/psr-7/) against OpenAPI definitions. It converts HttpFoundation request and response objects to PSR-7 messages using Symfony's [PSR-7 Bridge](https://symfony.com/doc/current/components/psr7.html) and [Tobias Nyholm](https://github.com/Nyholm)'s [PSR-7 implementation](https://github.com/Nyholm/psr7), before passing them on to OpenAPI PSR-7 Message Validator. ## Installation +> **Note** +> This package is mostly intended to be used as part of an API test suite. + Via Composer: ```bash $ composer require --dev osteel/openapi-httpfoundation-testing ``` -> **Note** -> This package is mostly intended to be used as part of an API test suite. - ## Usage Import the builder class: @@ -45,7 +45,7 @@ Import the builder class: use Osteel\OpenApi\Testing\ValidatorBuilder; ``` -Use the builder to create a `\Osteel\OpenApi\Testing\Validator` object, using one of the available factory methods for YAML or JSON: +Use the builder to create a [`\Osteel\OpenApi\Testing\Validator`](/src/Validator.php) object, using one of the available factory methods for YAML or JSON: ```php // From a file: @@ -64,6 +64,9 @@ $validator = ValidatorBuilder::fromYaml($yamlFileOrString)->getValidator(); $validator = ValidatorBuilder::fromJson($jsonFileOrString)->getValidator(); ``` +> **Note** +> You can also use a dependency injection container to bind the `ValidatorBuilder` class to the [`ValidatorBuilderInterface`](/src/ValidatorBuilderInterface.php) interface it implements and inject the interface instead, which would also be useful for testing and mocking. + You can now validate `\Symfony\Component\HttpFoundation\Request` and `\Symfony\Component\HttpFoundation\Response` objects for a given [path](https://swagger.io/specification/#paths-object) and method: ```php @@ -89,7 +92,53 @@ $validator->post($request, '/users'); In the example above, we check that the request matches the OpenAPI definition for a `POST` request on the `/users` path. -The `validate` method returns `true` in case of success, and throws `\Osteel\OpenApi\Testing\Exceptions\ValidationException` exceptions in case of error. +The `validate` method returns `true` in case of success, and throw a [`\Osteel\OpenApi\Testing\Exceptions\ValidationException`](/src/Exceptions/ValidationException.php) exception in case of error. + +## Caching + +This package supports caching to speed up the parsing of OpenAPI definitions. Simply pass your [PSR-6](https://www.php-fig.org/psr/psr-6/) or [PSR-16](https://www.php-fig.org/psr/psr-16/) cache object to the `setCache` method of the [`ValidatorBuilder`](/src/ValidatorBuilder.php) class. + +Here is an example using Symfony's [Array Cache Adapter](https://symfony.com/doc/current/components/cache/adapters/array_cache_adapter.html "Array Cache Adapter"): + +```php +use Osteel\OpenApi\Testing\ValidatorBuilder; +use Symfony\Component\Cache\Adapter\ArrayAdapter; + +$cache = new ArrayAdapter(); +$validator = ValidatorBuilder::fromYamlFile($yamlFile)->setCache($cache)->getValidator(); +``` + +## Extending the package + +There are two main extension points – message adapters and cache adapters. + +### Message adapters + +The [`ValidatorBuilder`](/src/ValidatorBuilder.php) class uses the [`HttpFoundationAdapter`](/src/Adapters/HttpFoundationAdapter.php) class as its default HTTP message adapter. This class converts HttpFoundation request and response objects to their PSR-7 counterparts. + +If you need to change the adapter's logic, or if you need a new adapter altogether, create a class implementing the [`MessageAdapterInterface`](/src/Adapters/MessageAdapterInterface.php) interface and pass it to the `setMessageAdapter` method of the [`ValidatorBuilder`](/src/ValidatorBuilder.php) class: + +```php +$validator = ValidatorBuilder::fromYamlFile($yamlFile) + ->setMessageAdapter($yourAdapter) + ->getValidator(); +``` + +### Cache adapters + +The [`ValidatorBuilder`](/src/ValidatorBuilder.php) class uses the [`Psr16Adapter`](/src/Cache/Psr16Adapter.php) class as its default cache adapter. This class converts PSR-16 cache objects to their PSR-6 counterparts. + +If you need to change the adapter's logic, or if you need a new adapter altogether, create a class implementing the [`CacheAdapterInterface`](/src/Cache/CacheAdapterInterface.php) interface and pass it to the `setCacheAdapter` method of the [`ValidatorBuilder`](/src/ValidatorBuilder.php) class: + +```php +$validator = ValidatorBuilder::fromYamlFile($yamlFile) + ->setCacheAdapter($yourAdapter) + ->getValidator(); +``` + +### Other interfaces + +The [`ValidatorBuilder`](/src/ValidatorBuilder.php) and [`Validator`](/src/Validator.php) classes are `final` but they implement the [`ValidatorBuilderInterface`](/src/ValidatorBuilderInterface.php) and [`ValidatorInterface`](/src/ValidatorInterface.php) interfaces respectively for which you can provide your own implementations if you need to. ## Change log diff --git a/composer.json b/composer.json index c835b14..04b3480 100644 --- a/composer.json +++ b/composer.json @@ -27,13 +27,16 @@ "ext-json": "*", "league/openapi-psr7-validator": "^0.21", "nyholm/psr7": "^1.0", + "psr/cache": "^3.0", "psr/http-message": "^1.0", + "psr/simple-cache": "^3.0", + "symfony/cache": "^6.0", "symfony/http-foundation": "^4.0 || ^5.0 || ^6.0", "symfony/psr-http-message-bridge": "^2.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.17", - "phpunit/phpunit": "^9.0", + "phpunit/phpunit": "^9.6", "rector/rector": "^0.17.1" }, "autoload": { @@ -47,8 +50,12 @@ } }, "scripts": { - "test": "phpunit", "fix": "php-cs-fixer fix -v", + "test": "phpunit", + "all": [ + "@fix", + "@test" + ], "refactor": "rector process" }, "extra": { diff --git a/src/Adapters/HttpFoundationAdapter.php b/src/Adapters/HttpFoundationAdapter.php index 1d750de..c5e3c43 100644 --- a/src/Adapters/HttpFoundationAdapter.php +++ b/src/Adapters/HttpFoundationAdapter.php @@ -13,10 +13,10 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -final class HttpFoundationAdapter implements AdapterInterface +final class HttpFoundationAdapter implements MessageAdapterInterface { /** - * @inheritDoc + * Convert a HttpFoundation object to a PSR-7 HTTP message. * * @param object $message the HTTP message to convert */ diff --git a/src/Adapters/AdapterInterface.php b/src/Adapters/MessageAdapterInterface.php similarity index 90% rename from src/Adapters/AdapterInterface.php rename to src/Adapters/MessageAdapterInterface.php index a9eae07..4de8937 100644 --- a/src/Adapters/AdapterInterface.php +++ b/src/Adapters/MessageAdapterInterface.php @@ -6,7 +6,7 @@ use Psr\Http\Message\MessageInterface; -interface AdapterInterface +interface MessageAdapterInterface { /** * Convert a HTTP message to a PSR-7 HTTP message. diff --git a/src/Cache/CacheAdapterInterface.php b/src/Cache/CacheAdapterInterface.php new file mode 100644 index 0000000..cc93da0 --- /dev/null +++ b/src/Cache/CacheAdapterInterface.php @@ -0,0 +1,17 @@ +cacheAdapter(); + + $this->validatorBuilder->setCache($adapter->convert($cache)); + + return $this; + } + /** @inheritDoc */ public function getValidator(): ValidatorInterface { @@ -108,25 +122,42 @@ public function getValidator(): ValidatorInterface } /** - * Change the adapter to use. The provided class must implement - * \Osteel\OpenApi\Testing\Adapters\AdapterInterface. + * Change the adapter to use. The provided class must implement \Osteel\OpenApi\Testing\Adapters\AdapterInterface. * * @param string $class the adapter's class * * @throws InvalidArgumentException */ - public function setAdapter(string $class): ValidatorBuilder + public function setMessageAdapter(string $class): ValidatorBuilder { - if (! is_subclass_of($class, AdapterInterface::class)) { - throw new InvalidArgumentException(sprintf( - 'Class %s does not implement the %s interface', - $class, - AdapterInterface::class - )); + if (is_subclass_of($class, MessageAdapterInterface::class)) { + $this->adapter = $class; + + return $this; } - $this->adapter = $class; + throw new InvalidArgumentException( + sprintf('Class %s does not implement the %s interface', $class, MessageAdapterInterface::class), + ); + } + + /** + * Change the cache adapter to use. The provided class must implement \Osteel\OpenApi\Testing\Cache\AdapterInterface. + * + * @param string $class the cache adapter's class + * + * @throws InvalidArgumentException + */ + public function setCacheAdapter(string $class): ValidatorBuilder + { + if (is_subclass_of($class, CacheAdapterInterface::class)) { + $this->cacheAdapter = $class; - return $this; + return $this; + } + + throw new InvalidArgumentException( + sprintf('Class %s does not implement the %s interface', $class, CacheAdapterInterface::class), + ); } } diff --git a/src/ValidatorBuilderInterface.php b/src/ValidatorBuilderInterface.php index 04a2036..667bee0 100644 --- a/src/ValidatorBuilderInterface.php +++ b/src/ValidatorBuilderInterface.php @@ -48,6 +48,9 @@ public static function fromYamlString(string $definition): ValidatorBuilderInter */ public static function fromJsonString(string $definition): ValidatorBuilderInterface; + /** Set a cache library. */ + public function setCache(object $cache): ValidatorBuilderInterface; + /** Return the validator. */ public function getValidator(): ValidatorInterface; } diff --git a/tests/Adapters/HttpFoundationAdapterTest.php b/tests/Adapters/HttpFoundationAdapterTest.php index 41c0f48..a073cfd 100644 --- a/tests/Adapters/HttpFoundationAdapterTest.php +++ b/tests/Adapters/HttpFoundationAdapterTest.php @@ -9,6 +9,7 @@ use Psr\Http\Message\ServerRequestInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use stdClass; class HttpFoundationAdapterTest extends TestCase { @@ -24,9 +25,9 @@ protected function setUp(): void public function test_it_does_not_convert_the_message_because_the_type_is_not_supported() { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Unsupported InvalidArgumentException object received'); + $this->expectExceptionMessage('Unsupported stdClass object received'); - $this->sut->convert(new InvalidArgumentException()); + $this->sut->convert(new stdClass()); } public function test_it_converts_the_http_foundation_request() diff --git a/tests/Cache/Psr16AdapterTest.php b/tests/Cache/Psr16AdapterTest.php new file mode 100644 index 0000000..9d18d6f --- /dev/null +++ b/tests/Cache/Psr16AdapterTest.php @@ -0,0 +1,45 @@ +sut = new Psr16Adapter(); + } + + public function test_it_does_not_convert_the_caching_library_because_the_type_is_not_supported() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Unsupported stdClass object received'); + + $this->sut->convert(new stdClass()); + } + + public function test_it_converts_the_psr16_caching_library() + { + $result = $this->sut->convert($this->createMock(CacheInterface::class)); + + $this->assertInstanceOf(CacheItemPoolInterface::class, $result); + } + + public function test_it_leaves_the_psr6_caching_library_untouched() + { + $cache = $this->createMock(CacheItemPoolInterface::class); + $result = $this->sut->convert($cache); + + $this->assertEquals($cache, $result); + } +} diff --git a/tests/ValidatorBuilderTest.php b/tests/ValidatorBuilderTest.php index 2737acf..f10f023 100644 --- a/tests/ValidatorBuilderTest.php +++ b/tests/ValidatorBuilderTest.php @@ -5,9 +5,11 @@ namespace Osteel\OpenApi\Testing\Tests; use InvalidArgumentException; -use Osteel\OpenApi\Testing\Adapters\AdapterInterface; +use Osteel\OpenApi\Testing\Adapters\MessageAdapterInterface; +use Osteel\OpenApi\Testing\Cache\CacheAdapterInterface; use Osteel\OpenApi\Testing\Validator; use Osteel\OpenApi\Testing\ValidatorBuilder; +use stdClass; class ValidatorBuilderTest extends TestCase { @@ -45,19 +47,38 @@ public function test_it_does_not_set_the_adapter_because_its_type_is_invalid() $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage(sprintf( 'Class %s does not implement the %s interface', - InvalidArgumentException::class, - AdapterInterface::class + stdClass::class, + MessageAdapterInterface::class )); - ValidatorBuilder::fromYaml(self::$yamlDefinition)->setAdapter(InvalidArgumentException::class); + ValidatorBuilder::fromYaml(self::$yamlDefinition)->setMessageAdapter(stdClass::class); } public function test_it_sets_the_adapter() { ValidatorBuilder::fromYaml(self::$yamlDefinition) - ->setAdapter($this->createMock(AdapterInterface::class)::class); + ->setMessageAdapter($this->createMock(MessageAdapterInterface::class)::class); - // No exception means the test was successful. - $this->assertTrue(true); + $this->addToAssertionCount(1); + } + + public function test_it_does_not_set_the_cache_adapter_because_its_type_is_invalid() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf( + 'Class %s does not implement the %s interface', + stdClass::class, + CacheAdapterInterface::class + )); + + ValidatorBuilder::fromYaml(self::$yamlDefinition)->setCacheAdapter(stdClass::class); + } + + public function test_it_sets_the_cache_adapter() + { + ValidatorBuilder::fromYaml(self::$yamlDefinition) + ->setCacheAdapter($this->createMock(CacheAdapterInterface::class)::class); + + $this->addToAssertionCount(1); } } From 7716190dd7d339b07605e8b416e95e38b9b521cb Mon Sep 17 00:00:00 2001 From: Yannick Chenot Date: Mon, 19 Jun 2023 16:16:15 +0100 Subject: [PATCH 11/19] phpstan (#34) --- .gitattributes | 3 ++- .github/CONTRIBUTING.md | 14 +++++--------- .github/workflows/ci.yml | 21 +++++++++++++++++++-- composer.json | 3 +++ phpstan.neon.dist | 7 +++++++ src/Adapters/HttpFoundationAdapter.php | 3 +-- src/Adapters/MessageAdapterInterface.php | 5 +++-- src/ValidatorBuilder.php | 2 ++ 8 files changed, 42 insertions(+), 16 deletions(-) create mode 100644 phpstan.neon.dist diff --git a/.gitattributes b/.gitattributes index c211397..5e43a5e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -9,5 +9,6 @@ /.php-cs-fixer.dist.php export-ignore /.phpunit.result.cache export-ignore /composer.lock export-ignore +/phpstan.neon.dist export-ignore /phpunit.xml.dist export-ignore -rector.php export-ignore \ No newline at end of file +/rector.php export-ignore \ No newline at end of file diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 255163c..b45ad9c 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -6,24 +6,20 @@ We accept contributions via Pull Requests on Github. ## Pull Requests -- **[PSR-12 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-12-extended-coding-style-guide.md)** - Fix the code style with `composer fix`. +- **[PSR-12 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-12-extended-coding-style-guide.md)** - Fix the code's style with `composer fix`. -- **Add tests!** - Your patch won't be accepted if it doesn't have tests. +- **[PHPStan level 9](https://phpstan.org/user-guide/rule-levels)** - Check compliance with `composer type`. + +- **Add tests!** - Your contribution won't be accepted if it doesn't have tests – Run the test suite with `composer test`. - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. -- **Create feature branches** - Don't ask us to pull from your master branch. +- **Create feature branches** - Don't ask us to pull from your main branch. - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. -## Testing - -```bash -$ composer test -``` - **Happy coding**! diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f970c7b..904563c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - name: Checkout code uses: actions/checkout@v3 - - name: Setup PHP + - name: Set up PHP uses: shivammathur/setup-php@v2 - name: Install composer dependencies @@ -25,6 +25,23 @@ jobs: - name: Check coding style run: vendor/bin/php-cs-fixer fix --dry-run + type: + name: Type + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + + - name: Install composer dependencies + uses: ramsey/composer-install@v2 + + - name: Check code typing + run: vendor/bin/phpstan + tests: name: Tests runs-on: ubuntu-latest @@ -55,5 +72,5 @@ jobs: with: dependency-versions: ${{ matrix.dependency-versions }} - - name: Run PHPUnit + - name: Run tests run: vendor/bin/phpunit tests diff --git a/composer.json b/composer.json index 04b3480..1094415 100644 --- a/composer.json +++ b/composer.json @@ -36,6 +36,7 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.17", + "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^9.6", "rector/rector": "^0.17.1" }, @@ -52,8 +53,10 @@ "scripts": { "fix": "php-cs-fixer fix -v", "test": "phpunit", + "type": "phpstan", "all": [ "@fix", + "@type", "@test" ], "refactor": "rector process" diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..0263f62 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,7 @@ +parameters: + level: 9 + + paths: + - src + + checkMissingCallableSignature: true \ No newline at end of file diff --git a/src/Adapters/HttpFoundationAdapter.php b/src/Adapters/HttpFoundationAdapter.php index c5e3c43..f003814 100644 --- a/src/Adapters/HttpFoundationAdapter.php +++ b/src/Adapters/HttpFoundationAdapter.php @@ -6,7 +6,6 @@ use InvalidArgumentException; use Nyholm\Psr7\Factory\Psr17Factory; -use Psr\Http\Message\MessageInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory; @@ -20,7 +19,7 @@ final class HttpFoundationAdapter implements MessageAdapterInterface * * @param object $message the HTTP message to convert */ - public function convert(object $message): MessageInterface + public function convert(object $message): ResponseInterface|ServerRequestInterface { if ($message instanceof ResponseInterface || $message instanceof ServerRequestInterface) { return $message; diff --git a/src/Adapters/MessageAdapterInterface.php b/src/Adapters/MessageAdapterInterface.php index 4de8937..bd77e62 100644 --- a/src/Adapters/MessageAdapterInterface.php +++ b/src/Adapters/MessageAdapterInterface.php @@ -4,7 +4,8 @@ namespace Osteel\OpenApi\Testing\Adapters; -use Psr\Http\Message\MessageInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; interface MessageAdapterInterface { @@ -13,5 +14,5 @@ interface MessageAdapterInterface * * @param object $message the HTTP message to convert */ - public function convert(object $message): MessageInterface; + public function convert(object $message): ResponseInterface|ServerRequestInterface; } diff --git a/src/ValidatorBuilder.php b/src/ValidatorBuilder.php index 13ac1f6..dac8918 100644 --- a/src/ValidatorBuilder.php +++ b/src/ValidatorBuilder.php @@ -16,8 +16,10 @@ */ final class ValidatorBuilder implements ValidatorBuilderInterface { + /** @var class-string */ private string $adapter = HttpFoundationAdapter::class; + /** @var class-string */ private string $cacheAdapter = Psr16Adapter::class; public function __construct(private BaseValidatorBuilder $validatorBuilder) From 09eca4e09df1902b125d69297535bf5c6a5b9791 Mon Sep 17 00:00:00 2001 From: Yannick Chenot Date: Wed, 21 Jun 2023 18:11:18 +0100 Subject: [PATCH 12/19] restored support for older dependency versions (#36) --- composer.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 1094415..d591f97 100644 --- a/composer.json +++ b/composer.json @@ -26,13 +26,13 @@ "php": "^8.0", "ext-json": "*", "league/openapi-psr7-validator": "^0.21", - "nyholm/psr7": "^1.0", - "psr/cache": "^3.0", + "nyholm/psr7": "^1.3.1", + "psr/cache": "^1.0 || ^2.0 || ^3.0", "psr/http-message": "^1.0", - "psr/simple-cache": "^3.0", - "symfony/cache": "^6.0", - "symfony/http-foundation": "^4.0 || ^5.0 || ^6.0", - "symfony/psr-http-message-bridge": "^2.0" + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", + "symfony/cache": "^5.0.9 || ^6.0", + "symfony/http-foundation": "^5.0.9 || ^6.0", + "symfony/psr-http-message-bridge": "^2.0.1" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.17", From 60119526d3f98183ab83a0830bae72c0dd52d6ad Mon Sep 17 00:00:00 2001 From: Tom de Wit Date: Wed, 29 Nov 2023 17:06:52 +0100 Subject: [PATCH 13/19] Add Symfony 7 support (#37) Add ^7.0 version constraint to Symfony Cache and Symfony HTTP foundation packages, plus the PSR HTTP message bridge. --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index d591f97..7ac3191 100644 --- a/composer.json +++ b/composer.json @@ -30,9 +30,9 @@ "psr/cache": "^1.0 || ^2.0 || ^3.0", "psr/http-message": "^1.0", "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", - "symfony/cache": "^5.0.9 || ^6.0", - "symfony/http-foundation": "^5.0.9 || ^6.0", - "symfony/psr-http-message-bridge": "^2.0.1" + "symfony/cache": "^5.0.9 || ^6.0 || ^7.0", + "symfony/http-foundation": "^5.0.9 || ^6.0 || ^7.0", + "symfony/psr-http-message-bridge": "^2.0.1 || ^7.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.17", From 420c26ad0a66c4c18c948b04b61298af3ec4df3b Mon Sep 17 00:00:00 2001 From: Tom de Wit Date: Thu, 30 Nov 2023 09:51:07 +0100 Subject: [PATCH 14/19] Fix deprecation issues for PHP CS Fixer (#38) Co-authored-by: Tom de Wit --- .php-cs-fixer.dist.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index fdb2201..bda0986 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -32,7 +32,7 @@ 'explicit_indirect_variable' => true, 'explicit_string_variable' => true, 'fully_qualified_strict_types' => true, - 'function_typehint_space' => true, + 'type_declaration_spaces' => true, 'general_phpdoc_tag_rename' => [ 'replacements' => ['inheritDocs' => 'inheritDoc'], ], @@ -59,7 +59,7 @@ 'strategy' => 'no_multi_line', ], 'native_function_casing' => true, - 'native_function_type_declaration_casing' => true, + 'native_type_declaration_casing' => true, 'no_alias_language_construct_call' => true, 'no_alternative_syntax' => true, 'no_blank_lines_after_phpdoc' => true, @@ -96,7 +96,7 @@ 'yield_from', ], ], - 'no_unneeded_curly_braces' => [ + 'no_unneeded_braces' => [ 'namespaces' => false, ], 'no_unneeded_import_alias' => true, From 276f7fad1da1b1f980e02fd198fbb8ed01f641d0 Mon Sep 17 00:00:00 2001 From: Tom de Wit Date: Thu, 30 Nov 2023 09:53:42 +0100 Subject: [PATCH 15/19] Add PHP 8.3 to testing matrix (#39) Co-authored-by: Tom de Wit --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 904563c..6ccd13c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,6 +53,7 @@ jobs: - "8.0" - "8.1" - "8.2" + - "8.3" dependency-versions: - "lowest" - "highest" From 978cfc7c212db6000ba4d2716a430ef74902e66a Mon Sep 17 00:00:00 2001 From: Yannick Chenot Date: Fri, 12 Jan 2024 15:36:27 +0000 Subject: [PATCH 16/19] Dependency bumps (#40) * updated symfony/psr-http-message-bridge dependency version * updated symfony/psr-http-message dependency version * updated league/openapi-psr7-validator dependency version --- composer.json | 6 +++--- tests/TestCase.php | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 7ac3191..6806ef4 100644 --- a/composer.json +++ b/composer.json @@ -25,14 +25,14 @@ "require": { "php": "^8.0", "ext-json": "*", - "league/openapi-psr7-validator": "^0.21", + "league/openapi-psr7-validator": "^0.22", "nyholm/psr7": "^1.3.1", "psr/cache": "^1.0 || ^2.0 || ^3.0", - "psr/http-message": "^1.0", + "psr/http-message": "^1.0 || ^2.0", "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", "symfony/cache": "^5.0.9 || ^6.0 || ^7.0", "symfony/http-foundation": "^5.0.9 || ^6.0 || ^7.0", - "symfony/psr-http-message-bridge": "^2.0.1 || ^7.0" + "symfony/psr-http-message-bridge": "^2.0 || ^6.0 || ^7.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.17", diff --git a/tests/TestCase.php b/tests/TestCase.php index 2aaece9..9ea7361 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -7,6 +7,7 @@ use Nyholm\Psr7\Factory\Psr17Factory; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\StreamInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -42,7 +43,10 @@ protected function psr7Response(?array $content = null): ResponseInterface return $response; } - $response->method('getBody')->willReturn(json_encode($content, JSON_THROW_ON_ERROR)); + $body = $this->createMock(StreamInterface::class); + $body->method('__toString')->willReturn(json_encode($content, JSON_THROW_ON_ERROR)); + + $response->method('getBody')->willReturn($body); $response->method('getStatusCode')->willReturn(Response::HTTP_OK); $response->method('getHeader')->willReturn(['application/json']); From 4431bcf455feeca643cf0a885701d1e1f496f6eb Mon Sep 17 00:00:00 2001 From: osteel Date: Fri, 12 Jan 2024 15:41:04 +0000 Subject: [PATCH 17/19] prettier README --- README.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f56836b..8980725 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,7 @@ Validate HttpFoundation requests and responses against OpenAPI (3+) definitions. See [this post](https://tech.osteel.me/posts/openapi-backed-api-testing-in-php-projects-a-laravel-example "OpenAPI-backed API testing in PHP projects – a Laravel example") for more details and [this repository](https://github.com/osteel/openapi-httpfoundation-testing-laravel-example) for an example use in a Laravel project. -> **Note** -> While you can safely use this package for your projects, as long as version `1.0` has not been released "minor" version patches can contain breaking changes. Make sure to check the [release section](../../releases) before you upgrade. +> 💡 While you can safely use this package for your projects, as long as version `1.0` has not been released "minor" version patches can contain breaking changes. Make sure to check the [release section](../../releases) before you upgrade. ## Why? @@ -28,8 +27,7 @@ It converts HttpFoundation request and response objects to PSR-7 messages using ## Installation -> **Note** -> This package is mostly intended to be used as part of an API test suite. +> 💡 This package is mostly intended to be used as part of an API test suite. Via Composer: @@ -64,8 +62,7 @@ $validator = ValidatorBuilder::fromYaml($yamlFileOrString)->getValidator(); $validator = ValidatorBuilder::fromJson($jsonFileOrString)->getValidator(); ``` -> **Note** -> You can also use a dependency injection container to bind the `ValidatorBuilder` class to the [`ValidatorBuilderInterface`](/src/ValidatorBuilderInterface.php) interface it implements and inject the interface instead, which would also be useful for testing and mocking. +> 💡 You can also use a dependency injection container to bind the `ValidatorBuilder` class to the [`ValidatorBuilderInterface`](/src/ValidatorBuilderInterface.php) interface it implements and inject the interface instead, which would also be useful for testing and mocking. You can now validate `\Symfony\Component\HttpFoundation\Request` and `\Symfony\Component\HttpFoundation\Response` objects for a given [path](https://swagger.io/specification/#paths-object) and method: @@ -73,8 +70,7 @@ You can now validate `\Symfony\Component\HttpFoundation\Request` and `\Symfony\C $validator->validate($response, '/users', 'post'); ``` -> **Note** -> For convenience, objects implementing `\Psr\Http\Message\ServerRequestInterface` or `\Psr\Http\Message\ResponseInterface` are also accepted. +> 💡 For convenience, objects implementing `\Psr\Http\Message\ServerRequestInterface` or `\Psr\Http\Message\ResponseInterface` are also accepted. In the example above, we check that the response matches the OpenAPI definition for a `POST` request on the `/users` path. From 4f126cd4e050227dd7a41f7fdf9933f67bcabf76 Mon Sep 17 00:00:00 2001 From: Olivier Dolbeau Date: Mon, 7 Oct 2024 17:13:41 +0200 Subject: [PATCH 18/19] Update README.md (#42) Remove `$` symbol which prevent copy / pasting the line easily --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8980725..bfb59aa 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ It converts HttpFoundation request and response objects to PSR-7 messages using Via Composer: ```bash -$ composer require --dev osteel/openapi-httpfoundation-testing +composer require --dev osteel/openapi-httpfoundation-testing ``` ## Usage From f310d2052eaf66ad665abd9971b3c13e8db1e2dd Mon Sep 17 00:00:00 2001 From: Yannick Chenot Date: Mon, 7 Oct 2024 16:35:45 +0100 Subject: [PATCH 19/19] fixed README (#43) --- README.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index bfb59aa..915e863 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,17 @@ # OpenAPI HttpFoundation Testing -[![Build Status](https://github.com/osteel/php-cli-demo/workflows/CI/badge.svg)](https://github.com/osteel/php-cli-demo/actions) -[![Latest Stable Version](https://poser.pugx.org/osteel/openapi-httpfoundation-testing/v)](//packagist.org/packages/osteel/openapi-httpfoundation-testing) -[![License](https://poser.pugx.org/osteel/openapi-httpfoundation-testing/license)](//packagist.org/packages/osteel/openapi-httpfoundation-testing) -[![Downloads](http://poser.pugx.org/osteel/openapi-httpfoundation-testing/downloads)](//packagist.org/packages/osteel/openapi-httpfoundation-testing) +[![Build Status](https://github.com/osteel/openapi-httpfoundation-testing/workflows/CI/badge.svg)](https://github.com/osteel/openapi-httpfoundation-testing/actions) +[![Latest Stable Version](https://img.shields.io/packagist/v/osteel/openapi-httpfoundation-testing)](https://packagist.org/packages/osteel/openapi-httpfoundation-testing) +[![License](https://img.shields.io/packagist/l/osteel/openapi-httpfoundation-testing)](https://packagist.org/packages/osteel/openapi-httpfoundation-testing) +[![Downloads](https://img.shields.io/packagist/dt/osteel/openapi-httpfoundation-testing)](https://packagist.org/packages/osteel/openapi-httpfoundation-testing) + Validate HttpFoundation requests and responses against OpenAPI (3+) definitions. See [this post](https://tech.osteel.me/posts/openapi-backed-api-testing-in-php-projects-a-laravel-example "OpenAPI-backed API testing in PHP projects – a Laravel example") for more details and [this repository](https://github.com/osteel/openapi-httpfoundation-testing-laravel-example) for an example use in a Laravel project. -> 💡 While you can safely use this package for your projects, as long as version `1.0` has not been released "minor" version patches can contain breaking changes. Make sure to check the [release section](../../releases) before you upgrade. +> [!IMPORTANT] +> While you can safely use this package for your projects, as long as version `1.0` has not been released "minor" version patches can contain breaking changes. Make sure to check the [release section](../../releases) before you upgrade. ## Why? @@ -27,7 +29,8 @@ It converts HttpFoundation request and response objects to PSR-7 messages using ## Installation -> 💡 This package is mostly intended to be used as part of an API test suite. +> [!NOTE] +> This package is mostly intended to be used as part of an API test suite. Via Composer: @@ -62,7 +65,8 @@ $validator = ValidatorBuilder::fromYaml($yamlFileOrString)->getValidator(); $validator = ValidatorBuilder::fromJson($jsonFileOrString)->getValidator(); ``` -> 💡 You can also use a dependency injection container to bind the `ValidatorBuilder` class to the [`ValidatorBuilderInterface`](/src/ValidatorBuilderInterface.php) interface it implements and inject the interface instead, which would also be useful for testing and mocking. +> [!TIP] +> You can also use a dependency injection container to bind the `ValidatorBuilder` class to the [`ValidatorBuilderInterface`](/src/ValidatorBuilderInterface.php) interface it implements and inject the interface instead, which would also be useful for testing and mocking. You can now validate `\Symfony\Component\HttpFoundation\Request` and `\Symfony\Component\HttpFoundation\Response` objects for a given [path](https://swagger.io/specification/#paths-object) and method: @@ -70,7 +74,8 @@ You can now validate `\Symfony\Component\HttpFoundation\Request` and `\Symfony\C $validator->validate($response, '/users', 'post'); ``` -> 💡 For convenience, objects implementing `\Psr\Http\Message\ServerRequestInterface` or `\Psr\Http\Message\ResponseInterface` are also accepted. +> [!TIP] +> For convenience, objects implementing `\Psr\Http\Message\ServerRequestInterface` or `\Psr\Http\Message\ResponseInterface` are also accepted. In the example above, we check that the response matches the OpenAPI definition for a `POST` request on the `/users` path.