diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a6ac9a5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +vendor +build +#A library must not provide a composer.lock file +composer.lock diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100644 index 0000000..5a1a311 --- /dev/null +++ b/.scrutinizer.yml @@ -0,0 +1,92 @@ +build_failure_conditions: + - 'project.metric_change("scrutinizer.quality", < -0.30)' + - 'elements.rating(<= D).exists' # No classes/methods with a rating of D or worse + - 'issues.severity(>= MAJOR).exists' # New major or higher severity issues + - 'project.metric("scrutinizer.quality", < 9)' # Code Quality Rating drops below 9 + - 'project.metric("scrutinizer.test_coverage", < 1)' # Code Coverage must alway be 100% + - 'patches.label("Doc Comments").exists' # No doc comments patches allowed + - 'patches.label("Spacing").exists' # No spacing patches allowed + - 'patches.label("Bug").exists' # No bug patches allowed + - 'issues.label("coding-style").exists' # No coding style issues allowed +build: + dependencies: + override: + - make build + tests: + stop_on_failure: true + override: + - php-scrutinizer-run --enable-security-analysis + - + command: make codestyle + analysis: + file: 'build/reports/cs-data' + format: 'php-cs-checkstyle' + - + command: make coverage + idle_timeout: 1200 + coverage: + file: 'build/coverage/clover.xml' + format: 'php-clover' + cache: + directories: + - ~/.composer + - vendor + + environment: + variables: + CI: 'true' + TEST_OUTPUT_STYLE: 'pretty' + COMPOSER_OPTIONS: '--optimize-autoloader' + COVERAGE_OUTPUT_STYLE: 'clover' + COVERAGE_CLOVER_FILE_PATH: 'build/coverage/clover.xml' + PHPCS_REPORT_STYLE: 'checkstyle' + PHPCS_REPORT_FILE: 'build/reports/cs-data' + php: + version: "7.1" + timezone: UTC + postgresql: false + redis: false +filter: + paths: + - src/* +checks: + php: + code_rating: true + duplication: true + no_debug_code: true + check_method_contracts: + verify_interface_like_constraints: true + verify_documented_constraints: true + verify_parent_constraints: true + simplify_boolean_return: true + return_doc_comments: true + return_doc_comment_if_not_inferrable: true + remove_extra_empty_lines: true + properties_in_camelcaps: true + phpunit_assertions: true + parameters_in_camelcaps: true + parameter_doc_comments: true + param_doc_comment_if_not_inferrable: true + overriding_parameter: true + no_trailing_whitespace: true + no_short_variable_names: + minimum: '3' + no_short_method_names: + minimum: '3' + no_long_variable_names: + maximum: '20' + no_goto: true + naming_conventions: + local_variable: '^[a-z][a-zA-Z0-9]*$' + abstract_class_name: ^Abstract|Factory$ + utility_class_name: 'Utils?$' + constant_name: '^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$' + property_name: '^[a-z][a-zA-Z0-9]*$' + method_name: '^(?:[a-z]|__)[a-zA-Z0-9]*$' + parameter_name: '^[a-z][a-zA-Z0-9]*$' + interface_name: '^[A-Z][a-zA-Z0-9]*Interface$' + type_name: '^[A-Z][a-zA-Z0-9]*$' + exception_name: '^[A-Z][a-zA-Z0-9]*Exception$' + isser_method_name: '^(?:is|has|should|may|supports)' + more_specific_types_in_doc_comments: true + fix_doc_comments: false diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b9cd43f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,33 @@ +language: php + +php: + - '7.0' + - '7.1' + - '7.2' + +env: + global: + CI: 'true' + TEST_OUTPUT_STYLE: 'pretty' + PHPCS_REPORT_STYLE: 'full' + COMPOSER_OPTIONS: '--optimize-autoloader' + +sudo: false + +matrix: + fast_finish: true + +before_install: + # remove xdebug to speed up build + - phpenv config-rm xdebug.ini + +install: + - make build +script: + - make test-technical + - make test-functional + +cache: + directories: + - $HOME/.composer + - vendor diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5741f51 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +# Contributing + +## Getting Started + * Fork, then clone the repo: +```bash +git clone git@github.com:your-username/symfony-jsonrpc-http-server-openapi-doc.git +```` + + * Make sure everything goes well: +```bash +make build +make test +``` + + * Make your changes (Add/Update tests according to your changes). + * Make sure tests are still green: +```bash +make test +``` + + * To check code coverage, launch +```bash +make coverage +``` + + * Push to your fork and [submit a pull request](https://github.com/yoanm/symfony-jsonrpc-http-server-openapi-doc/compare/). + * Wait for feedback or merge. + + Some stuff that will increase your pull request's acceptance: + * Write tests. + * Follow PSR-2 coding style. + * Write good commit messages. + * Do not rebase or squash your commits when a review has been made. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..61a3638 --- /dev/null +++ b/Makefile @@ -0,0 +1,95 @@ +COLOR_ENABLED ?= true +TEST_OUTPUT_STYLE ?= dot +COVERAGE_OUTPUT_STYLE ?= html + +## DIRECTORY AND FILE +BUILD_DIRECTORY ?= build +REPORTS_DIRECTORY ?= ${BUILD_DIRECTORY}/reports +COVERAGE_DIRECTORY ?= ${BUILD_DIRECTORY}/coverage +COVERAGE_CLOVER_FILE_PATH ?= ${COVERAGE_DIRECTORY}/clover.xml + +## Commands options +### Composer +#COMPOSER_OPTIONS= +### Phpcs +PHPCS_REPORT_STYLE ?= full +#PHPCS_REPORT_FILE= +#PHPCS_REPORT_FILE_OPTION= + +# Enable/Disable color ouput +ifeq ("${COLOR_ENABLED}","true") + PHPUNIT_COLOR_OPTION ?= --colors=always + BEHAT_COLOR_OPTION ?= --colors + PHPCS_COLOR_OPTION ?= --colors + COMPOSER_COLOR_OPTION ?= --ansi +else + PHPUNIT_COLOR_OPTION ?= --colors=never + PHPCS_COLOR_OPTION ?= --no-colors + BEHAT_COLOR_OPTION ?= --no-colors + COMPOSER_COLOR_OPTION ?= --no-ansi +endif + +ifeq ("${TEST_OUTPUT_STYLE}","pretty") + PHPUNIT_OUTPUT_STYLE_OPTION ?= --testdox + BEHAT_OUTPUT_STYLE_OPTION ?= --format pretty +else + PHPUNIT_OUTPUT_STYLE_OPTION ?= + BEHAT_OUTPUT_STYLE_OPTION ?= --format progress +endif + +ifeq ("${COVERAGE_OUTPUT_STYLE}","clover") + PHPUNIT_COVERAGE_OPTION ?= --coverage-clover ${COVERAGE_CLOVER_FILE_PATH} +else + ifeq ("${COVERAGE_OUTPUT_STYLE}","html") + PHPUNIT_COVERAGE_OPTION ?= --coverage-html ${COVERAGE_DIRECTORY} + else + PHPUNIT_COVERAGE_OPTION ?= --coverage-text + endif +endif + +ifneq ("${PHPCS_REPORT_FILE}","") + PHPCS_REPORT_FILE_OPTION ?= --report-file=${PHPCS_REPORT_FILE} +endif + + +## Project build (install and configure) +build: install configure + +## Project installation +install: + composer install ${COMPOSER_COLOR_OPTION} ${COMPOSER_OPTIONS} --prefer-dist --no-suggest --no-interaction + +## project Configuration +configure: + +# Project tests +test: + make test-functional + make test-technical + make codestyle + +test-technical: + ./vendor/bin/phpunit ${PHPUNIT_COLOR_OPTION} ${PHPUNIT_OUTPUT_STYLE_OPTION} --testsuite technical + +test-functional: + ./vendor/bin/phpunit ${PHPUNIT_COLOR_OPTION} ${PHPUNIT_OUTPUT_STYLE_OPTION} --testsuite functional + ./vendor/bin/behat ${BEHAT_COLOR_OPTION} ${BEHAT_OUTPUT_STYLE_OPTION} --no-snippets + +codestyle: create-reports-directory + ./vendor/bin/phpcs --standard=phpcs.xml.dist ${PHPCS_COLOR_OPTION} ${PHPCS_REPORT_FILE_OPTION} --report=${PHPCS_REPORT_STYLE} + +coverage: create-coverage-directory + ./vendor/bin/phpunit ${PHPUNIT_COLOR_OPTION} ${PHPUNIT_OUTPUT_STYLE_OPTION} ${PHPUNIT_COVERAGE_OPTION} + + + +# Internal commands +create-coverage-directory: + mkdir -p ${COVERAGE_DIRECTORY} + +create-reports-directory: + mkdir -p ${REPORTS_DIRECTORY} + + +.PHONY: build install configure test test-technical test-functional codestyle coverage create-coverage-directory create-reports-directory +.DEFAULT: build diff --git a/README.md b/README.md new file mode 100644 index 0000000..482af6d --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# Symfony JSON-RPC Http server OpenAPI documentation + +[![License](https://img.shields.io/github/license/yoanm/symfony-jsonrpc-http-server-openapi-doc.svg)](https://github.com/yoanm/symfony-jsonrpc-http-server-openapi-doc) [![Code size](https://img.shields.io/github/languages/code-size/yoanm/symfony-jsonrpc-http-server-openapi-doc.svg)](https://github.com/yoanm/symfony-jsonrpc-http-server-openapi-doc) [![PHP Versions](https://img.shields.io/badge/php-7.0%20%2F%207.1%20%2F%207.2-8892BF.svg)](https://php.net/) + +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/yoanm/symfony-jsonrpc-http-server-openapi-doc/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/yoanm/symfony-jsonrpc-http-server-openapi-doc/?branch=master) [![Build Status](https://scrutinizer-ci.com/g/yoanm/symfony-jsonrpc-http-server-openapi-doc/badges/build.png?b=master)](https://scrutinizer-ci.com/g/yoanm/symfony-jsonrpc-http-server-openapi-doc/build-status/master) [![Code Coverage](https://scrutinizer-ci.com/g/yoanm/symfony-jsonrpc-http-server-openapi-doc/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/yoanm/symfony-jsonrpc-http-server-openapi-doc/?branch=master) + +[![Travis Build Status](https://img.shields.io/travis/yoanm/symfony-jsonrpc-http-server-openapi-doc/master.svg?label=travis)](https://travis-ci.org/yoanm/symfony-jsonrpc-http-server-openapi-doc) [![Travis PHP versions](https://img.shields.io/travis/php-v/yoanm/symfony-jsonrpc-http-server-openapi-doc.svg)](https://travis-ci.org/yoanm/symfony-jsonrpc-http-server-openapi-doc) + +[![Latest Stable Version](https://img.shields.io/packagist/v/yoanm/symfony-jsonrpc-http-server-openapi-doc.svg)](https://packagist.org/packages/yoanm/symfony-jsonrpc-http-server-openapi-doc) [![Packagist PHP version](https://img.shields.io/packagist/php-v/yoanm/symfony-jsonrpc-http-server-openapi-doc.svg)](https://packagist.org/packages/yoanm/symfony-jsonrpc-http-server-openapi-doc) + +Symfony bundle for easy JSON-RPC server OpenAPI 3.0.0 documentation + +## How to use +   + +## Contributing +See [contributing note](./CONTRIBUTING.md) diff --git a/behat.yml b/behat.yml new file mode 100644 index 0000000..a5ce152 --- /dev/null +++ b/behat.yml @@ -0,0 +1,5 @@ +default: + suites: + default: + contexts: + - Tests\Functional\BehatContext\FeatureContext: ~ diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..69beefd --- /dev/null +++ b/composer.json @@ -0,0 +1,46 @@ +{ + "name": "yoanm/symfony-jsonrpc-http-server-openapi-doc", + "description": "Symfony bundle for easy JSON-RPC server OpenAPI 3.0.0 documentation", + "license": "MIT", + "type": "library", + "support": { + "issues": "https://github.com/yoanm/symfony-jsonrpc-http-server-openapi-doc/issues" + }, + "authors": [ + { + "name": "Yoanm", + "email": "yoanm@users.noreply.github.com", + "role": "Developer" + } + ], + "autoload": { + "psr-4": { + "Yoanm\\SymfonyJsonRpcHttpServerOpenAPIDoc\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests", + "Tests\\Functional\\BehatContext\\": "features/bootstrap" + } + }, + "require": { + "php": ">=5.5", + "yoanm/jsonrpc-server-doc-sdk": "dev-feature/improve", + "yoanm/jsonrpc-http-server-openapi-doc-sdk": "dev-feature/improve", + "yoanm/symfony-jsonrpc-http-server-doc": "dev-feature/improve", + "symfony/http-foundation": "^3.0 || ^4.0", + "symfony/http-kernel": "^3.0 || ^4.0", + "symfony/config": "^3.0 || ^4.0", + "symfony/dependency-injection": "^3.0 || ^4.0" + }, + "require-dev": { + "behat/behat": "~3.0", + "squizlabs/php_codesniffer": "3.*", + "phpunit/phpunit": "^6.0 || ^7.0", + "matthiasnoback/symfony-dependency-injection-test": "^2.0 || ^3.0", + "matthiasnoback/symfony-config-test": "^3.0 || ^4.0", + "symfony/framework-bundle": "^3.4", + "yoanm/php-unit-extended": "~1.0" + } +} diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php new file mode 100644 index 0000000..f562608 --- /dev/null +++ b/features/bootstrap/FeatureContext.php @@ -0,0 +1,21 @@ + + + src + tests + features/bootstrap + + + + + + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..cb7c344 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,49 @@ + + + + + + + + + + + tests/Functional + + + tests/Technical + + + + + + src + + + diff --git a/src/.gitkeep b/src/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/DependencyInjection/JsonRpcHttpServerOpenAPIDocExtension.php b/src/DependencyInjection/JsonRpcHttpServerOpenAPIDocExtension.php new file mode 100644 index 0000000..53a0748 --- /dev/null +++ b/src/DependencyInjection/JsonRpcHttpServerOpenAPIDocExtension.php @@ -0,0 +1,53 @@ +load('services.sdk.yaml'); + $loader->load('services.public.yaml'); + } + + /** + * {@inheritdoc} + */ + public function getNamespace() + { + return 'http://example.org/schema/dic/'.$this->getAlias(); + } + + /** + * {@inheritdoc} + */ + public function getXsdValidationBasePath() + { + return ''; + } + + /** + * {@inheritdoc} + */ + public function getAlias() + { + return self::EXTENSION_IDENTIFIER; + } +} diff --git a/src/Event/OpenAPIDocCreatedEvent.php b/src/Event/OpenAPIDocCreatedEvent.php new file mode 100644 index 0000000..1b9ac3e --- /dev/null +++ b/src/Event/OpenAPIDocCreatedEvent.php @@ -0,0 +1,56 @@ +openAPIDoc = $openAPIDoc; + $this->serverDoc = $serverDoc; + } + + /** + * @return HttpServerDoc + */ + public function getOpenAPIDoc() + { + return $this->openAPIDoc; + } + + /** + * @return HttpServerDoc|null + */ + public function getServerDoc() + { + return $this->serverDoc; + } + + /** + * @param array $openAPIDoc + * + * @return OpenAPIDocCreatedEvent + */ + public function setOpenAPIDoc(array $openAPIDoc) + { + $this->openAPIDoc = $openAPIDoc; + + return $this; + } +} diff --git a/src/JsonRpcHttpServerOpenAPIDocBundle.php b/src/JsonRpcHttpServerOpenAPIDocBundle.php new file mode 100644 index 0000000..a4cf7ed --- /dev/null +++ b/src/JsonRpcHttpServerOpenAPIDocBundle.php @@ -0,0 +1,11 @@ +dispatcher = $dispatcher; + $this->HttpServerDocCreator = $HttpServerDocCreator; + $this->docNormalizer = $docNormalizer; + } + + /** + * @param string|null $host + * + * @return array + */ + public function getDoc($host = null) + { + $rawDoc = $this->HttpServerDocCreator->create($host); + + $openApiDoc = $this->docNormalizer->normalize($rawDoc); + + $event = new OpenAPIDocCreatedEvent($openApiDoc, $rawDoc); + $this->dispatcher->dispatch($event::EVENT_NAME, $event); + + return $event->getOpenAPIDoc(); + } + + /** + * {@inheritdoc} + */ + public function supports($filename, $host = null) + { + return 'openapi.json' === $filename; + } +} diff --git a/src/Resources/config/services.public.yaml b/src/Resources/config/services.public.yaml new file mode 100644 index 0000000..d9f186d --- /dev/null +++ b/src/Resources/config/services.public.yaml @@ -0,0 +1,12 @@ +services: + _defaults: + public: true + + json_rpc_http_server_open_api_doc.provider: + class: Yoanm\SymfonyJsonRpcHttpServerOpenAPIDoc\Provider\DocProvider + arguments: + - '@event_dispatcher' + - '@json_rpc_http_server_doc.creator.http_server' + - '@json_rpc_http_server_open_api_doc_sdk.normalizer.doc' + tags: ['json_rpc_server_doc.doc_provider'] + diff --git a/src/Resources/config/services.sdk.yaml b/src/Resources/config/services.sdk.yaml new file mode 100644 index 0000000..e0280c8 --- /dev/null +++ b/src/Resources/config/services.sdk.yaml @@ -0,0 +1,58 @@ +services: + _defaults: + public: true + + + # Def resolver + json_rpc_http_server_open_api_doc_sdk.resolver.definition_ref: + class: Yoanm\JsonRpcHttpServerOpenAPIDoc\Resolver\DefinitionRefResolver + + + # Normalizer + json_rpc_http_server_open_api_doc_sdk.normalizer.doc: + class: Yoanm\JsonRpcHttpServerOpenAPIDoc\Normalizer\DocNormalizer + arguments: + - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.request_doc' + - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.response' + - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.external_schema_list' + - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.operation' + + ## Components + json_rpc_http_server_open_api_doc_sdk.normalizer.component.schema_type: + class: Yoanm\JsonRpcHttpServerOpenAPIDoc\Normalizer\Component\SchemaTypeNormalizer + json_rpc_http_server_open_api_doc_sdk.normalizer.component.parameter_doc: + class: Yoanm\JsonRpcHttpServerOpenAPIDoc\Normalizer\Component\TypeDocNormalizer + arguments: + - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.schema_type' + json_rpc_http_server_open_api_doc_sdk.normalizer.component.shape: + class: Yoanm\JsonRpcHttpServerOpenAPIDoc\Normalizer\Component\ShapeNormalizer + arguments: + - '@json_rpc_http_server_open_api_doc_sdk.resolver.definition_ref' + json_rpc_http_server_open_api_doc_sdk.normalizer.component.error: + class: Yoanm\JsonRpcHttpServerOpenAPIDoc\Normalizer\Component\ErrorDocNormalizer + arguments: + - '@json_rpc_http_server_open_api_doc_sdk.resolver.definition_ref' + - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.parameter_doc' + - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.shape' + json_rpc_http_server_open_api_doc_sdk.normalizer.component.external_schema_list: + class: Yoanm\JsonRpcHttpServerOpenAPIDoc\Normalizer\Component\ExternalSchemaListDocNormalizer + arguments: + - '@json_rpc_http_server_open_api_doc_sdk.resolver.definition_ref' + - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.parameter_doc' + - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.error' + json_rpc_http_server_open_api_doc_sdk.normalizer.component.request_doc: + class: Yoanm\JsonRpcHttpServerOpenAPIDoc\Normalizer\Component\RequestDocNormalizer + arguments: + - '@json_rpc_http_server_open_api_doc_sdk.resolver.definition_ref' + - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.shape' + json_rpc_http_server_open_api_doc_sdk.normalizer.component.response: + class: Yoanm\JsonRpcHttpServerOpenAPIDoc\Normalizer\Component\ResponseDocNormalizer + arguments: + - '@json_rpc_http_server_open_api_doc_sdk.resolver.definition_ref' + - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.shape' + json_rpc_http_server_open_api_doc_sdk.normalizer.component.operation: + class: Yoanm\JsonRpcHttpServerOpenAPIDoc\Normalizer\Component\OperationDocNormalizer + arguments: + - '@json_rpc_http_server_open_api_doc_sdk.resolver.definition_ref' + - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.request_doc' + - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.response' diff --git a/tests/Common/DependencyInjection/AbstractTestClass.php b/tests/Common/DependencyInjection/AbstractTestClass.php new file mode 100644 index 0000000..da704f4 --- /dev/null +++ b/tests/Common/DependencyInjection/AbstractTestClass.php @@ -0,0 +1,91 @@ +compile(); + } + + + protected function assertEndpointIsUsable() + { + // Retrieving this service will imply to load all related dependencies + // Any binding issues will be raised + $this->assertNotNull($this->container->get(self::EXPECTED_ENDPOINT_SERVICE_ID)); + } + + /** + * @param $jsonRpcMethodServiceId + */ + protected function assertJsonRpcMethodServiceIsAvailable($jsonRpcMethodServiceId) + { + $this->assertNotNull($this->container->get($jsonRpcMethodServiceId)); + } + + /** + * @return Definition + */ + protected function createJsonRpcMethodDefinition() + { + return (new Definition(\stdClass::class)) + ->setPrivate(false); + } + + /** + * @param Definition $definition + * @param string $methodName + */ + protected function addJsonRpcMethodTag(Definition $definition, $methodName) + { + $definition->addTag( + self::EXPECTED_JSONRPC_METHOD_TAG, + [self::EXPECTED_JSONRPC_METHOD_TAG_METHOD_NAME_KEY => $methodName] + ); + } + + /** + * @return Definition + */ + protected function createCustomMethodResolverDefinition() + { + $customResolverService = new Definition($this->prophesize(MethodResolverInterface::class)->reveal()); + $customResolverService->addTag(self::EXPECTED_METHOD_RESOLVER_TAG); + + return $customResolverService; + } +} diff --git a/tests/Functional/.gitkeep b/tests/Functional/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/Functional/DependencyInjection/JsonRpcHttpServerExtensionTest.php b/tests/Functional/DependencyInjection/JsonRpcHttpServerExtensionTest.php new file mode 100644 index 0000000..83b79df --- /dev/null +++ b/tests/Functional/DependencyInjection/JsonRpcHttpServerExtensionTest.php @@ -0,0 +1,220 @@ +load(); + + $this->assertContainerBuilderHasService(self::EXPECTED_ENDPOINT_SERVICE_ID, JsonRpcHttpEndpoint::class); + + // Check that service is accessible through the container + $this->assertNotNull($this->container->get(self::EXPECTED_ENDPOINT_SERVICE_ID)); + + $this->assertEndpointIsUsable(); + } + + public function testShouldReturnAnXsdValidationBasePath() + { + $this->assertNotNull((new JsonRpcHttpServerExtension())->getXsdValidationBasePath()); + } + + public function testShouldExposeServiceNameResolverService() + { + $this->load(); + + $this->assertContainerBuilderHasService( + self::EXPECTED_SERVICE_NAME_RESOLVER_SERVICE_ID, + ServiceNameResolver::class + ); + + // Check that service is accessible through the container + $this->assertNotNull($this->container->get(self::EXPECTED_SERVICE_NAME_RESOLVER_SERVICE_ID)); + + $this->assertEndpointIsUsable(); + } + + public function testShouldAliasPSR11MethodResolverByDefault() + { + $this->load(); + + // Assert that MethodManager have the stub resolver + $this->assertContainerBuilderHasServiceDefinitionWithArgument( + self::EXPECTED_METHOD_MANAGER_SERVICE_ID, + 0, + new Reference(self::EXPECTED_METHOD_RESOLVER_STUB_SERVICE_ID) + ); + + // Assert PSR-11 resolver is an alias of the stub + $this->assertContainerBuilderHasAlias( + self::EXPECTED_METHOD_RESOLVER_STUB_SERVICE_ID, + 'json_rpc_http_server.psr11.infra.resolver.method' + ); + + $this->assertEndpointIsUsable(); + } + + public function testShouldAliasMethodResolverInjectionFoundByTag() + { + $myCustomResolverServiceId = 'my_custom_resolver'; + + $this->setDefinition($myCustomResolverServiceId, $this->createCustomMethodResolverDefinition()); + + $this->load(); + + // Assert that MethodManager have the stub resolver + $this->assertContainerBuilderHasServiceDefinitionWithArgument( + self::EXPECTED_METHOD_MANAGER_SERVICE_ID, + 0, + new Reference(self::EXPECTED_METHOD_RESOLVER_STUB_SERVICE_ID) + ); + + // Assert custom resolver is an alias of the stub + $this->assertContainerBuilderHasAlias( + self::EXPECTED_METHOD_RESOLVER_STUB_SERVICE_ID, + $myCustomResolverServiceId + ); + + $this->assertEndpointIsUsable(); + } + + public function testHandleManageJsonRpcMethodTag() + { + $jsonRpcMethodServiceId = uniqid(); + $jsonRpcMethodServiceId2 = uniqid(); + $methodName = 'my-method-name'; + $methodName2 = 'my-method-name-2'; + + // A first method + $methodService = $this->createJsonRpcMethodDefinition(); + $this->addJsonRpcMethodTag($methodService, $methodName); + $this->setDefinition($jsonRpcMethodServiceId, $methodService); + // A second method + $methodService2 = $this->createJsonRpcMethodDefinition(); + $this->addJsonRpcMethodTag($methodService2, $methodName2); + $this->setDefinition($jsonRpcMethodServiceId2, $methodService2); + + $this->load(); + + // Assert that method mapping have been correctly injected + $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( + self::EXPECTED_SERVICE_NAME_RESOLVER_SERVICE_ID, + 'addMethodMapping', + [ + $methodName, + $jsonRpcMethodServiceId + ], + 0 + ); + // Assert that method mapping have been correctly injected + $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( + self::EXPECTED_SERVICE_NAME_RESOLVER_SERVICE_ID, + 'addMethodMapping', + [ + $methodName2, + $jsonRpcMethodServiceId2 + ], + 1 + ); + + $this->assertEndpointIsUsable(); + } + + public function testHandleNotManageJsonRpcMethodTagIfCustomResolverIsUsed() + { + $jsonRpcMethodServiceId = uniqid(); + $jsonRpcMethodServiceId2 = uniqid(); + $methodName = 'my-method-name'; + $methodName2 = 'my-method-name-2'; + + // A first method + $methodService = $this->createJsonRpcMethodDefinition(); + $this->addJsonRpcMethodTag($methodService, $methodName); + $this->setDefinition($jsonRpcMethodServiceId, $methodService); + // A second method + $methodService2 = $this->createJsonRpcMethodDefinition(); + $this->addJsonRpcMethodTag($methodService2, $methodName2); + $this->setDefinition($jsonRpcMethodServiceId2, $methodService2); + + // Add the custom method resolver + $this->setDefinition(uniqid(), $this->createCustomMethodResolverDefinition()); + + $this->load(); + + // Assert that no method mapping have been added + $this->assertEmpty( + $this->container->getDefinition(self::EXPECTED_SERVICE_NAME_RESOLVER_SERVICE_ID)->getMethodCalls() + ); + + $this->assertEndpointIsUsable(); + } + + public function testShouldThrowAnExceptionIfJsonRpcMethodUsedWithTagIsDoesNotHaveTheMethodTagAttribute() + { + $jsonRpcMethodServiceId = uniqid(); + $jsonRpcMethodServiceId2 = uniqid(); + $methodName = 'my-method-name'; + + // A first method + $methodService = $this->createJsonRpcMethodDefinition(); + $this->addJsonRpcMethodTag($methodService, $methodName); + $this->setDefinition($jsonRpcMethodServiceId, $methodService); + // A second method with empty tag attribute + $methodService2 = $this->createJsonRpcMethodDefinition(); + $methodService2->addTag(self::EXPECTED_JSONRPC_METHOD_TAG); + $this->setDefinition($jsonRpcMethodServiceId2, $methodService2); + + $this->expectException(LogicException::class); + // Check that exception is for the second method + $this->expectExceptionMessage( + sprintf( + 'Service "%s" is taggued as JSON-RPC method but does not have' + . ' method name defined under "%s" tag attribute key', + $jsonRpcMethodServiceId2, + self::EXPECTED_JSONRPC_METHOD_TAG_METHOD_NAME_KEY + ) + ); + + $this->load(); + } + + public function testShouldThrowAnExceptionIfJsonRpcMethodUsedWithTagIsNotPublic() + { + $jsonRpcMethodServiceId = uniqid(); + $jsonRpcMethodServiceId2 = uniqid(); + $methodName = 'my-method-name'; + $methodName2 = 'my-method-name-2'; + + // A first method + $methodService = $this->createJsonRpcMethodDefinition(); + $this->addJsonRpcMethodTag($methodService, $methodName); + $this->setDefinition($jsonRpcMethodServiceId, $methodService); + // A second method with private service + $methodService2 = $this->createJsonRpcMethodDefinition()->setPublic(false); + $this->addJsonRpcMethodTag($methodService2, $methodName2); + $this->setDefinition($jsonRpcMethodServiceId2, $methodService2); + + $this->expectException(LogicException::class); + // Check that exception is for the second method + $this->expectExceptionMessage( + sprintf( + 'Service "%s" is taggued as JSON-RPC method but is not public. ' + .'Service must be public in order to retrieve it later', + $jsonRpcMethodServiceId2 + ) + ); + + $this->load(); + } +} diff --git a/tests/Functional/DependencyInjection/JsonRpcHttpServerExtensionWithConfigParsedTest.php b/tests/Functional/DependencyInjection/JsonRpcHttpServerExtensionWithConfigParsedTest.php new file mode 100644 index 0000000..af608b1 --- /dev/null +++ b/tests/Functional/DependencyInjection/JsonRpcHttpServerExtensionWithConfigParsedTest.php @@ -0,0 +1,146 @@ +load(['http_endpoint_path' => $myCustomEndpoint]); + + // Assert custom resolver is an alias of the stub + $this->assertContainerBuilderHasParameter(self::EXPECTED_HTTP_ENDPOINT_PATH_CONTAINER_PARAM, $myCustomEndpoint); + + $this->assertEndpointIsUsable(); + } + + public function testShouldManageCustomResolverFromConfiguration() + { + $myCustomResolverServiceId = 'my-custom-resolver'; + $this->setDefinition($myCustomResolverServiceId, $this->createCustomMethodResolverDefinition()); + + $this->load(['method_resolver' => $myCustomResolverServiceId]); + + // Assert custom resolver is an alias of the stub + $this->assertContainerBuilderHasAlias( + self::EXPECTED_METHOD_RESOLVER_STUB_SERVICE_ID, + $myCustomResolverServiceId + ); + + $this->assertEndpointIsUsable(); + } + + public function testShouldManageMethodsMapping() + { + $serviceA = uniqid(); + $serviceB = uniqid(); + $serviceC = uniqid(); + $methodAName = 'method-a'; + $methodAAlias1 = 'method-a-alias-1'; + $methodAAlias2 = 'method-a-alias-2'; + $methodBName = 'method-b'; + $methodBAlias = 'method-b-alias'; + $methodCName = 'method-c'; + $this->setDefinition($serviceA, $this->createJsonRpcMethodDefinition()); + $this->setDefinition($serviceB, $this->createJsonRpcMethodDefinition()); + $this->setDefinition($serviceC, $this->createJsonRpcMethodDefinition()); + + $this->load([ + 'methods_mapping' => [ + $methodAName => [ + 'service' => $serviceA, + 'aliases' => [$methodAAlias1, $methodAAlias2] + ], + $methodBName => [ + 'service' => $serviceB, + 'aliases' => $methodBAlias, + ], + $methodCName => $serviceC + ] + ]); + + // Assert each methods have been mapped correctly + + // MethodA and aliases + $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( + self::EXPECTED_SERVICE_NAME_RESOLVER_SERVICE_ID, + 'addMethodMapping', + [$methodAName, $serviceA] + ); + $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( + self::EXPECTED_SERVICE_NAME_RESOLVER_SERVICE_ID, + 'addMethodMapping', + [$methodAAlias1, $serviceA] + ); + $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( + self::EXPECTED_SERVICE_NAME_RESOLVER_SERVICE_ID, + 'addMethodMapping', + [$methodAAlias2, $serviceA] + ); + // MethodB and alias + $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( + self::EXPECTED_SERVICE_NAME_RESOLVER_SERVICE_ID, + 'addMethodMapping', + [$methodBName, $serviceB] + ); + $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( + self::EXPECTED_SERVICE_NAME_RESOLVER_SERVICE_ID, + 'addMethodMapping', + [$methodBAlias, $serviceB] + ); + // MethodC + $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( + self::EXPECTED_SERVICE_NAME_RESOLVER_SERVICE_ID, + 'addMethodMapping', + [$methodCName, $serviceC] + ); + + $this->assertJsonRpcMethodServiceIsAvailable($serviceA); + $this->assertJsonRpcMethodServiceIsAvailable($serviceB); + $this->assertJsonRpcMethodServiceIsAvailable($serviceC); + + $this->assertEndpointIsUsable(); + } + + public function testShouldThrowAnExceptionIfJsonRpcMethodUsedWithTagIsNotPublic() + { + $jsonRpcMethodServiceId = uniqid(); + + $methodService = $this->createJsonRpcMethodDefinition()->setPublic(false); + $this->setDefinition($jsonRpcMethodServiceId, $methodService); + + + $this->expectException(LogicException::class); + // Check that exception is for the second method + $this->expectExceptionMessage( + sprintf( + 'Service "%s" is taggued as JSON-RPC method but is not public. ' + .'Service must be public in order to retrieve it later', + $jsonRpcMethodServiceId + ) + ); + + $this->load([ + 'methods_mapping' => [ + 'a-method' => $jsonRpcMethodServiceId + ] + ]); + } +} diff --git a/tests/Functional/Endpoint/JsonRpcHttpEndpointTest.php b/tests/Functional/Endpoint/JsonRpcHttpEndpointTest.php new file mode 100644 index 0000000..befdbca --- /dev/null +++ b/tests/Functional/Endpoint/JsonRpcHttpEndpointTest.php @@ -0,0 +1,72 @@ +sdkEndpoint = $this->prophesize(SdkJsonRpcEndpoint::class); + + $this->endpoint = new JsonRpcHttpEndpoint( + $this->sdkEndpoint->reveal() + ); + } + + public function testShouldHandleRequestContentAndReturnA200ResponseContainingSDKEndpointReturnedValue() + { + $requestContent = 'request-content'; + $expextedResponseContent = 'expected-response-content'; + + /** @var Request|ObjectProphecy $request */ + $request = $this->prophesize(Request::class); + + $request->getMethod() + ->willReturn(Request::METHOD_POST) + ->shouldBeCalled(); + + $request->getContent() + ->willReturn($requestContent) + ->shouldBeCalled(); + + $this->sdkEndpoint->index($requestContent) + ->willReturn($expextedResponseContent) + ->shouldBeCalled(); + + $response = $this->endpoint->index($request->reveal()); + + $this->assertSame(Response::HTTP_OK, $response->getStatusCode()); + $this->assertSame($expextedResponseContent, $response->getContent()); + } + + public function testShouldCheckIfRequestUsePostMethodAndReturnErrorResponseIfNot() + { + /** @var Request|ObjectProphecy $request */ + $request = $this->prophesize(Request::class); + + $request->getMethod() + ->willReturn(Request::METHOD_GET) + ->shouldBeCalled(); + + $response = $this->endpoint->index($request->reveal()); + + $this->assertSame(Response::HTTP_METHOD_NOT_ALLOWED, $response->getStatusCode()); + $this->assertSame('A JSON-RPC HTTP call must use POST', $response->getContent()); + } +} diff --git a/tests/Functional/JsonRpcHttpServerBundleTest.php b/tests/Functional/JsonRpcHttpServerBundleTest.php new file mode 100644 index 0000000..007793e --- /dev/null +++ b/tests/Functional/JsonRpcHttpServerBundleTest.php @@ -0,0 +1,37 @@ +getContainerExtension() + ]; + } + + public function testShouldManageConfigurationByDefault() + { + $myCustomResolverServiceId = 'my-custom-resolver'; + $this->setDefinition($myCustomResolverServiceId, $this->createCustomMethodResolverDefinition()); + + $this->load(['method_resolver' => $myCustomResolverServiceId]); + + // Assert custom resolver is an alias of the stub + $this->assertContainerBuilderHasAlias( + self::EXPECTED_METHOD_RESOLVER_STUB_SERVICE_ID, + $myCustomResolverServiceId + ); + + $this->assertEndpointIsUsable(); + } +} diff --git a/tests/Functional/Resolver/ServiceNameResolverTest.php b/tests/Functional/Resolver/ServiceNameResolverTest.php new file mode 100644 index 0000000..ddc77f3 --- /dev/null +++ b/tests/Functional/Resolver/ServiceNameResolverTest.php @@ -0,0 +1,37 @@ +resolver = new ServiceNameResolver(); + } + + public function testShouldSaveMappingAndGiveItBack() + { + $this->resolver->addMethodMapping('a', 'b'); + $this->resolver->addMethodMapping('c', 'D'); + $this->resolver->addMethodMapping('e', 'blabla'); + + $this->assertSame('b', $this->resolver->resolve('a')); + $this->assertSame('D', $this->resolver->resolve('c')); + $this->assertSame('blabla', $this->resolver->resolve('e')); + } + + public function testShouldReturnOriginalValueInCaseNoMappingHaveBeenDefinedForAMethod() + { + $this->resolver->addMethodMapping('a', 'b'); + + $this->assertSame('abcde', $this->resolver->resolve('abcde')); + } +} diff --git a/tests/Technical/.gitkeep b/tests/Technical/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/Technical/DependencyInjection/ConfigurationTest.php b/tests/Technical/DependencyInjection/ConfigurationTest.php new file mode 100644 index 0000000..2028ba4 --- /dev/null +++ b/tests/Technical/DependencyInjection/ConfigurationTest.php @@ -0,0 +1,125 @@ +assertProcessedConfigurationEquals([[]], ['method_resolver'=> false], 'method_resolver'); + } + + public function testShouldReturnCustomMethodResolverServiceIfDefined() + { + $expectedCustomMethodResolverService = 'my-service'; + + $this->assertProcessedConfigurationEquals( + [ + [ + 'method_resolver' => $expectedCustomMethodResolverService + ] + ], + [ + 'method_resolver'=> $expectedCustomMethodResolverService + ], + 'method_resolver' + ); + } + + public function testShouldManageSimpleMethodMapping() + { + $expectedMethodName = 'my-method-name'; + $expectedService = 'my-service-id'; + + $this->assertProcessedConfigurationEquals( + [ + [ + 'methods_mapping' => [ + $expectedMethodName => $expectedService + ] + ] + ], + [ + 'methods_mapping'=> [ + $expectedMethodName => [ + 'service' => $expectedService, + 'aliases' => [] + ] + ] + ], + 'methods_mapping' + ); + } + + public function testShouldManageFullyConfiguredMapping() + { + $expectedMethodName = 'my-method-name'; + $expectedAlias1 = 'my-method-name-alias-1'; + $expectedAlias2 = 'my-method-name-alias-2'; + $expectedService = 'my-service-id'; + + $this->assertProcessedConfigurationEquals( + [ + [ + 'methods_mapping' => [ + $expectedMethodName => [ + 'service' => $expectedService, + 'aliases' => [$expectedAlias1, $expectedAlias2] + ] + ] + ] + ], + [ + 'methods_mapping'=> [ + $expectedMethodName => [ + 'service' => $expectedService, + 'aliases' => [$expectedAlias1, $expectedAlias2] + ] + ] + ], + 'methods_mapping' + ); + } + + public function testShouldManageASimpleAliasDefinition() + { + $expectedMethodName = 'my-method-name'; + $expectedAlias = 'my-method-name-alias'; + $expectedService = 'my-service-id'; + + $this->assertProcessedConfigurationEquals( + [ + [ + 'methods_mapping' => [ + $expectedMethodName => [ + 'service' => $expectedService, + 'aliases' => $expectedAlias, + ] + ] + ] + ], + [ + 'methods_mapping'=> [ + $expectedMethodName => [ + 'service' => $expectedService, + 'aliases' => [$expectedAlias] + ] + ] + ], + 'methods_mapping' + ); + } +} diff --git a/tests/Technical/DependencyInjection/JsonRpcHttpServerExtensionTest.php b/tests/Technical/DependencyInjection/JsonRpcHttpServerExtensionTest.php new file mode 100644 index 0000000..7b78e5e --- /dev/null +++ b/tests/Technical/DependencyInjection/JsonRpcHttpServerExtensionTest.php @@ -0,0 +1,31 @@ +setDefinition($serviceId1, $this->createCustomMethodResolverDefinition()); + $this->setDefinition($serviceId2, $this->createCustomMethodResolverDefinition()); + + $this->expectException(LogicException::class); + // Check that exception is for the second method + $this->expectExceptionMessage( + sprintf( + 'Only one method resolver could be defined, found following services : %s', + implode(', ', [$serviceId1, $serviceId2]) + ) + ); + + $this->load(); + } +} diff --git a/tests/Technical/DependencyInjection/JsonRpcHttpServerExtensionWithConfigurationParsedTest.php b/tests/Technical/DependencyInjection/JsonRpcHttpServerExtensionWithConfigurationParsedTest.php new file mode 100644 index 0000000..c5c661e --- /dev/null +++ b/tests/Technical/DependencyInjection/JsonRpcHttpServerExtensionWithConfigurationParsedTest.php @@ -0,0 +1,64 @@ +load(); + + // Assert custom resolver is an alias of the stub + $this->assertContainerBuilderHasParameter( + self::EXPECTED_HTTP_ENDPOINT_PATH_CONTAINER_PARAM, + JsonRpcHttpServerExtension::HTTP_ENDPOINT_PATH + ); + + $this->assertEndpointIsUsable(); + } + + public function testShouldNormalizeExternalServiceIdStringPassedForMethodResolver() + { + $myCustomResolverServiceId = 'my-custom-resolver'; + $this->setDefinition($myCustomResolverServiceId, $this->createCustomMethodResolverDefinition()); + + $this->load(['method_resolver' => '@'.$myCustomResolverServiceId]); + + $this->assertEndpointIsUsable(); + } + + public function testShouldNormalizeExternalServiceIdStringPassedForMethodMapping() + { + $jsonRpcMethodServiceId = uniqid(); + + $methodService = $this->createJsonRpcMethodDefinition(); + $this->setDefinition($jsonRpcMethodServiceId, $methodService); + + + $this->load([ + 'methods_mapping' => [ + 'a-method' => '@'.$jsonRpcMethodServiceId + ] + ]); + + $this->assertJsonRpcMethodServiceIsAvailable($jsonRpcMethodServiceId); + + $this->assertEndpointIsUsable(); + } +}