From 7f6e791fd51ea4fc8866fc322e81d1028e99ca88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Klatt?= Date: Tue, 16 Jul 2024 10:24:54 +0200 Subject: [PATCH] ACP-2626 Added configurationValidation (#12) * ACP-2626 Added configurationValidation --------- Co-authored-by: Stanislav Matveyev --- .gitignore | 1 + composer.json | 155 ++++++------ config/Shared/config_default.php | 48 +++- rector.php | 2 + .../Propel/Schema/spy_app_kernel.schema.xml | 21 -- src/Orm/Propel/Schema/spy_locale.schema.xml | 48 ---- src/Orm/Propel/Schema/spy_queue.schema.xml | 23 -- src/Orm/Propel/Schema/spy_store.schema.xml | 15 -- .../AppKernel/AppKernelDependencyProvider.php | 74 ++---- .../Glue/AppKernel/AppKernelFactory.php | 23 +- .../AppKernel/Builder/ResponseBuilder.php | 10 +- .../AppKernelToAppKernelFacadeBridge.php | 8 + .../AppKernelToAppKernelFacadeInterface.php | 6 + .../AppKernelToUtilEncodingServiceBridge.php | 47 ++++ ...ppKernelToUtilEncodingServiceInterface.php | 28 +++ .../AppKernel/Mapper/GlueRequestMapper.php | 48 +++- .../Mapper/GlueRequestMapperInterface.php | 11 + .../BodyStructureValidatorPlugin.php | 30 --- .../HeaderValidatorPlugin.php | 30 --- .../AppKernelRouteProviderPlugin.php | 3 + .../Validator/BodyStructureValidator.php | 6 +- .../Validator/ConfigurationValidator.php | 35 +++ .../AppKernel/Validator/RequestValidator.php | 9 +- .../Transfer/app_kernel.transfer.xml | 22 ++ .../AppKernel/AppKernelDependencyProvider.php | 34 +++ .../Business/AppKernelBusinessFactory.php | 22 +- .../AppKernel/Business/AppKernelFacade.php | 23 +- .../Business/AppKernelFacadeInterface.php | 20 ++ .../Configuration/ConfigurationValidator.php | 79 ++++++ .../Business/Writer/ConfigWriter.php | 4 +- .../Controller/AppConfigureControllerTest.php | 231 ++++++++++++++++-- .../Glue/AppKernel/codeception.yml | 5 + .../Helper/AppKernelGlueBackendApiHelper.php | 145 +++++++++++ .../Bootstrap/GlueBackendApiBootstrap.php | 23 ++ .../Helper/AppKernelAssertionHelper.php | 26 ++ 35 files changed, 981 insertions(+), 334 deletions(-) delete mode 100644 src/Orm/Propel/Schema/spy_app_kernel.schema.xml delete mode 100644 src/Orm/Propel/Schema/spy_locale.schema.xml delete mode 100644 src/Orm/Propel/Schema/spy_queue.schema.xml delete mode 100644 src/Orm/Propel/Schema/spy_store.schema.xml create mode 100644 src/Spryker/Glue/AppKernel/Dependency/Service/AppKernelToUtilEncodingServiceBridge.php create mode 100644 src/Spryker/Glue/AppKernel/Dependency/Service/AppKernelToUtilEncodingServiceInterface.php delete mode 100644 src/Spryker/Glue/AppKernel/Plugin/RequestValidator/BodyStructureValidatorPlugin.php delete mode 100644 src/Spryker/Glue/AppKernel/Plugin/RequestValidator/HeaderValidatorPlugin.php create mode 100644 src/Spryker/Glue/AppKernel/Validator/ConfigurationValidator.php create mode 100644 src/Spryker/Zed/AppKernel/Business/Configuration/ConfigurationValidator.php create mode 100644 tests/SprykerTest/Glue/Testify/_support/Helper/AppKernelGlueBackendApiHelper.php create mode 100644 tests/SprykerTest/Glue/Testify/_support/Helper/GlueApplication/Bootstrap/GlueBackendApiBootstrap.php diff --git a/.gitignore b/.gitignore index 503db3b..1de4327 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ composer.lock # built resources src/Generated src/Orm/Zed/ +src/Orm/Propel/Schema/ src/Orm/Propel/Migration_SQLite/ tests/_data/app_kernel_db diff --git a/composer.json b/composer.json index 2d10934..7545bc5 100644 --- a/composer.json +++ b/composer.json @@ -1,76 +1,85 @@ { - "name": "spryker/app-kernel", - "type": "library", - "description": "AppKernel module", - "license": "proprietary", - "require": { - "php": ">=8.1", - "spryker/app-kernel-extension": "dev-master", - "spryker/glue-application-extension": "^1.0.0", - "spryker/kernel": "^3.30.0", - "spryker/log": "^3.0.0", - "spryker/message-broker": "^1.11.0", - "spryker/propel-encryption-behavior": "^0.1.1", - "spryker/secrets-manager": "^1.0.0", - "spryker/secrets-manager-extension": "^1.0.0", - "spryker/symfony": "^3.0.0", - "spryker/transfer": "^3.33.0", - "spryker/util-encoding": "^2.0.0", - "spryker/util-text": "^1.0.0" - }, - "require-dev": { - "codeception/codeception": "^5.0", - "codeception/module-asserts": "^3.0", - "phpstan/phpdoc-parser": "1.25.0", - "phpstan/phpstan": "1.10.66", - "rector/rector": "^0.19.0", - "spryker/code-sniffer": "*", - "spryker/container": "*", - "spryker/development": "^3.34.0", - "spryker/message-broker-aws": "^1.7.0", - "spryker/propel": "*", - "spryker/testify": "*", - "spryker/testify-async-api": "^0.1.4" - }, - "autoload": { - "psr-4": { - "Spryker\\": "src/Spryker/", - "SprykerTest\\Glue\\AppKernel\\Helper\\": "tests/SprykerTest/Glue/AppKernel/_support/Helper/", - "SprykerTest\\Zed\\AppKernel\\Helper\\": "tests/SprykerTest/Zed/AppKernel/_support/Helper/", - "SprykerTest\\Shared\\AppKernel\\Helper\\": "tests/SprykerTest/Shared/AppKernel/_support/Helper/", - "SprykerTest\\Zed\\Propel\\Helper\\": "tests/SprykerTest/Zed/Propel/_support/Helper/" - } - }, - "autoload-dev": { - "psr-4": { - "SprykerTest\\": "tests/SprykerTest/", - "Generated\\": "src/Generated/", - "Orm\\": "src/Orm/" - } - }, - "minimum-stability": "dev", - "prefer-stable": true, - "scripts": { - "setup": "tests/bin/console app-kernel:setup && tests/bin/console transfer:generate && tests/bin/console transfer:databuilder:generate && tests/bin/console propel:install && tests/bin/console dev:ide-auto-completion:zed:generate && tests/bin/console dev:ide-auto-completion:glue:generate && tests/bin/console dev:ide-auto-completion:glue-backend:generate", - "cs-check": "phpcs -p src/ tests/", - "cs-fix": "phpcbf -p src/ tests/", - "stan": "phpstan analyze src/Spryker/", - "test": "codecept build && codecept run", - "test-cover": "codecept build && codecept run --coverage-xml", - "test-cover-html": "codecept build && codecept run --coverage-html", - "rector": "vendor/bin/rector process src/Spryker/ --config rector.php --ansi", - "rector-ci": "vendor/bin/rector process src/Spryker/ --config rector.php --ansi --dry-run", - "local-ci": "composer cs-fix && composer cs-check && composer stan && composer rector-ci && composer test" - }, - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "config": { - "sort-packages": true, - "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": true - } + "name": "spryker/app-kernel", + "type": "library", + "description": "AppKernel module", + "license": "proprietary", + "require": { + "php": ">=8.1", + "spryker/app-kernel-extension": "dev-master", + "spryker/glue-application-extension": "^1.0.0", + "spryker/kernel": "^3.30.0", + "spryker/log": "^3.0.0", + "spryker/message-broker": "^1.11.0", + "spryker/propel-encryption-behavior": "^0.1.1", + "spryker/secrets-manager": "^1.0.0", + "spryker/secrets-manager-extension": "^1.0.0", + "spryker/symfony": "^3.0.0", + "spryker/transfer": "^3.33.0", + "spryker/util-encoding": "^2.0.0", + "spryker/util-text": "^1.0.0" + }, + "require-dev": { + "codeception/codeception": "^5.0", + "codeception/module-cli": "^2.0.0", + "codeception/module-filesystem": "^3.0.0", + "codeception/module-phpbrowser": "^3.0.0", + "codeception/module-rest": "^3.0.0", + "codeception/module-webdriver": "^3.0.0", + "phpstan/phpdoc-parser": "1.25.0", + "phpstan/phpstan": "1.10.66", + "rector/rector": "^0.19.0", + "spryker/code-sniffer": "*", + "spryker/container": "*", + "spryker/development": "^3.34.0", + "spryker/glue-application": "^1.64.0", + "spryker/glue-backend-api-application": "^1.6.0", + "spryker/glue-json-api-convention": "^1.3.0", + "spryker/http": "^1.11.0", + "spryker/message-broker-aws": "^1.7.0", + "spryker/propel": "*", + "spryker/testify": "*", + "spryker/testify-async-api": "^0.1.4" + }, + "autoload": { + "psr-4": { + "Spryker\\": "src/Spryker/", + "SprykerTest\\Glue\\AppKernel\\Helper\\": "tests/SprykerTest/Glue/AppKernel/_support/Helper/", + "SprykerTest\\Glue\\Testify\\Helper\\": "tests/SprykerTest/Glue/Testify/_support/Helper/", + "SprykerTest\\Zed\\AppKernel\\Helper\\": "tests/SprykerTest/Zed/AppKernel/_support/Helper/", + "SprykerTest\\Shared\\AppKernel\\Helper\\": "tests/SprykerTest/Shared/AppKernel/_support/Helper/", + "SprykerTest\\Zed\\Propel\\Helper\\": "tests/SprykerTest/Zed/Propel/_support/Helper/" } + }, + "autoload-dev": { + "psr-4": { + "SprykerTest\\": "tests/SprykerTest/", + "Generated\\": "src/Generated/", + "Orm\\": "src/Orm/" + } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "scripts": { + "setup": "tests/bin/console app-kernel:setup && tests/bin/console transfer:generate && tests/bin/console transfer:databuilder:generate && tests/bin/console propel:install && tests/bin/console dev:ide-auto-completion:zed:generate && tests/bin/console dev:ide-auto-completion:glue:generate && tests/bin/console dev:ide-auto-completion:glue-backend:generate", + "cs-check": "phpcs -p src/ tests/", + "cs-fix": "phpcbf -p src/ tests/", + "stan": "phpstan analyze src/Spryker/", + "test": "codecept build && codecept run", + "test-cover": "codecept build && codecept run --coverage-xml", + "test-cover-html": "codecept build && codecept run --coverage-html", + "rector": "vendor/bin/rector process src/Spryker/ --config rector.php --ansi", + "rector-ci": "vendor/bin/rector process src/Spryker/ --config rector.php --ansi --dry-run", + "local-ci": "composer cs-fix && composer cs-check && composer stan && composer rector-ci && composer test" + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "config": { + "sort-packages": true, + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + } } diff --git a/config/Shared/config_default.php b/config/Shared/config_default.php index 12e91a5..157a6d6 100644 --- a/config/Shared/config_default.php +++ b/config/Shared/config_default.php @@ -7,15 +7,55 @@ use Generated\Shared\Transfer\AppConfigUpdatedTransfer; use Ramsey\Uuid\Uuid; use Spryker\Shared\AppKernel\AppKernelConstants; +use Spryker\Shared\Application\ApplicationConstants; +use Spryker\Shared\GlueBackendApiApplication\GlueBackendApiApplicationConstants; +use Spryker\Shared\GlueJsonApiConvention\GlueJsonApiConventionConstants; +use Spryker\Shared\Http\HttpConstants; +use Spryker\Shared\Kernel\KernelConstants; use Spryker\Shared\MessageBroker\MessageBrokerConstants; +use Spryker\Shared\ZedRequest\ZedRequestConstants; use Spryker\Zed\MessageBrokerAws\MessageBrokerAwsConfig; +use Spryker\Shared\MessageBrokerAws\MessageBrokerAwsConstants; -$config[MessageBrokerConstants::MESSAGE_TO_CHANNEL_MAP] = [ +$config[AppKernelConstants::APP_IDENTIFIER] = Uuid::uuid4()->toString(); + +// ---------------------------------------------------------------------------- +// ------------------------------ Glue Backend API ---------------------------- +// ---------------------------------------------------------------------------- +$config[GlueBackendApiApplicationConstants::GLUE_BACKEND_API_HOST] = 'api.payment.local'; + +$config[KernelConstants::ENABLE_CONTAINER_OVERRIDING] = true; +$config[KernelConstants::PROJECT_NAMESPACES] = +$config[GlueBackendApiApplicationConstants::PROJECT_NAMESPACES] = [ + 'Spryker', +]; +$config[ZedRequestConstants::ZED_API_SSL_ENABLED] = (bool)getenv('SPRYKER_ZED_SSL_ENABLED'); + +$config[ApplicationConstants::BASE_URL_ZED] = sprintf( + 'https://%s', + 'api.kernel.local', +); + +$config[AppKernelConstants::APP_IDENTIFIER] = Uuid::uuid4()->toString(); + +$config[HttpConstants::URI_SIGNER_SECRET_KEY] = Uuid::uuid4()->toString(); + +$config[GlueJsonApiConventionConstants::GLUE_DOMAIN] = sprintf( + '%s://%s', + getenv('SPRYKER_SSL_ENABLE') ? 'https' : 'http', + $config[GlueBackendApiApplicationConstants::GLUE_BACKEND_API_HOST] ?: 'localhost', +); + +$config[MessageBrokerConstants::MESSAGE_TO_CHANNEL_MAP] = +$config[MessageBrokerAwsConstants::MESSAGE_TO_CHANNEL_MAP] = [ + // App event AppConfigUpdatedTransfer::class => 'app-events', ]; -$config[MessageBrokerConstants::CHANNEL_TO_SENDER_TRANSPORT_MAP] = [ - 'app-events' => MessageBrokerAwsConfig::HTTP_CHANNEL_TRANSPORT, +$config[MessageBrokerConstants::CHANNEL_TO_TRANSPORT_MAP] = [ + 'app-events' => MessageBrokerAwsConfig::HTTP_TRANSPORT, ]; -$config[AppKernelConstants::APP_IDENTIFIER] = Uuid::uuid4()->toString(); +$config[MessageBrokerAwsConstants::CHANNEL_TO_SENDER_TRANSPORT_MAP] = [ + 'app-events' => MessageBrokerAwsConfig::HTTP_TRANSPORT, +]; diff --git a/rector.php b/rector.php index dc8f23f..dd58318 100644 --- a/rector.php +++ b/rector.php @@ -46,6 +46,7 @@ __DIR__ . '/src/Spryker/Zed/AppKernel/Dependency/Service/AppKernelToUtilEncodingServiceBridge.php', __DIR__ . '/src/Spryker/Zed/AppKernel/Dependency/Service/AppKernelToUtilTextServiceBridge.php', __DIR__ . '/src/Spryker/Glue/AppKernel/Dependency/Facade/AppKernelToAppKernelFacadeBridge.php', + __DIR__ . '/src/Spryker/Glue/AppKernel/Dependency/Service/AppKernelToUtilEncodingServiceBridge.php', ], AddParamTypeFromPropertyTypeRector::class => [ __DIR__ . '/src/Spryker/Zed/AppKernel/Dependency/Client/AppKernelToSecretsManagerClientBridge.php', @@ -53,6 +54,7 @@ __DIR__ . '/src/Spryker/Zed/AppKernel/Dependency/Service/AppKernelToUtilEncodingServiceBridge.php', __DIR__ . '/src/Spryker/Zed/AppKernel/Dependency/Service/AppKernelToUtilTextServiceBridge.php', __DIR__ . '/src/Spryker/Glue/AppKernel/Dependency/Facade/AppKernelToAppKernelFacadeBridge.php', + __DIR__ . '/src/Spryker/Glue/AppKernel/Dependency/Service/AppKernelToUtilEncodingServiceBridge.php', ], ]); }; diff --git a/src/Orm/Propel/Schema/spy_app_kernel.schema.xml b/src/Orm/Propel/Schema/spy_app_kernel.schema.xml deleted file mode 100644 index 8db2333..0000000 --- a/src/Orm/Propel/Schema/spy_app_kernel.schema.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - -
- -
diff --git a/src/Orm/Propel/Schema/spy_locale.schema.xml b/src/Orm/Propel/Schema/spy_locale.schema.xml deleted file mode 100644 index b0e2c60..0000000 --- a/src/Orm/Propel/Schema/spy_locale.schema.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
diff --git a/src/Orm/Propel/Schema/spy_queue.schema.xml b/src/Orm/Propel/Schema/spy_queue.schema.xml deleted file mode 100644 index 35f2128..0000000 --- a/src/Orm/Propel/Schema/spy_queue.schema.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - -
- -
diff --git a/src/Orm/Propel/Schema/spy_store.schema.xml b/src/Orm/Propel/Schema/spy_store.schema.xml deleted file mode 100644 index edf4bc5..0000000 --- a/src/Orm/Propel/Schema/spy_store.schema.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - -
-
diff --git a/src/Spryker/Glue/AppKernel/AppKernelDependencyProvider.php b/src/Spryker/Glue/AppKernel/AppKernelDependencyProvider.php index c70d7bf..c161836 100644 --- a/src/Spryker/Glue/AppKernel/AppKernelDependencyProvider.php +++ b/src/Spryker/Glue/AppKernel/AppKernelDependencyProvider.php @@ -8,10 +8,11 @@ namespace Spryker\Glue\AppKernel; use Spryker\Glue\AppKernel\Dependency\Facade\AppKernelToAppKernelFacadeBridge; -use Spryker\Glue\AppKernel\Plugin\RequestValidator\BodyStructureValidatorPlugin; -use Spryker\Glue\AppKernel\Plugin\RequestValidator\HeaderValidatorPlugin; +use Spryker\Glue\AppKernel\Dependency\Facade\AppKernelToAppKernelFacadeInterface; +use Spryker\Glue\AppKernel\Dependency\Service\AppKernelToUtilEncodingServiceBridge; +use Spryker\Glue\AppKernel\Dependency\Service\AppKernelToUtilEncodingServiceInterface; use Spryker\Glue\Kernel\Backend\AbstractBundleDependencyProvider; -use Spryker\Glue\Kernel\Backend\Container as GlueBackendContainer; +use Spryker\Glue\Kernel\Backend\Container; /** * @method \Spryker\Glue\AppKernel\AppKernelConfig getConfig() @@ -38,54 +39,43 @@ class AppKernelDependencyProvider extends AbstractBundleDependencyProvider */ public const PLUGINS_REQUEST_DISCONNECT_VALIDATOR = 'PLUGINS_REQUEST_DISCONNECT_VALIDATOR'; - public function provideBackendDependencies(GlueBackendContainer $glueBackendContainer): GlueBackendContainer + public function provideBackendDependencies(Container $container): Container { - $glueBackendContainer = parent::provideBackendDependencies($glueBackendContainer); + $container = parent::provideBackendDependencies($container); - $glueBackendContainer = $this->addUtilEncodingService($glueBackendContainer); - $glueBackendContainer = $this->addAppKernelFacade($glueBackendContainer); - $glueBackendContainer = $this->addRequestConfigureValidatorPlugins($glueBackendContainer); - $glueBackendContainer = $this->addRequestDisconnectValidatorPlugins($glueBackendContainer); + $container = $this->addUtilEncodingService($container); + $container = $this->addAppKernelFacade($container); + $container = $this->addRequestConfigureValidatorPlugins($container); + $container = $this->addRequestDisconnectValidatorPlugins($container); - return $glueBackendContainer; + return $container; } - protected function addUtilEncodingService(GlueBackendContainer $glueBackendContainer): GlueBackendContainer + protected function addUtilEncodingService(Container $container): Container { - $glueBackendContainer->set(static::SERVICE_UTIL_ENCODING, static function (GlueBackendContainer $glueBackendContainer) { - return $glueBackendContainer->getLocator()->utilEncoding()->service(); + $container->set(static::SERVICE_UTIL_ENCODING, static function (Container $container): AppKernelToUtilEncodingServiceInterface { + return new AppKernelToUtilEncodingServiceBridge($container->getLocator()->utilEncoding()->service()); }); - return $glueBackendContainer; + return $container; } - protected function addAppKernelFacade(GlueBackendContainer $glueBackendContainer): GlueBackendContainer + protected function addAppKernelFacade(Container $container): Container { - $glueBackendContainer->set(static::FACADE_APP_KERNEL, static function (GlueBackendContainer $glueBackendContainer): AppKernelToAppKernelFacadeBridge { - return new AppKernelToAppKernelFacadeBridge($glueBackendContainer->getLocator()->appKernel()->facade()); + $container->set(static::FACADE_APP_KERNEL, static function (Container $container): AppKernelToAppKernelFacadeInterface { + return new AppKernelToAppKernelFacadeBridge($container->getLocator()->appKernel()->facade()); }); - return $glueBackendContainer; + return $container; } - protected function addRequestConfigureValidatorPlugins(GlueBackendContainer $glueBackendContainer): GlueBackendContainer + protected function addRequestConfigureValidatorPlugins(Container $container): Container { - $glueBackendContainer->set(static::PLUGINS_REQUEST_CONFIGURE_VALIDATOR, function (): array { - return array_merge($this->getDefaultRequestConfigureValidatorPlugins(), $this->getRequestConfigureValidatorPlugins()); + $container->set(static::PLUGINS_REQUEST_CONFIGURE_VALIDATOR, function (): array { + return $this->getRequestConfigureValidatorPlugins(); }); - return $glueBackendContainer; - } - - /** - * @return array<\Spryker\Glue\GlueApplicationExtension\Dependency\Plugin\RequestValidatorPluginInterface> - */ - private function getDefaultRequestConfigureValidatorPlugins(): array - { - return [ - new HeaderValidatorPlugin(), - new BodyStructureValidatorPlugin(), - ]; + return $container; } /** @@ -96,23 +86,13 @@ protected function getRequestConfigureValidatorPlugins(): array return []; } - protected function addRequestDisconnectValidatorPlugins(GlueBackendContainer $glueBackendContainer): GlueBackendContainer + protected function addRequestDisconnectValidatorPlugins(Container $container): Container { - $glueBackendContainer->set(static::PLUGINS_REQUEST_DISCONNECT_VALIDATOR, function (): array { - return array_merge($this->getDefaultRequestDisconnectValidatorPlugins(), $this->getRequestDisconnectValidatorPlugins()); + $container->set(static::PLUGINS_REQUEST_DISCONNECT_VALIDATOR, function (): array { + return $this->getRequestDisconnectValidatorPlugins(); }); - return $glueBackendContainer; - } - - /** - * @return array<\Spryker\Glue\GlueApplicationExtension\Dependency\Plugin\RequestValidatorPluginInterface> - */ - protected function getDefaultRequestDisconnectValidatorPlugins(): array - { - return [ - new HeaderValidatorPlugin(), - ]; + return $container; } /** diff --git a/src/Spryker/Glue/AppKernel/AppKernelFactory.php b/src/Spryker/Glue/AppKernel/AppKernelFactory.php index b60de16..aae56cc 100644 --- a/src/Spryker/Glue/AppKernel/AppKernelFactory.php +++ b/src/Spryker/Glue/AppKernel/AppKernelFactory.php @@ -10,14 +10,15 @@ use Spryker\Glue\AppKernel\Builder\ResponseBuilder; use Spryker\Glue\AppKernel\Builder\ResponseBuilderInterface; use Spryker\Glue\AppKernel\Dependency\Facade\AppKernelToAppKernelFacadeInterface; +use Spryker\Glue\AppKernel\Dependency\Service\AppKernelToUtilEncodingServiceInterface; use Spryker\Glue\AppKernel\Mapper\GlueRequestMapper; use Spryker\Glue\AppKernel\Mapper\GlueRequestMapperInterface; use Spryker\Glue\AppKernel\Validator\BodyStructureValidator; +use Spryker\Glue\AppKernel\Validator\ConfigurationValidator; use Spryker\Glue\AppKernel\Validator\HeaderValidator; use Spryker\Glue\AppKernel\Validator\RequestValidator; use Spryker\Glue\AppKernel\Validator\RequestValidatorInterface; use Spryker\Glue\Kernel\Backend\AbstractFactory; -use Spryker\Service\UtilEncoding\UtilEncodingServiceInterface; use Symfony\Component\Validator\Validation; use Symfony\Component\Validator\Validator\ValidatorInterface; @@ -36,6 +37,14 @@ public function createBodyStructureValidator(): RequestValidatorInterface ); } + public function createConfigurationValidator(): RequestValidatorInterface + { + return new ConfigurationValidator( + $this->createGlueRequestMapper(), + $this->getAppKernelFacade(), + ); + } + public function createResponseBuilder(): ResponseBuilderInterface { return new ResponseBuilder($this->getUtilEncodingService()); @@ -55,15 +64,21 @@ public function createGlueRequestMapper(): GlueRequestMapperInterface public function createApiRequestSaveConfigValidator(): RequestValidatorInterface { - return new RequestValidator($this->getApiRequestConfigureValidatorPlugins()); + return new RequestValidator($this->getApiRequestConfigureValidatorPlugins(), [ + $this->createBodyStructureValidator(), + $this->createHeaderValidator(), + $this->createConfigurationValidator(), + ]); } public function createApiRequestDisconnectValidator(): RequestValidatorInterface { - return new RequestValidator($this->getApiRequestDisconnectValidatorPlugins()); + return new RequestValidator($this->getApiRequestDisconnectValidatorPlugins(), [ + $this->createHeaderValidator(), + ]); } - public function getUtilEncodingService(): UtilEncodingServiceInterface + public function getUtilEncodingService(): AppKernelToUtilEncodingServiceInterface { return $this->getProvidedDependency(AppKernelDependencyProvider::SERVICE_UTIL_ENCODING); } diff --git a/src/Spryker/Glue/AppKernel/Builder/ResponseBuilder.php b/src/Spryker/Glue/AppKernel/Builder/ResponseBuilder.php index 2264de3..4c2a2a7 100644 --- a/src/Spryker/Glue/AppKernel/Builder/ResponseBuilder.php +++ b/src/Spryker/Glue/AppKernel/Builder/ResponseBuilder.php @@ -11,12 +11,12 @@ use Generated\Shared\Transfer\GlueErrorTransfer; use Generated\Shared\Transfer\GlueRequestValidationTransfer; use Generated\Shared\Transfer\GlueResponseTransfer; -use Spryker\Service\UtilEncoding\UtilEncodingServiceInterface; +use Spryker\Glue\AppKernel\Dependency\Service\AppKernelToUtilEncodingServiceInterface; use Symfony\Component\HttpFoundation\Response; class ResponseBuilder implements ResponseBuilderInterface { - public function __construct(protected UtilEncodingServiceInterface $utilEncodingService) + public function __construct(protected AppKernelToUtilEncodingServiceInterface $appKernelToUtilEncodingService) { } @@ -38,7 +38,7 @@ public function buildRequestNotValidResponse( } return $glueResponseTransfer - ->setContent($this->utilEncodingService->encodeJson(['errors' => $errors])) + ->setContent($this->appKernelToUtilEncodingService->encodeJson(['errors' => $errors])) ->setHttpStatus($glueRequestValidationTransfer->getStatus() ?? Response::HTTP_BAD_REQUEST); } @@ -47,7 +47,7 @@ public function buildErrorResponse(string $errorMessage): GlueResponseTransfer $errorData = $this->composeErrorArray($errorMessage); return (new GlueResponseTransfer()) - ->setContent($this->utilEncodingService + ->setContent($this->appKernelToUtilEncodingService ->encodeJson([ 'errors' => [ $errorData, @@ -71,7 +71,7 @@ public function buildSuccessfulResponse(?AppConfigTransfer $appConfigTransfer = ]; $glueResponseTransfer - ->setContent($this->utilEncodingService->encodeJson($content)) + ->setContent($this->appKernelToUtilEncodingService->encodeJson($content)) ->setHttpStatus(Response::HTTP_OK); } diff --git a/src/Spryker/Glue/AppKernel/Dependency/Facade/AppKernelToAppKernelFacadeBridge.php b/src/Spryker/Glue/AppKernel/Dependency/Facade/AppKernelToAppKernelFacadeBridge.php index e247048..fe65040 100644 --- a/src/Spryker/Glue/AppKernel/Dependency/Facade/AppKernelToAppKernelFacadeBridge.php +++ b/src/Spryker/Glue/AppKernel/Dependency/Facade/AppKernelToAppKernelFacadeBridge.php @@ -10,6 +10,8 @@ use Generated\Shared\Transfer\AppConfigCriteriaTransfer; use Generated\Shared\Transfer\AppConfigResponseTransfer; use Generated\Shared\Transfer\AppConfigTransfer; +use Generated\Shared\Transfer\ConfigurationValidationRequestTransfer; +use Generated\Shared\Transfer\ConfigurationValidationResponseTransfer; class AppKernelToAppKernelFacadeBridge implements AppKernelToAppKernelFacadeInterface { @@ -26,6 +28,12 @@ public function __construct($appKernelFacade) $this->appKernelFacade = $appKernelFacade; } + public function validateConfiguration( + ConfigurationValidationRequestTransfer $configurationValidationRequestTransfer + ): ConfigurationValidationResponseTransfer { + return $this->appKernelFacade->validateConfiguration($configurationValidationRequestTransfer); + } + public function getConfig(AppConfigCriteriaTransfer $appConfigCriteriaTransfer): AppConfigTransfer { return $this->appKernelFacade->getConfig($appConfigCriteriaTransfer); diff --git a/src/Spryker/Glue/AppKernel/Dependency/Facade/AppKernelToAppKernelFacadeInterface.php b/src/Spryker/Glue/AppKernel/Dependency/Facade/AppKernelToAppKernelFacadeInterface.php index 9e74feb..ba6e8f0 100644 --- a/src/Spryker/Glue/AppKernel/Dependency/Facade/AppKernelToAppKernelFacadeInterface.php +++ b/src/Spryker/Glue/AppKernel/Dependency/Facade/AppKernelToAppKernelFacadeInterface.php @@ -10,9 +10,15 @@ use Generated\Shared\Transfer\AppConfigCriteriaTransfer; use Generated\Shared\Transfer\AppConfigResponseTransfer; use Generated\Shared\Transfer\AppConfigTransfer; +use Generated\Shared\Transfer\ConfigurationValidationRequestTransfer; +use Generated\Shared\Transfer\ConfigurationValidationResponseTransfer; interface AppKernelToAppKernelFacadeInterface { + public function validateConfiguration( + ConfigurationValidationRequestTransfer $configurationValidationRequestTransfer + ): ConfigurationValidationResponseTransfer; + public function getConfig(AppConfigCriteriaTransfer $appConfigCriteriaTransfer): AppConfigTransfer; public function saveConfig(AppConfigTransfer $appConfigTransfer): AppConfigResponseTransfer; diff --git a/src/Spryker/Glue/AppKernel/Dependency/Service/AppKernelToUtilEncodingServiceBridge.php b/src/Spryker/Glue/AppKernel/Dependency/Service/AppKernelToUtilEncodingServiceBridge.php new file mode 100644 index 0000000..1c26462 --- /dev/null +++ b/src/Spryker/Glue/AppKernel/Dependency/Service/AppKernelToUtilEncodingServiceBridge.php @@ -0,0 +1,47 @@ +utilEncodingService = $utilEncodingService; + } + + /** + * @param string $jsonValue + * @param bool $assoc Deprecated: `false` is deprecated, always use `true` for array return. + * @param int|null $depth + * @param int|null $options + * + * @return object|array|null + */ + public function decodeJson($jsonValue, $assoc = false, $depth = null, $options = null): object|array|null + { + return $this->utilEncodingService->decodeJson($jsonValue, $assoc, $depth, $options); + } + + /** + * @param array $value + * @param int|null $options + * @param int|null $depth + */ + public function encodeJson($value, $options = null, $depth = null): string|null + { + return $this->utilEncodingService->encodeJson($value, $options, $depth); + } +} diff --git a/src/Spryker/Glue/AppKernel/Dependency/Service/AppKernelToUtilEncodingServiceInterface.php b/src/Spryker/Glue/AppKernel/Dependency/Service/AppKernelToUtilEncodingServiceInterface.php new file mode 100644 index 0000000..595ef2b --- /dev/null +++ b/src/Spryker/Glue/AppKernel/Dependency/Service/AppKernelToUtilEncodingServiceInterface.php @@ -0,0 +1,28 @@ +|null + */ + public function decodeJson($jsonValue, $assoc = false, $depth = null, $options = null): object|array|null; + + /** + * @param array $value + * @param int|null $options + * @param int|null $depth + */ + public function encodeJson($value, $options = null, $depth = null): string|null; +} diff --git a/src/Spryker/Glue/AppKernel/Mapper/GlueRequestMapper.php b/src/Spryker/Glue/AppKernel/Mapper/GlueRequestMapper.php index d3738a3..4c8ea5a 100644 --- a/src/Spryker/Glue/AppKernel/Mapper/GlueRequestMapper.php +++ b/src/Spryker/Glue/AppKernel/Mapper/GlueRequestMapper.php @@ -8,13 +8,18 @@ namespace Spryker\Glue\AppKernel\Mapper; use Generated\Shared\Transfer\AppConfigTransfer; +use Generated\Shared\Transfer\ConfigurationValidationRequestTransfer; +use Generated\Shared\Transfer\ConfigurationValidationResponseTransfer; +use Generated\Shared\Transfer\GlueErrorTransfer; use Generated\Shared\Transfer\GlueRequestTransfer; +use Generated\Shared\Transfer\GlueRequestValidationTransfer; use Spryker\Glue\AppKernel\AppKernelConfig; -use Spryker\Service\UtilEncoding\UtilEncodingServiceInterface; +use Spryker\Glue\AppKernel\Dependency\Service\AppKernelToUtilEncodingServiceInterface; +use Symfony\Component\HttpFoundation\Response; class GlueRequestMapper implements GlueRequestMapperInterface { - public function __construct(protected UtilEncodingServiceInterface $utilEncodingService) + public function __construct(protected AppKernelToUtilEncodingServiceInterface $appKernelToUtilEncodingService) { } @@ -28,12 +33,45 @@ public function mapGlueRequestTransferToAppConfigTransfer( $appConfigTransfer->setConfig($configuration) ->setTenantIdentifier($tenantIdentifier); + if ($glueRequestTransfer->getLocale() === null) { + $glueRequestTransfer->setLocale('en_US'); + } + + $appConfigTransfer->setLocale($glueRequestTransfer->getLocale()); + return $appConfigTransfer; } + public function mapGlueRequestTransferToConfigurationValidationRequestTransfer( + GlueRequestTransfer $glueRequestTransfer + ): ConfigurationValidationRequestTransfer { + $configurationValidationRequestTransfer = new ConfigurationValidationRequestTransfer(); + $configurationValidationRequestTransfer->setAppConfig($this->mapGlueRequestTransferToAppConfigTransfer($glueRequestTransfer, new AppConfigTransfer())); + + return $configurationValidationRequestTransfer; + } + + public function mapConfigurationValidationResponseTransferToGlueRequestValidationTransfer( + ConfigurationValidationResponseTransfer $configurationValidationResponseTransfer + ): GlueRequestValidationTransfer { + $glueRequestValidationTransfer = new GlueRequestValidationTransfer(); + $glueRequestValidationTransfer->setIsValid($configurationValidationResponseTransfer->getIsSuccessful()) + ->setStatus($configurationValidationResponseTransfer->getIsSuccessful() === false ? Response::HTTP_UNPROCESSABLE_ENTITY : null); + + if ($configurationValidationResponseTransfer->getIsSuccessful() === false) { + $glueErrorTransfer = new GlueErrorTransfer(); + $glueErrorTransfer->setMessage($configurationValidationResponseTransfer->getMessage() ?? $configurationValidationResponseTransfer->getExceptionMessage()); + + $glueRequestValidationTransfer->addError($glueErrorTransfer); + $glueRequestValidationTransfer->setStatus($configurationValidationResponseTransfer->getExceptionMessage() !== null && $configurationValidationResponseTransfer->getExceptionMessage() !== '' && $configurationValidationResponseTransfer->getExceptionMessage() !== '0' ? Response::HTTP_INTERNAL_SERVER_ERROR : Response::HTTP_UNPROCESSABLE_ENTITY); + } + + return $glueRequestValidationTransfer; + } + protected function getTenantIdentifier(GlueRequestTransfer $glueRequestTransfer): string { - return $glueRequestTransfer->getMeta()[AppKernelConfig::HEADER_TENANT_IDENTIFIER][0]; + return $glueRequestTransfer->getMeta()[AppKernelConfig::HEADER_TENANT_IDENTIFIER][0] ?? ''; } /** @@ -41,12 +79,12 @@ protected function getTenantIdentifier(GlueRequestTransfer $glueRequestTransfer) */ protected function getConfiguration(GlueRequestTransfer $glueRequestTransfer): array { - $content = (array)$this->utilEncodingService->decodeJson((string)$glueRequestTransfer->getContent(), true); + $content = (array)$this->appKernelToUtilEncodingService->decodeJson((string)$glueRequestTransfer->getContent(), true); if (!isset($content['data']['attributes']['configuration'])) { return []; } - return (array)$this->utilEncodingService->decodeJson($content['data']['attributes']['configuration'], true); + return (array)$this->appKernelToUtilEncodingService->decodeJson($content['data']['attributes']['configuration'], true); } } diff --git a/src/Spryker/Glue/AppKernel/Mapper/GlueRequestMapperInterface.php b/src/Spryker/Glue/AppKernel/Mapper/GlueRequestMapperInterface.php index 52ad52f..38375ad 100644 --- a/src/Spryker/Glue/AppKernel/Mapper/GlueRequestMapperInterface.php +++ b/src/Spryker/Glue/AppKernel/Mapper/GlueRequestMapperInterface.php @@ -8,7 +8,10 @@ namespace Spryker\Glue\AppKernel\Mapper; use Generated\Shared\Transfer\AppConfigTransfer; +use Generated\Shared\Transfer\ConfigurationValidationRequestTransfer; +use Generated\Shared\Transfer\ConfigurationValidationResponseTransfer; use Generated\Shared\Transfer\GlueRequestTransfer; +use Generated\Shared\Transfer\GlueRequestValidationTransfer; interface GlueRequestMapperInterface { @@ -16,4 +19,12 @@ public function mapGlueRequestTransferToAppConfigTransfer( GlueRequestTransfer $glueRequestTransfer, AppConfigTransfer $appConfigTransfer ): AppConfigTransfer; + + public function mapGlueRequestTransferToConfigurationValidationRequestTransfer( + GlueRequestTransfer $glueRequestTransfer + ): ConfigurationValidationRequestTransfer; + + public function mapConfigurationValidationResponseTransferToGlueRequestValidationTransfer( + ConfigurationValidationResponseTransfer $configurationValidationResponseTransfer + ): GlueRequestValidationTransfer; } diff --git a/src/Spryker/Glue/AppKernel/Plugin/RequestValidator/BodyStructureValidatorPlugin.php b/src/Spryker/Glue/AppKernel/Plugin/RequestValidator/BodyStructureValidatorPlugin.php deleted file mode 100644 index baac3ef..0000000 --- a/src/Spryker/Glue/AppKernel/Plugin/RequestValidator/BodyStructureValidatorPlugin.php +++ /dev/null @@ -1,30 +0,0 @@ -getFactory()->createBodyStructureValidator()->validate($glueRequestTransfer); - } -} diff --git a/src/Spryker/Glue/AppKernel/Plugin/RequestValidator/HeaderValidatorPlugin.php b/src/Spryker/Glue/AppKernel/Plugin/RequestValidator/HeaderValidatorPlugin.php deleted file mode 100644 index b06cddc..0000000 --- a/src/Spryker/Glue/AppKernel/Plugin/RequestValidator/HeaderValidatorPlugin.php +++ /dev/null @@ -1,30 +0,0 @@ -getFactory()->createHeaderValidator()->validate($glueRequestTransfer); - } -} diff --git a/src/Spryker/Glue/AppKernel/Plugin/RouteProvider/AppKernelRouteProviderPlugin.php b/src/Spryker/Glue/AppKernel/Plugin/RouteProvider/AppKernelRouteProviderPlugin.php index 219c509..60c6556 100644 --- a/src/Spryker/Glue/AppKernel/Plugin/RouteProvider/AppKernelRouteProviderPlugin.php +++ b/src/Spryker/Glue/AppKernel/Plugin/RouteProvider/AppKernelRouteProviderPlugin.php @@ -16,6 +16,9 @@ use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; +/** + * @codeCoverageIgnore + */ class AppKernelRouteProviderPlugin extends AbstractPlugin implements RouteProviderPluginInterface { public function addRoutes(RouteCollection $routeCollection): RouteCollection diff --git a/src/Spryker/Glue/AppKernel/Validator/BodyStructureValidator.php b/src/Spryker/Glue/AppKernel/Validator/BodyStructureValidator.php index e58aa59..49644d1 100644 --- a/src/Spryker/Glue/AppKernel/Validator/BodyStructureValidator.php +++ b/src/Spryker/Glue/AppKernel/Validator/BodyStructureValidator.php @@ -11,7 +11,7 @@ use Generated\Shared\Transfer\GlueRequestTransfer; use Generated\Shared\Transfer\GlueRequestValidationTransfer; use Spryker\Glue\AppKernel\AppKernelConfig; -use Spryker\Service\UtilEncoding\UtilEncodingServiceInterface; +use Spryker\Glue\AppKernel\Dependency\Service\AppKernelToUtilEncodingServiceInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Validator\Constraints\Collection; use Symfony\Component\Validator\Constraints\EqualTo; @@ -25,7 +25,7 @@ class BodyStructureValidator implements RequestValidatorInterface { public function __construct( protected ValidatorInterface $validator, - protected UtilEncodingServiceInterface $utilEncodingService + protected AppKernelToUtilEncodingServiceInterface $appKernelToUtilEncodingService ) { } @@ -35,7 +35,7 @@ public function validate(GlueRequestTransfer $glueRequestTransfer): GlueRequestV ->setIsValid(true) ->setStatus(Response::HTTP_OK); - $content = $this->utilEncodingService->decodeJson((string)$glueRequestTransfer->getContent(), true); + $content = $this->appKernelToUtilEncodingService->decodeJson((string)$glueRequestTransfer->getContent(), true); $constraintViolationList = $this->validator->validate($content, $this->getConstraintForRequestStructure()); if ($constraintViolationList->count() > 0) { diff --git a/src/Spryker/Glue/AppKernel/Validator/ConfigurationValidator.php b/src/Spryker/Glue/AppKernel/Validator/ConfigurationValidator.php new file mode 100644 index 0000000..1451610 --- /dev/null +++ b/src/Spryker/Glue/AppKernel/Validator/ConfigurationValidator.php @@ -0,0 +1,35 @@ +glueRequestMapper->mapGlueRequestTransferToConfigurationValidationRequestTransfer($glueRequestTransfer); + $configurationValidationResponseTransfer = $this->appKernelToAppKernelFacade->validateConfiguration($configurationValidationRequestTransfer); + + return $this->glueRequestMapper->mapConfigurationValidationResponseTransferToGlueRequestValidationTransfer($configurationValidationResponseTransfer); + } +} diff --git a/src/Spryker/Glue/AppKernel/Validator/RequestValidator.php b/src/Spryker/Glue/AppKernel/Validator/RequestValidator.php index a2f6dbb..d1f3bfe 100644 --- a/src/Spryker/Glue/AppKernel/Validator/RequestValidator.php +++ b/src/Spryker/Glue/AppKernel/Validator/RequestValidator.php @@ -14,15 +14,18 @@ class RequestValidator implements RequestValidatorInterface { /** * @param array<\Spryker\Glue\GlueApplicationExtension\Dependency\Plugin\RequestValidatorPluginInterface> $requestValidatorPlugins + * @param array<\Spryker\Glue\AppKernel\Validator\RequestValidatorInterface> $defaultRequestValidatorPlugins */ - public function __construct(protected array $requestValidatorPlugins) + public function __construct(protected array $requestValidatorPlugins, protected array $defaultRequestValidatorPlugins = []) { } public function validate(GlueRequestTransfer $glueRequestTransfer): GlueRequestValidationTransfer { - foreach ($this->requestValidatorPlugins as $requestValidatorPlugin) { - $glueRequestValidationTransfer = $requestValidatorPlugin->validate($glueRequestTransfer); + $validators = array_merge($this->defaultRequestValidatorPlugins, $this->requestValidatorPlugins); + + foreach ($validators as $validator) { + $glueRequestValidationTransfer = $validator->validate($glueRequestTransfer); if (!$glueRequestValidationTransfer->getIsValid()) { return $glueRequestValidationTransfer; diff --git a/src/Spryker/Shared/AppKernel/Transfer/app_kernel.transfer.xml b/src/Spryker/Shared/AppKernel/Transfer/app_kernel.transfer.xml index 53cd2f7..ef7f29b 100644 --- a/src/Spryker/Shared/AppKernel/Transfer/app_kernel.transfer.xml +++ b/src/Spryker/Shared/AppKernel/Transfer/app_kernel.transfer.xml @@ -5,6 +5,7 @@ + @@ -31,6 +32,26 @@ + + + + + + + + + + + + + + + + + + + + @@ -59,6 +80,7 @@ + diff --git a/src/Spryker/Zed/AppKernel/AppKernelDependencyProvider.php b/src/Spryker/Zed/AppKernel/AppKernelDependencyProvider.php index e977e20..d9845f0 100644 --- a/src/Spryker/Zed/AppKernel/AppKernelDependencyProvider.php +++ b/src/Spryker/Zed/AppKernel/AppKernelDependencyProvider.php @@ -7,11 +7,14 @@ namespace Spryker\Zed\AppKernel; +use Generated\Shared\Transfer\AppConfigTransfer; +use Generated\Shared\Transfer\AppConfigValidateResponseTransfer; use Spryker\Zed\AppKernel\Dependency\Client\AppKernelToSecretsManagerClientBridge; use Spryker\Zed\AppKernel\Dependency\Facade\AppKernelToMessageBrokerFacadeBridge; use Spryker\Zed\AppKernel\Dependency\Facade\AppKernelToMessageBrokerFacadeInterface; use Spryker\Zed\AppKernel\Dependency\Service\AppKernelToUtilEncodingServiceBridge; use Spryker\Zed\AppKernel\Dependency\Service\AppKernelToUtilTextServiceBridge; +use Spryker\Zed\AppKernelExtension\Dependency\Plugin\AppKernelPlatformPluginInterface; use Spryker\Zed\Kernel\AbstractBundleDependencyProvider; use Spryker\Zed\Kernel\Container; @@ -20,6 +23,11 @@ */ class AppKernelDependencyProvider extends AbstractBundleDependencyProvider { + /** + * @var string + */ + public const PLUGIN_PLATFORM = 'APP_KERNEL:PLUGIN_PLATFORM'; + /** * @var string */ @@ -71,6 +79,7 @@ public function provideBusinessLayerDependencies(Container $container): Containe $container = $this->addConfigurationAfterSavePlugins($container); $container = $this->addConfigurationBeforeDeletePlugins($container); $container = $this->addConfigurationAfterDeletePlugins($container); + $container = $this->addPlatformPlugin($container); return $container; } @@ -82,6 +91,31 @@ public function providePersistenceLayerDependencies(Container $container): Conta return $this->addUtilEncodingService($container); } + protected function addPlatformPlugin(Container $container): Container + { + $container->set(static::PLUGIN_PLATFORM, function (): AppKernelPlatformPluginInterface { + // @codeCoverageIgnoreStart + return $this->getPlatformPlugin(); + // @codeCoverageIgnoreEnd + }); + + return $container; + } + + /** + * This method must be overridden in the project implementation of the AppKernelDependencyProvider. + * This one exists only for simpler testing. + */ + protected function getPlatformPlugin(): AppKernelPlatformPluginInterface + { + return new class implements AppKernelPlatformPluginInterface { + public function validateConfiguration(AppConfigTransfer $appConfigTransfer): AppConfigValidateResponseTransfer + { + return (new AppConfigValidateResponseTransfer())->setIsSuccessful(true); + } + }; + } + protected function addMessageBrokerFacade(Container $container): Container { $container->set(static::FACADE_MESSAGE_BROKER, static function (Container $container): AppKernelToMessageBrokerFacadeInterface { diff --git a/src/Spryker/Zed/AppKernel/Business/AppKernelBusinessFactory.php b/src/Spryker/Zed/AppKernel/Business/AppKernelBusinessFactory.php index db8598a..f8846e8 100644 --- a/src/Spryker/Zed/AppKernel/Business/AppKernelBusinessFactory.php +++ b/src/Spryker/Zed/AppKernel/Business/AppKernelBusinessFactory.php @@ -8,6 +8,7 @@ namespace Spryker\Zed\AppKernel\Business; use Spryker\Zed\AppKernel\AppKernelDependencyProvider; +use Spryker\Zed\AppKernel\Business\Configuration\ConfigurationValidator; use Spryker\Zed\AppKernel\Business\EncryptionConfigurator\PropelEncryptionConfigurator; use Spryker\Zed\AppKernel\Business\EncryptionConfigurator\PropelEncryptionConfiguratorInterface; use Spryker\Zed\AppKernel\Business\MessageSender\MessageSender; @@ -20,7 +21,9 @@ use Spryker\Zed\AppKernel\Business\Writer\ConfigWriterInterface; use Spryker\Zed\AppKernel\Dependency\Client\AppKernelToSecretsManagerClientInterface; use Spryker\Zed\AppKernel\Dependency\Facade\AppKernelToMessageBrokerFacadeInterface; +use Spryker\Zed\AppKernel\Dependency\Service\AppKernelToUtilEncodingServiceInterface; use Spryker\Zed\AppKernel\Dependency\Service\AppKernelToUtilTextServiceInterface; +use Spryker\Zed\AppKernelExtension\Dependency\Plugin\AppKernelPlatformPluginInterface; use Spryker\Zed\Kernel\Business\AbstractBusinessFactory; /** @@ -30,6 +33,11 @@ */ class AppKernelBusinessFactory extends AbstractBusinessFactory { + public function createConfigurationValidator(): ConfigurationValidator + { + return new ConfigurationValidator($this->getPlatformPlugin(), $this->getUtilEncodingService()); + } + public function createConfigWriter(): ConfigWriterInterface { return new ConfigWriter( @@ -63,7 +71,7 @@ public function createSecretsManager(): SecretsManagerInterface { return new SecretsManager( $this->getSecretsManagerClient(), - $this->getTextServiceUtil(), + $this->getUtilTextService(), ); } @@ -72,16 +80,26 @@ public function createMessageSender(): MessageSenderInterface return new MessageSender($this->getMessageBrokerFacade(), $this->getConfig()); } + public function getPlatformPlugin(): AppKernelPlatformPluginInterface + { + return $this->getProvidedDependency(AppKernelDependencyProvider::PLUGIN_PLATFORM); + } + public function getSecretsManagerClient(): AppKernelToSecretsManagerClientInterface { return $this->getProvidedDependency(AppKernelDependencyProvider::CLIENT_SECRETS_MANAGER); } - public function getTextServiceUtil(): AppKernelToUtilTextServiceInterface + public function getUtilTextService(): AppKernelToUtilTextServiceInterface { return $this->getProvidedDependency(AppKernelDependencyProvider::SERVICE_UTIL_TEXT); } + public function getUtilEncodingService(): AppKernelToUtilEncodingServiceInterface + { + return $this->getProvidedDependency(AppKernelDependencyProvider::SERVICE_UTIL_ENCODING); + } + /** * @return array<\Spryker\Zed\AppKernelExtension\Dependency\Plugin\ConfigurationBeforeSavePluginInterface> */ diff --git a/src/Spryker/Zed/AppKernel/Business/AppKernelFacade.php b/src/Spryker/Zed/AppKernel/Business/AppKernelFacade.php index db10cea..ca6525c 100644 --- a/src/Spryker/Zed/AppKernel/Business/AppKernelFacade.php +++ b/src/Spryker/Zed/AppKernel/Business/AppKernelFacade.php @@ -10,6 +10,8 @@ use Generated\Shared\Transfer\AppConfigCriteriaTransfer; use Generated\Shared\Transfer\AppConfigResponseTransfer; use Generated\Shared\Transfer\AppConfigTransfer; +use Generated\Shared\Transfer\ConfigurationValidationRequestTransfer; +use Generated\Shared\Transfer\ConfigurationValidationResponseTransfer; use Spryker\Zed\Kernel\Business\AbstractFacade; /** @@ -19,11 +21,22 @@ */ class AppKernelFacade extends AbstractFacade implements AppKernelFacadeInterface { - /** - * {@inheritDoc} - * - * @api - */ + /** + * @api + * + * @inheritDoc + */ + public function validateConfiguration( + ConfigurationValidationRequestTransfer $configurationValidationRequestTransfer + ): ConfigurationValidationResponseTransfer { + return $this->getFactory()->createConfigurationValidator()->validateConfiguration($configurationValidationRequestTransfer); + } + + /** + * {@inheritDoc} + * + * @api + */ public function saveConfig(AppConfigTransfer $appConfigTransfer): AppConfigResponseTransfer { return $this->getFactory() diff --git a/src/Spryker/Zed/AppKernel/Business/AppKernelFacadeInterface.php b/src/Spryker/Zed/AppKernel/Business/AppKernelFacadeInterface.php index 999a87a..b48e662 100644 --- a/src/Spryker/Zed/AppKernel/Business/AppKernelFacadeInterface.php +++ b/src/Spryker/Zed/AppKernel/Business/AppKernelFacadeInterface.php @@ -10,9 +10,29 @@ use Generated\Shared\Transfer\AppConfigCriteriaTransfer; use Generated\Shared\Transfer\AppConfigResponseTransfer; use Generated\Shared\Transfer\AppConfigTransfer; +use Generated\Shared\Transfer\ConfigurationValidationRequestTransfer; +use Generated\Shared\Transfer\ConfigurationValidationResponseTransfer; interface AppKernelFacadeInterface { + /** + * Specification: + * - Converts the `GlueRequestTransfer::getContent()` data from a JSON string into a `AppConfigTransfer`. + * - Calls `PlatformPluginInterface::validateConfiguration()` and passes the `AppConfigTransfer`. + * - When `PlatformPluginInterface::validateConfiguration()` throws an exception, the exception is logged. + * - When `PlatformPluginInterface::validateConfiguration()` throws an exception, a `GlueRequestValidationTransfer` with a failed response is returned. + * - When `PlatformPluginInterface::validateConfiguration()` is successful, a `GlueRequestValidationTransfer` with HTTP Status Code 200 (OK) is returned. + * - When `PlatformPluginInterface::validateConfiguration()` is not successful, validation errors from the `AppConfigValidateResponseTransfer` are converted + * to error messages and added to the `GlueRequestValidationTransfer`. + * - When `PlatformPluginInterface::validateConfiguration()` is NOT successful, a `GlueRequestValidationTransfer` with HTTP Status Code 422 (UNPROCESSABLE ENTITY) is returned. + * - Requires `GlueRequestTransfer::getContent()`. + * + * @api + */ + public function validateConfiguration( + ConfigurationValidationRequestTransfer $configurationValidationRequestTransfer + ): ConfigurationValidationResponseTransfer; + /** * Specification: * - Saves the App configuration in the DB. diff --git a/src/Spryker/Zed/AppKernel/Business/Configuration/ConfigurationValidator.php b/src/Spryker/Zed/AppKernel/Business/Configuration/ConfigurationValidator.php new file mode 100644 index 0000000..9f39280 --- /dev/null +++ b/src/Spryker/Zed/AppKernel/Business/Configuration/ConfigurationValidator.php @@ -0,0 +1,79 @@ +setIsSuccessful(true); + + try { + $appConfigValidateResponseTransfer = $this->appKernelPlatformPlugin->validateConfiguration($configurationValidationRequestTransfer->getAppConfigOrFail()); + } catch (Throwable $throwable) { + return $this->buildFailedResponseFromException($throwable, $configurationValidationResponseTransfer); + } + + if ($appConfigValidateResponseTransfer->getIsSuccessful() === true) { + return $configurationValidationResponseTransfer; + } + + return $this->mapAppConfigurationValidationResponseTransferToConfigurationValidationResponseTransfer($appConfigValidateResponseTransfer, $configurationValidationResponseTransfer); + } + + protected function mapAppConfigurationValidationResponseTransferToConfigurationValidationResponseTransfer( + AppConfigValidateResponseTransfer $appConfigValidateResponseTransfer, + ConfigurationValidationResponseTransfer $configurationValidationResponseTransfer + ): ConfigurationValidationResponseTransfer { + $configurationValidationResponseTransfer + ->setIsSuccessful(false); + + $messages = []; + foreach ($appConfigValidateResponseTransfer->getConfigurationValidationErrors() as $configurationValidationErrorTransfer) { + foreach ($configurationValidationErrorTransfer->getErrorMessages() as $errorMessage) { + $messages[$configurationValidationErrorTransfer->getProperty()][] = $errorMessage; + } + + $messages[$configurationValidationErrorTransfer->getProperty()] = implode(', ', $messages[$configurationValidationErrorTransfer->getProperty()]); + } + + $configurationValidationResponseTransfer->setMessage(implode(', ', $messages)); + + return $configurationValidationResponseTransfer; + } + + protected function buildFailedResponseFromException( + Throwable $throwable, + ConfigurationValidationResponseTransfer $configurationValidationResponseTransfer + ): ConfigurationValidationResponseTransfer { + $this->getLogger()->error($throwable->getMessage()); + $configurationValidationResponseTransfer + ->setIsSuccessful(false) + ->setExceptionMessage($throwable->getMessage()); + + return $configurationValidationResponseTransfer; + } +} diff --git a/src/Spryker/Zed/AppKernel/Business/Writer/ConfigWriter.php b/src/Spryker/Zed/AppKernel/Business/Writer/ConfigWriter.php index bd8af4e..6c1e263 100644 --- a/src/Spryker/Zed/AppKernel/Business/Writer/ConfigWriter.php +++ b/src/Spryker/Zed/AppKernel/Business/Writer/ConfigWriter.php @@ -77,9 +77,9 @@ protected function doSaveAppConfig(AppConfigTransfer $appConfigTransfer): AppCon $this->configurePropelEncryption($appConfigTransfer); - // New configurations will be set to "connected" by default. + // New configurations will be set to true by default. if ($appConfigTransfer->getStatus() === null || $appConfigTransfer->getStatus() === '' || $appConfigTransfer->getStatus() === '0') { - $appConfigTransfer->setStatus(AppKernelConfig::APP_STATUS_CONNECTED); + $appConfigTransfer->setStatus(AppKernelConfig::APP_STATUS_NEW); } // When the app gets deactivated, we set the status to disconnected. diff --git a/tests/SprykerTest/Glue/AppKernel/Controller/AppConfigureControllerTest.php b/tests/SprykerTest/Glue/AppKernel/Controller/AppConfigureControllerTest.php index 527c4d3..a751e3c 100644 --- a/tests/SprykerTest/Glue/AppKernel/Controller/AppConfigureControllerTest.php +++ b/tests/SprykerTest/Glue/AppKernel/Controller/AppConfigureControllerTest.php @@ -7,16 +7,26 @@ namespace SprykerTest\Glue\AppKernel\Controller; +use Codeception\Stub; use Codeception\Test\Unit; use Exception; use Generated\Shared\Transfer\AppConfigTransfer; -use Spryker\Zed\AppKernel\AppKernelConfig; +use Generated\Shared\Transfer\AppConfigValidateResponseTransfer; +use Generated\Shared\Transfer\ConfigurationValidationErrorTransfer; +use Orm\Zed\AppKernel\Persistence\SpyAppConfigQuery; +use Ramsey\Uuid\Uuid; +use Spryker\Glue\AppKernel\AppKernelConfig; +use Spryker\Zed\AppKernel\AppKernelConfig as SprykerAppKernelConfig; use Spryker\Zed\AppKernel\AppKernelDependencyProvider; +use Spryker\Zed\AppKernelExtension\Dependency\Plugin\AppKernelPlatformPluginInterface; use Spryker\Zed\AppKernelExtension\Dependency\Plugin\ConfigurationAfterSavePluginInterface; use Spryker\Zed\AppKernelExtension\Dependency\Plugin\ConfigurationBeforeSavePluginInterface; use SprykerTest\Glue\AppKernel\AppKernelTester; +use SprykerTest\Shared\Testify\Helper\DataCleanupHelperTrait; +use SprykerTest\Shared\Testify\Helper\DependencyHelperTrait; use SprykerTest\Shared\Testify\Helper\LocatorHelperTrait; use SprykerTest\Zed\Testify\Helper\Business\DependencyProviderHelperTrait; +use Symfony\Component\HttpFoundation\Response; /** * Auto-generated group annotations @@ -32,6 +42,8 @@ class AppConfigureControllerTest extends Unit { use LocatorHelperTrait; use DependencyProviderHelperTrait; + use DependencyHelperTrait; + use DataCleanupHelperTrait; /** * @var \SprykerTest\Glue\AppKernel\AppKernelTester @@ -86,42 +98,41 @@ public function testPostConfigureReturnsSuccessResponseAndActivatesTheAppConfigu /** * @return void */ - public function testPostConfigureReturnsSuccessResponseAndActivatesTheAppConfigurationWhenAnAppConfigurationExistsAndWasMarkedAsDisconnected(): void + public function testPostConfigureReturnsSuccessResponseAndKeepsPreviousConfigurationWhenAnAppConfigurationExists(): void { // Arrange $glueRequest = $this->tester->createGlueRequestFromFixture('valid-config-request'); - $this->tester->havePersistedAppConfigTransfer([AppConfigTransfer::TENANT_IDENTIFIER => 'tenant-identifier', AppConfigTransfer::IS_ACTIVE => true, AppConfigTransfer::STATUS => AppKernelConfig::APP_STATUS_DISCONNECTED]); + $this->tester->havePersistedAppConfigTransfer([AppConfigTransfer::TENANT_IDENTIFIER => 'tenant-identifier', AppConfigTransfer::IS_ACTIVE => false, AppConfigTransfer::CONFIG => ['key' => 'value']]); $appConfigController = $this->tester->createAppConfigController(); + $expectedConfig = ['key' => 'value', 'clientId' => 'ClientID', 'clientSecret' => 'ClientSecret', 'isActive' => true]; + // Act $glueResponse = $appConfigController->postConfigureAction($glueRequest); + // Set the expected content for the validation + $glueRequest->setContent('{"data":{"type":"configuration","attributes":{"configuration":"{\"key\":\"value\",\"clientId\":\"ClientID\",\"clientSecret\":\"ClientSecret\",\"isActive\":true}"}}}'); + // Assert $this->tester->assertGlueResponseContainsSuccessContents($glueRequest, $glueResponse); - $this->tester->assertAppConfigIsActivated('tenant-identifier'); + $this->tester->assertPersistedAppConfig('tenant-identifier', $expectedConfig); } /** * @return void */ - public function testPostConfigureReturnsSuccessResponseAndKeepsPreviousConfigurationWhenAnAppConfigurationExists(): void + public function testGivenAnAppConfigInStatusDisconnectedAndIsActiveTrueWhenTheConfigurationIsSavedTheStatusIsChangedToConnected(): void { // Arrange $glueRequest = $this->tester->createGlueRequestFromFixture('valid-config-request'); - $this->tester->havePersistedAppConfigTransfer([AppConfigTransfer::TENANT_IDENTIFIER => 'tenant-identifier', AppConfigTransfer::IS_ACTIVE => false, AppConfigTransfer::CONFIG => ['key' => 'value']]); + $this->tester->havePersistedAppConfigTransfer([AppConfigTransfer::TENANT_IDENTIFIER => 'tenant-identifier', AppConfigTransfer::IS_ACTIVE => true, AppConfigTransfer::STATUS => SprykerAppKernelConfig::APP_STATUS_DISCONNECTED, AppConfigTransfer::CONFIG => ['key' => 'value']]); $appConfigController = $this->tester->createAppConfigController(); - $expectedConfig = ['key' => 'value', 'clientId' => 'ClientID', 'clientSecret' => 'ClientSecret', 'isActive' => true]; - // Act - $glueResponse = $appConfigController->postConfigureAction($glueRequest); - - // Set the expected content for the validation - $glueRequest->setContent('{"data":{"type":"configuration","attributes":{"configuration":"{\"key\":\"value\",\"clientId\":\"ClientID\",\"clientSecret\":\"ClientSecret\",\"isActive\":true}"}}}'); + $appConfigController->postConfigureAction($glueRequest); // Assert - $this->tester->assertGlueResponseContainsSuccessContents($glueRequest, $glueResponse); - $this->tester->assertPersistedAppConfig('tenant-identifier', $expectedConfig); + $this->tester->assertAppConfigStatus('tenant-identifier', SprykerAppKernelConfig::APP_STATUS_CONNECTED); } /** @@ -373,4 +384,196 @@ public function beforeSave(AppConfigTransfer $appConfigTransfer): AppConfigTrans // Assert $this->tester->assertGlueResponseContainsErrorMessageWhenExceptionWasThrown($glueResponse); } + + public function testReceivingConfigurationFromAppStoreCatalogWithoutTheAcceptLanguageHeaderAppliesDefaultLocaleEnUSAndSavesAppConfigurationWhenPlatformValidationWasSuccessful(): void + { + // Arrange + $tenantIdentifier = Uuid::uuid4()->toString(); + + $this->getDataCleanupHelper()->_addCleanup(function () use ($tenantIdentifier): void { + SpyAppConfigQuery::create() + ->filterByTenantIdentifier($tenantIdentifier) + ->delete(); + }); + + $appConfigValidateResponseTransfer = new AppConfigValidateResponseTransfer(); + $appConfigValidateResponseTransfer->setIsSuccessful(true); + + $platformPluginMock = Stub::makeEmpty(AppKernelPlatformPluginInterface::class, [ + 'validateConfiguration' => $appConfigValidateResponseTransfer, + ]); + + $this->getDependencyHelper()->setDependency(AppKernelDependencyProvider::PLUGIN_PLATFORM, $platformPluginMock); + + $this->tester->setHeaders([ + AppKernelConfig::HEADER_TENANT_IDENTIFIER => $tenantIdentifier, + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + ]); + + // Act + $response = $this->tester->sendPost('/private/configure', $this->tester->getAppConfigureRequestData()); + + // Assert + $this->assertSame(200, $response->getStatusCode()); + $this->tester->assertAppConfigForTenantEquals($tenantIdentifier); + } + + public function testReceivingConfigurationFromAppStoreCatalogSavesAppConfigurationWhenPlatformValidationWasSuccessful(): void + { + // Arrange + $tenantIdentifier = Uuid::uuid4()->toString(); + + $this->getDataCleanupHelper()->_addCleanup(function () use ($tenantIdentifier): void { + SpyAppConfigQuery::create() + ->filterByTenantIdentifier($tenantIdentifier) + ->delete(); + }); + + $appConfigValidateResponseTransfer = new AppConfigValidateResponseTransfer(); + $appConfigValidateResponseTransfer->setIsSuccessful(true); + + $platformPluginMock = Stub::makeEmpty(AppKernelPlatformPluginInterface::class, [ + 'validateConfiguration' => $appConfigValidateResponseTransfer, + ]); + + $this->getDependencyHelper()->setDependency(AppKernelDependencyProvider::PLUGIN_PLATFORM, $platformPluginMock); + + $this->tester->setHeaders([ + AppKernelConfig::HEADER_TENANT_IDENTIFIER => $tenantIdentifier, + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'Accept-Language' => 'en-US, en;q=0.9,*;q=0.5', + ]); + + // Act + $response = $this->tester->sendPost('/private/configure', $this->tester->getAppConfigureRequestData()); + + // Assert + $this->assertSame(200, $response->getStatusCode()); + $this->tester->assertAppConfigForTenantEquals($tenantIdentifier); + } + + public function testReceivingConfigurationFromAppStoreCatalogReturns422UnprocessableEntityWhenPlatformValidationFailed(): void + { + // Arrange + $tenantIdentifier = Uuid::uuid4()->toString(); + + $appConfigValidateResponseTransfer = new AppConfigValidateResponseTransfer(); + $appConfigValidateResponseTransfer->setIsSuccessful(false); + + $platformPluginMock = Stub::makeEmpty(AppKernelPlatformPluginInterface::class, [ + 'validateConfiguration' => $appConfigValidateResponseTransfer, + ]); + + $this->getDependencyHelper()->setDependency(AppKernelDependencyProvider::PLUGIN_PLATFORM, $platformPluginMock); + + $this->tester->setHeaders([ + AppKernelConfig::HEADER_TENANT_IDENTIFIER => $tenantIdentifier, + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'Accept-Language' => 'en-US, en;q=0.9,*;q=0.5', + ]); + + // Act + $response = $this->tester->sendPost('/private/configure', $this->tester->getAppConfigureRequestData()); + + // Assert + $this->assertSame(Response::HTTP_UNPROCESSABLE_ENTITY, $response->getStatusCode()); + $this->tester->assertAppConfigurationForTenantDoesNotExist($tenantIdentifier); + } + + public function testReceivingConfigurationFromAppStoreCatalogReturns422UnprocessableEntityWithErrorMessagesWhenPlatformValidationFailed(): void + { + // Arrange + $tenantIdentifier = Uuid::uuid4()->toString(); + + $configurationValidationErrorTransfer = new ConfigurationValidationErrorTransfer(); + $configurationValidationErrorTransfer->addErrorMessage('Something went wrong'); + + $appConfigValidateResponseTransfer = new AppConfigValidateResponseTransfer(); + $appConfigValidateResponseTransfer + ->setIsSuccessful(false) + ->addConfigurationValidationError($configurationValidationErrorTransfer); + + $platformPluginMock = Stub::makeEmpty(AppKernelPlatformPluginInterface::class, [ + 'validateConfiguration' => $appConfigValidateResponseTransfer, + ]); + + $this->getDependencyHelper()->setDependency(AppKernelDependencyProvider::PLUGIN_PLATFORM, $platformPluginMock); + + $this->tester->setHeaders([ + AppKernelConfig::HEADER_TENANT_IDENTIFIER => $tenantIdentifier, + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'Accept-Language' => 'en-US, en;q=0.9,*;q=0.5', + ]); + + // Act + $response = $this->tester->sendPost('/private/configure', $this->tester->getAppConfigureRequestData()); + + // Assert + $this->assertSame(Response::HTTP_UNPROCESSABLE_ENTITY, $response->getStatusCode()); + $this->tester->assertAppConfigurationForTenantDoesNotExist($tenantIdentifier); + } + + public function testReceivingConfigurationFromAppStoreCatalogReturns422UnprocessableEntityWithErrorMessagesWhenPlatformValidationThrowsAnException(): void + { + // Arrange + $tenantIdentifier = Uuid::uuid4()->toString(); + + $platformPluginMock = Stub::makeEmpty(AppKernelPlatformPluginInterface::class, [ + 'validateConfiguration' => static function (): never { + throw new Exception('Something went wrong'); + }, + ]); + + $this->getDependencyHelper()->setDependency(AppKernelDependencyProvider::PLUGIN_PLATFORM, $platformPluginMock); + + $this->tester->setHeaders([ + AppKernelConfig::HEADER_TENANT_IDENTIFIER => $tenantIdentifier, + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'Accept-Language' => 'en-US, en;q=0.9,*;q=0.5', + ]); + + // Act + $response = $this->tester->sendPost('/private/configure', $this->tester->getAppConfigureRequestData()); + + // Assert + $this->assertSame(Response::HTTP_INTERNAL_SERVER_ERROR, $response->getStatusCode()); + $this->tester->assertAppConfigurationForTenantDoesNotExist($tenantIdentifier); + } + + public function testDisconnectAppForAnExistingTenantDeactivatesAppConfiguration(): void + { + // Arrange + $tenantIdentifier = Uuid::uuid4()->toString(); + + $this->tester->haveAppConfigForTenant($tenantIdentifier); + $this->tester->assertAppConfigForTenantEquals($tenantIdentifier); + + $appConfigValidateResponseTransfer = new AppConfigValidateResponseTransfer(); + $appConfigValidateResponseTransfer->setIsSuccessful(false); + + $platformPluginMock = Stub::makeEmpty(AppKernelPlatformPluginInterface::class, [ + 'validateConfiguration' => $appConfigValidateResponseTransfer, + ]); + + $this->getDependencyHelper()->setDependency(AppKernelDependencyProvider::PLUGIN_PLATFORM, $platformPluginMock); + + $this->tester->setHeaders([ + AppKernelConfig::HEADER_TENANT_IDENTIFIER => $tenantIdentifier, + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'Accept-Language' => 'en-US, en;q=0.9,*;q=0.5', + ]); + + // Act + $response = $this->tester->sendPost('/private/disconnect'); + + // Assert + $this->assertSame(204, $response->getStatusCode()); + $this->tester->assertAppConfigurationForTenantIsDeactivated($tenantIdentifier); + } } diff --git a/tests/SprykerTest/Glue/AppKernel/codeception.yml b/tests/SprykerTest/Glue/AppKernel/codeception.yml index b97bc51..60cd1b9 100644 --- a/tests/SprykerTest/Glue/AppKernel/codeception.yml +++ b/tests/SprykerTest/Glue/AppKernel/codeception.yml @@ -18,6 +18,7 @@ suites: modules: enabled: - \SprykerTest\Zed\Testify\Helper\Business\BusinessHelper + - \SprykerTest\Glue\Testify\Helper\DependencyProviderHelper - \SprykerTest\Shared\Testify\Helper\Environment - \SprykerTest\Zed\Propel\Helper\PropelEncryptionHelper - \SprykerTest\Zed\Testify\Helper\Business\DependencyProviderHelper @@ -25,7 +26,11 @@ suites: - \SprykerTest\Glue\AppKernel\Helper\AppKernelAssertionHelper - \SprykerTest\Zed\AppKernel\Helper\AppKernelAssertionHelper - \SprykerTest\Zed\AppKernel\Helper\AppKernelHelper + - \SprykerTest\Shared\AppKernel\Helper\AppConfigHelper - \SprykerTest\Shared\Propel\Helper\TransactionHelper - \SprykerTest\Shared\Testify\Helper\LocatorHelper - \SprykerTest\Shared\Testify\Helper\ConfigHelper - \SprykerTest\Zed\MessageBroker\Helper\InMemoryMessageBrokerHelper + - \SprykerTest\Shared\Testify\Helper\DataCleanupHelper + - \SprykerTest\Shared\Testify\Helper\DependencyHelper + - \SprykerTest\Glue\Testify\Helper\AppKernelGlueBackendApiHelper diff --git a/tests/SprykerTest/Glue/Testify/_support/Helper/AppKernelGlueBackendApiHelper.php b/tests/SprykerTest/Glue/Testify/_support/Helper/AppKernelGlueBackendApiHelper.php new file mode 100644 index 0000000..f31629a --- /dev/null +++ b/tests/SprykerTest/Glue/Testify/_support/Helper/AppKernelGlueBackendApiHelper.php @@ -0,0 +1,145 @@ +|string $parameters + */ + protected function executeRequest(string $url, string $method, array $parameters = []): Response + { + $this->addHeader('Accept', 'application/json'); + + $request = Request::create($url, strtolower($method), $parameters, [], [], [], $parameters !== [] ? json_encode($parameters, JSON_PRESERVE_ZERO_FRACTION | JSON_THROW_ON_ERROR) : null); + $request = $this->removeServerAndHeaderDefaults($request); + + $request->headers->add($this->headers); + + // Set the predefined Request so that the GlueBackendApiApplication can pick it up instead of creating an empty Request. + $this->getRequestBuilderStub()->setRequest($request); + + // Run the mocked GlueBackendApiApplication. + $this->getGlueBackendApiApplication()->run(); + + // Get the response that was created from the GlueBackendApiApplication. + $response = $this->getHttpSenderStub()->getResponse(); + + $this->persistLastConnection($request, $response); + + return $response; + } + + /** + * The Request::create() method adds some default headers and server values that we do not want to have in our tests. + */ + protected function removeServerAndHeaderDefaults(Request $request): Request + { + foreach (['HTTP_ACCEPT', 'HTTP_ACCEPT_LANGUAGE', 'HTTP_ACCEPT_CHARSET'] as $server) { + $request->server->remove($server); + } + foreach (['accept', 'accept-language', 'accept-charset'] as $header) { + $request->headers->remove($header); + } + + return $request; + } + + protected function getGlueBackendApiApplication(): ApplicationInterface + { + /** @var \Spryker\Glue\GlueApplication\GlueApplicationFactory $glueApplicationFactory */ + $glueApplicationFactory = Stub::make(GlueApplicationFactory::class, [ + 'createHttpRequestBuilder' => $this->getRequestBuilderStub(), + 'createHttpSender' => $this->getHttpSenderStub(), + 'resolveDependencyProvider' => new GlueApplicationDependencyProvider(), + 'getConfig' => $this->getConfigHelper()->getModuleConfig('GlueApplication'), + ]); + + $this->getDependencyProviderHelper()->setDependency(GlueBackendApiApplicationDependencyProvider::PLUGINS_REQUEST_BUILDER, [ + new ApplicationIdentifierRequestBuilderPlugin(), + ], GlueBackendApiApplicationFactory::class); + + $this->getDependencyProviderHelper()->setDependency(GlueBackendApiApplicationDependencyProvider::PLUGINS_ROUTE_PROVIDER, [ + new AppKernelRouteProviderPlugin(), + ], GlueBackendApiApplicationFactory::class); + + $this->getDependencyProviderHelper()->setDependency(GlueApplicationDependencyProvider::PLUGINS_RESOURCES_PROVIDER, [ + new BackendResourcesProviderPlugin(), + ], get_class($glueApplicationFactory)); + + $this->getDependencyProviderHelper()->setDependency(GlueApplicationDependencyProvider::PLUGINS_CONVENTION, [ + new JsonApiConventionPlugin(), + ], get_class($glueApplicationFactory)); + + $this->getDependencyProviderHelper()->setDependency(GlueApplicationDependencyProvider::PLUGINS_ROUTES_PROVIDER, [ + new CustomRouteRoutesProviderPlugin(), + ], get_class($glueApplicationFactory)); + + $this->getDependencyProviderHelper()->setDependency(GlueApplicationDependencyProvider::PLUGINS_CONTROLLER_CACHE_COLLECTOR, [ + new BackendControllerCacheCollectorPlugin(), + ], get_class($glueApplicationFactory)); + + $this->getDependencyProviderHelper()->setDependency( + GlueBackendApiApplicationDependencyProvider::PLUGINS_RESOURCE, + $this->getJsonApiResourcePlugins(), + GlueBackendApiApplicationFactory::class, + ); + + return (new GlueBackendApiBootstrap()) + ->setFactory($glueApplicationFactory) + ->boot(); + } + + /** + * @param int $code + * + * @return void + */ + public function seeResponseCodeIs(int $code): void + { + $failureMessage = sprintf( + 'Expected HTTP Status Code: %s. Actual Status Code: %s. Response: %s', + HttpCode::getDescription($code), + HttpCode::getDescription($this->getResponse()->getStatusCode()), + $this->getResponse(), + ); + $this->assertSame($code, $this->getResponse()->getStatusCode(), $failureMessage); + } + + /** + * Overridden to not throw an exception when we are on project level and do not need to set plugins manually. + * + * @return array<\Spryker\Glue\GlueJsonApiConventionExtension\Dependency\Plugin\JsonApiResourceInterface> + */ + protected function getJsonApiResourcePlugins(): array + { + if ($this->jsonApiResourcePlugins === []) { + return []; + } + + return $this->jsonApiResourcePlugins; + } +} diff --git a/tests/SprykerTest/Glue/Testify/_support/Helper/GlueApplication/Bootstrap/GlueBackendApiBootstrap.php b/tests/SprykerTest/Glue/Testify/_support/Helper/GlueApplication/Bootstrap/GlueBackendApiBootstrap.php new file mode 100644 index 0000000..570acbe --- /dev/null +++ b/tests/SprykerTest/Glue/Testify/_support/Helper/GlueApplication/Bootstrap/GlueBackendApiBootstrap.php @@ -0,0 +1,23 @@ + $glueApplicationBootstrapPluginClassNames + */ + public function boot(array $glueApplicationBootstrapPluginClassNames = []): ApplicationInterface + { + return parent::boot([BackendApiGlueApplicationBootstrapPlugin::class]); + } +} diff --git a/tests/SprykerTest/Zed/AppKernel/_support/Helper/AppKernelAssertionHelper.php b/tests/SprykerTest/Zed/AppKernel/_support/Helper/AppKernelAssertionHelper.php index 4a9dd32..8f068a5 100644 --- a/tests/SprykerTest/Zed/AppKernel/_support/Helper/AppKernelAssertionHelper.php +++ b/tests/SprykerTest/Zed/AppKernel/_support/Helper/AppKernelAssertionHelper.php @@ -74,6 +74,32 @@ public function assertAppConfigIsActivated(string $tenantIdentifier): void )); } + /** + * @param string $tenantIdentifier + * + * @return void + */ + public function assertAppConfigStatus(string $tenantIdentifier, string $expectedStatus): void + { + $appConfigEntities = SpyAppConfigQuery::create() + ->filterByTenantIdentifier($tenantIdentifier) + ->find() + ->toArray(); + + $this->assertSame(1, count($appConfigEntities), sprintf( + 'Expected to have one persisted configurations for tenant identifier "%s" but found "%d".', + $tenantIdentifier, + count($appConfigEntities), + )); + + $this->assertSame($expectedStatus, $appConfigEntities[0]['Status'], sprintf( + 'Expected to have the configuration for tenant identifier "%s" in status "%s" but it is in "%s".', + $tenantIdentifier, + $expectedStatus, + $appConfigEntities[0]['Status'], + )); + } + /** * @param string $tenantIdentifier * @param array $expectedConfig