diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 0000000..be4561e --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1,3 @@ +coverage_clover: tests/_output/coverage.xml +json_path: tests/_output/coveralls-upload.json +service_name: travis-ci diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3191e88 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +; This file is for unifying the coding style for different editors and IDEs. +; More information at https://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.bat] +end_of_line = crlf + diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..307be39 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,36 @@ +# Define the line ending behavior of the different file extensions +# Set the default behavior, in case people don't have core.autocrlf set. +* text text=auto eol=lf + +*.php diff=php + +# Denote all files that are truly binary and should not be modified. +*.png binary +*.jpg binary +*.gif binary +*.jpeg binary +*.zip binary +*.phar binary +*.ttf binary +*.woff binary +*.woff2 binary +*.eot binary +*.ico binary +*.mo binary +*.pdf binary +*.xsd binary +*.exe binary + +# Remove files for archives generated using `git archive` +dependency.json export-ignore +phpstan.json export-ignore +phpstan.neon export-ignore +psalm-report.json export-ignore linguist-generated=true +tooling.yml export-ignore +.coveralls.yml export-ignore +.travis.yml export-ignore +.editorconfig export-ignore +.gitattributes export-ignore +.gitignore export-ignore +architecture-baseline.json export-ignore +.github/ export-ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e851193 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +name: CI + +on: + push: + branches: + - 'master' + pull_request: + workflow_dispatch: + +jobs: + validation: + name: Validation + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + extensions: mbstring, intl, bcmath + coverage: none + + - name: Composer Install + run: composer install --prefer-dist --no-interaction --profile + + - name: Run validation + run: composer validate + + - name: Syntax check + run: find ./src -path src -prune -o -type f -name '*.php' -print0 | xargs -0 -n1 -P4 php -l -n | (! grep -v "No syntax errors detected" ) + + lowest: + name: Prefer Lowest + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + extensions: mbstring, intl, bcmath + coverage: none + + - name: Composer Install + run: composer install --prefer-dist --no-interaction --profile + + - name: Composer Update + run: composer update --prefer-lowest --prefer-dist --no-interaction --profile -vvv diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..58b3e11 --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +# IDE +.idea/ +.project/ +nbproject/ +.buildpath/ +.settings/ +*.sublime-* + +# OS +.DS_Store +*.AppleDouble +*.AppleDB +*.AppleDesktop + +# grunt stuff +.grunt +.sass-cache +/node_modules/ + +# tooling +vendor/ +composer.lock +.phpunit.result.cache + +# built client resources +src/*/Zed/*/Static/Public +src/*/Zed/*/Static/Assets/sprite + +# Propel classes +src/*/Zed/*/Persistence/Propel/Base/* +src/*/Zed/*/Persistence/Propel/Map/* + +# tests +tests/**/_generated/ +tests/_output/* +!tests/_output/.gitkeep diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100644 index 0000000..802f863 --- /dev/null +++ b/.scrutinizer.yml @@ -0,0 +1,5 @@ +tools: + external_code_coverage: true +checks: + php: + code_rating: true diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e68b348 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +# KernelApp Changelog + +[Release Changelog](https://github.com/spryker/kernel-app/releases) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5eff362 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,46 @@ +CODE CONTRIBUTION LICENSE AGREEMENT + +BY DISPLAYING, PUBLISHING, UPLOADING OR PROMOTING (COLLECTIVELY, “POSTING”) SOURCE CODE (“SOURCE CODE”) +TO SPRYKER SYSTEMS GMBH, REGISTERED WITH THE COMMERCIAL REGISTER OF THE LOWER COURT OF HAMBURG UNDER +HRB 134310 (“WE”, “US” OR ”SPRYKER”), YOU AGREE TO THIS CODE CONTRIBUTION LICENSE AGREEMENT (THE “AGREEMENT”). + +You grant us and our affiliates an irrevocable, perpetual, worldwide, royalty-free, non-exclusive, unrestricted +license and right to use, reproduce and store, disseminate and otherwise exploit, modify, delete from, add to, +create derivative works of, publicly perform, publicly display, reproduce, exchange parts of Source Code or combine them with +other Source Code, use in data networks and distribute with or without consideration and without limitations as to the +number of items via all distribution channels (and to sublicense the foregoing rights through multiple tiers of licensees) +of such Source Code and any other copyright protected material for any reason and in connection with advertising and +promoting our software and/or our products in any media formats and through any channels now existing or developed in +the future. The transfer and assignment of rights covers any usage and exploitation rights for any unknown types of use +as well as with regard to any known types of use the right to unrestrictedly make publicly available and publish, +irrespective of the medium including any editions and versions and grant simple or exclusive usage, exploitation or +adaptation rights to third parties. + +Spryker may reject, refuse to post or delete any Source Code for any or no reason, including, without limitation. + +From time to time, we may remove the Source Code permanently or temporarily, provided that even if we do remove such +Source Code, we shall have no obligation to cease our other uses of the Source Code as permitted above. + +You agree to be fully responsible for and to pay any and all royalties, fees, and any other monies owing any person or +entity by reason of any Source Code posted by you. + +Spryker respects the intellectual property of others, and requires that you do the same. Your postings and the Source Code +must not infringe any copyright, patent, trademark, trade secret or other proprietary rights or other rights of any person +or entity and you may not upload, embed, post, email, transmit or otherwise make available Source Code, software or any other +material that that infringes such rights. + +YOU GUARANTEE THAT: (I) YOU OWN THE SOURCE CODE POSTED BY YOU OR OTHERWISE HAVE THE RIGHT TO GRANT THE LICENSES AND RIGHTS +SET FORTH ABOVE, AND (II) THE POSTING OF YOUR SOURCE CODE DOES NOT VIOLATE THE PRIVACY RIGHTS, PUBLICITY RIGHTS, CONTRACT RIGHTS, +INTELLECTUAL PROPERTY OR ANY OTHER RIGHTS OF ANY PERSON OR ENTITY OR ANY APPLICABLE LAW. + +YOU AGREE TO INDEMNIFY AND HOLD SPRYKER, ITS SUBSIDIARIES, AND AFFILIATES, AND THEIR RESPECTIVE OFFICERS, AGENTS, PARTNERS +AND EMPLOYEES, HARMLESS FROM ANY LOSS, LIABILITY, COST, EXPENSE, CLAIM OR DEMAND, INCLUDING WITHOUT LIMITATION, REASONABLE +ATTORNEYS’ FEES, DUE OR RELATING TO OR ARISING OUT OF THE USE OF YOUR SOURCE CODE IN VIOLATION OF THIS AGREEMENT AND/OR +ARISING FROM A BREACH OF ANY TERMS OF THIS AGREEMENT AND/OR ANY BREACH OF YOUR REPRESENTATIONS AND WARRANTIES SET FORTH IN +THIS AGREEMENT AND/OR ARISING OUT OF OR RELATING TO ANY SOURCE CODE THAT YOU POST. + +This Agreement shall be governed by the laws of Germany to the exclusion of IPR (International Law) and the United Nations Convention +on Contracts for the International Sale of Goods (CISG). The parties consent to the jurisdiction of the courts in Berlin (Germany). + +This Agreement constitutes the entire agreement between you and us concerning Spryker’s use of the Source Code. This Agreement +supersedes any prior verbal understanding between the parties. This Agreement may be amended only in a writing signed by an authorized officer of Spryker. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fc1838e --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +SPRYKER - LICENSE AGREEMENT FOR EVALUATION OF AND EARLY ACCESS TO SOFTWARE + +SPRYKER SYSTEMS GMBH, REGISTERED WITH THE COMMERCIAL REGISTER OF THE LOWER COURT OF HAMBURG UNDER HRB 134310 ("WE" OR "SPRYKER") GRANTS YOU ("LICENSEE") THE RIGHT TO USE THE SOFTWARE (AS DEFINED BELOW) UNDER THE PROVISIONS OF THIS LICENSE AGREEMENT FOR EVALUATION AND EARLY ACCESS (THE "AGREEMENT"). + +For the purposes of this Agreement, the "Software" includes any software, which is owned and made available (i) by Spryker for evaluation purposes or (ii) as part of Spryker`s early access program under this Agreement. + +Spryker grants to Licensee, (i) during the 45-calendar-day period (the "Evaluation Period") following the first download of the Software or (ii) during the period it makes available a feature, module or functionality as part of its early access program (the "Early Access Period"), the non-transferable, non-exclusive limited, free of charge license (for (i) the "Evaluation License" or for (ii) the "Early Access License", as applicable, both a "License") to permit Licensee and/or Licensee`s employees to internally use the Software to test and evaluate the Software. In case of the Evaluation License, Licensee will not be granted the license under this Agreement for one particular piece of the Software more than once. + +Under the License granted in this Agreement, Licensee shall not (i) use the Software to set up a production live system, for development purposes or any other purposes apart from evaluating and testing the Software; (ii) copy any part of the Software except to make one copy for back-up purposes; (iii) distribute, disclose, market, rent, lease, or transfer the Software or act as a service bureau with respect to the Software; (iv) export the Software or install it in multiple locations; (v) disclose any confidential information provided by Spryker; (vi) modify or make derivative works of the Software; or (vii) allow others to make or obtain copies of the Software. + +The use of the Software in a production environment, to the extent permitted at all, requires prior conclusion of a separate agreement between Spryker and Licensee. Spryker will not support any versions of the Software being provided for evaluation purposes or as part of Spryker`s early access program. The provision of the Software as part of Spryker`s early access program does not necessarily mean it will be released as part of Spryker`s products in the future. + +The Software may contain elements of open source components, to which different license terms apply respectively. Licensee may need to install these open source components separately. + +THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND. SPRYKER DISCLAIMS ALL WARRANTIES, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, TITLE, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE. SPRYKER WILL NOT BE LIABLE FOR ANY DAMAGES ASSOCIATED WITH THE SOFTWARE, INCLUDING WITHOUT LIMITATION ORDINARY, INCIDENTAL, INDIRECT, OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING BUT NOT LIMITED TO DAMAGES RELATING TO LOST DATA OR LOST PROFITS, EVEN IF SPRYKER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +Licensee can provide input, suggestions, suggest or recommend changes or enhancements of the Software ("Feedback"). Any Feedback provided by Licensee is not considered confidential information and is received and treated by Spryker on a non-confidential and unrestricted basis. Licensee grants Spryker a sub-licenseable right to use, distribute, disclose and/or incorporate it into the Software or other products of Spryker, free of charge and without restriction as to time and place. + +Licensee`s License to use the Software under this Agreement shall terminate on the earlier of (i) the expiration of the Evaluation Period or the Early Access Period (as applicable), or (ii) the date both parties enter into a definitive agreement for the provision by Spryker to Licensee of a full non-evaluation license to Spryker`s software. + +Upon termination of the License as provided under this Agreement, Licensee shall promptly destroy the Software and any back-up copy of the Software made during the Evaluation Period or Early Access Period if Spryker and Licensee have not agreed a non-evaluation license to Spryker`s software. + +This Agreement shall be governed by the laws of Germany to the exclusion of IPR (International Law) and the United Nations Convention on Contracts for the International Sale of Goods (CISG). The parties consent to the jurisdiction of the courts in Berlin (Germany). + +This Agreement and/or the License is not assignable or transferable by Licensee and any attempt to do so is null and void. + +This Agreement constitutes the entire agreement between the parties concerning Licensee`s use of the Software. This Agreement supersedes any prior verbal understanding between the parties. This Agreement may be amended only in a writing signed by an authorized officer of Spryker. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1fb78ac --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# KernelApp Module +[![Latest Stable Version](https://poser.pugx.org/spryker/kernel-app/v/stable.svg)](https://packagist.org/packages/spryker/kernel-app) +[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%208.1-8892BF.svg)](https://php.net/) + +KernelApp is the basic module for all other modules that are used with ACP Apps, it's the extended heart of Spryker. It provides a communication layer between SCOS and Apps. + +## Installation + +``` +composer require spryker/kernel-app +``` + +## Documentation + +[Spryker Documentation](https://docs.spryker.com) diff --git a/architecture-baseline.json b/architecture-baseline.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/architecture-baseline.json @@ -0,0 +1 @@ +[] diff --git a/codeception.yml b/codeception.yml new file mode 100644 index 0000000..38546da --- /dev/null +++ b/codeception.yml @@ -0,0 +1,19 @@ +namespace: SprykerTest + +include: + - tests/SprykerTest/AsyncApi/KernelApp/ + - tests/SprykerTest/Client/ + - tests/SprykerTest/Zed/KernelApp/ + +paths: + tests: tests + support: . + output: tests/_output + data: tests/_data + envs: tests/_envs + +settings: + suite_class: \PHPUnit\Framework\TestSuite + colors: true + memory_limit: 1024M + log: true diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..1a8b069 --- /dev/null +++ b/composer.json @@ -0,0 +1,53 @@ +{ + "name": "spryker/kernel-app", + "type": "library", + "description": "KernelApp module", + "license": "proprietary", + "require": { + "php": ">=8.1", + "spryker/guzzle": "^2.0.0", + "spryker/kernel": "^3.30.0", + "spryker/message-broker-extension": "^1.0.0", + "spryker/symfony": "^3.17.0", + "spryker/transfer": "^3.27.0", + "spryker/util-encoding": "^2.0.0" + }, + "require-dev": { + "psr/http-message": "^1.1 || ^2.0", + "spryker/code-sniffer": "*", + "spryker/container": "*", + "spryker/kernel-app-extension": "*", + "spryker/message-broker": "*", + "spryker/propel": "*", + "spryker/ramsey-uuid": "*", + "spryker/testify": "*" + }, + "autoload": { + "psr-4": { + "Spryker\\": "src/Spryker/", + "SprykerTest\\Shared\\KernelApp\\Helper\\": "tests/SprykerTest/Shared/KernelApp/_support/Helper/" + } + }, + "autoload-dev": { + "psr-4": { + "SprykerTest\\": "tests/SprykerTest/" + } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "scripts": { + "cs-check": "phpcs -p -s --standard=vendor/spryker/code-sniffer/Spryker/ruleset.xml src/ tests/", + "cs-fix": "phpcbf -p --standard=vendor/spryker/code-sniffer/Spryker/ruleset.xml src/ tests/" + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "config": { + "sort-packages": true, + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} diff --git a/dependency.json b/dependency.json new file mode 100644 index 0000000..1932e90 --- /dev/null +++ b/dependency.json @@ -0,0 +1,5 @@ +{ + "include": { + "spryker/transfer": "Provides transfer objects definition with strict types." + } +} diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..f2cf3bc --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,9 @@ +parameters: + level: 8 + checkMissingIterableValueType: false + reportUnmatchedIgnoredErrors: false + dynamicConstantNames: + - APPLICATION + - APPLICATION_ENV + - APPLICATION_STORE + - APPLICATION_CODE_BUCKET diff --git a/psalm-report.json b/psalm-report.json new file mode 100644 index 0000000..6bcd29c --- /dev/null +++ b/psalm-report.json @@ -0,0 +1,6 @@ +{ + "error": [], + "warning": [], + "deprecation": [], + "count": 0 +} diff --git a/resources/api/asyncapi.yml b/resources/api/asyncapi.yml new file mode 100644 index 0000000..4a64607 --- /dev/null +++ b/resources/api/asyncapi.yml @@ -0,0 +1,43 @@ +asyncapi: 2.6.0 +info: + title: 'PBC AsyncAPI definition.' + version: 0.1.0 +channels: + app-events: + publish: + message: + oneOf: + - $ref: '#/components/messages/AppConfigUpdated' +components: + messages: + AppConfigUpdated: + x-spryker: + module: AppConfig + name: AppConfigUpdated + title: Contains information about an updated App configuration. + summary: 'Contains information about an updated App configuration.' + payload: + $ref: '#/components/schemas/AppConfigUpdated' + headers: + $ref: '#/components/schemas/message-broker/components/schemas/headers' + schemas: + AppConfigUpdated: + type: object + properties: + appIdentifier: + type: string + description: The app identifier to identify the PBC. + status: + type: boolean + description: The status of PBC (new, connected). + isActive: + type: boolean + config: + type: array + required: + - appIdentifier + - status + - isActive + - config + message-broker: + $ref: 'https://raw.githubusercontent.com/spryker/message-broker/1.6.0/resources/api/template.yml' diff --git a/src/Spryker/Client/KernelApp/Dependency/External/KernelAppToGuzzleHttpClientAdapter.php b/src/Spryker/Client/KernelApp/Dependency/External/KernelAppToGuzzleHttpClientAdapter.php new file mode 100644 index 0000000..3962451 --- /dev/null +++ b/src/Spryker/Client/KernelApp/Dependency/External/KernelAppToGuzzleHttpClientAdapter.php @@ -0,0 +1,55 @@ +httpClient = $httpClient; + } + + /** + * @param \Generated\Shared\Transfer\AcpHttpRequestTransfer $acpHttpRequestTransfer + * + * @return \Generated\Shared\Transfer\AcpHttpResponseTransfer + */ + public function send(AcpHttpRequestTransfer $acpHttpRequestTransfer): AcpHttpResponseTransfer + { + $request = new Request( + $acpHttpRequestTransfer->getMethodOrFail(), + $acpHttpRequestTransfer->getUriOrFail(), + $acpHttpRequestTransfer->getHeaders(), + $acpHttpRequestTransfer->getBodyOrFail(), + ); + + $response = $this->httpClient->send($request); + + $responseBody = $response->getBody()->getContents(); + + $acpHttpResponseTransfer = new AcpHttpResponseTransfer(); + $acpHttpResponseTransfer + ->setHttpStatusCode($response->getStatusCode()) + ->setContent($responseBody); + + return $acpHttpResponseTransfer; + } +} diff --git a/src/Spryker/Client/KernelApp/Dependency/External/KernelAppToHttpClientAdapterInterface.php b/src/Spryker/Client/KernelApp/Dependency/External/KernelAppToHttpClientAdapterInterface.php new file mode 100644 index 0000000..1087160 --- /dev/null +++ b/src/Spryker/Client/KernelApp/Dependency/External/KernelAppToHttpClientAdapterInterface.php @@ -0,0 +1,21 @@ +getFactory()->createRequest()->request($acpHttpRequestTransfer); + } +} diff --git a/src/Spryker/Client/KernelApp/KernelAppClientInterface.php b/src/Spryker/Client/KernelApp/KernelAppClientInterface.php new file mode 100644 index 0000000..f4fae12 --- /dev/null +++ b/src/Spryker/Client/KernelApp/KernelAppClientInterface.php @@ -0,0 +1,32 @@ +get(KernelAppConstants::TENANT_IDENTIFIER); + } +} diff --git a/src/Spryker/Client/KernelApp/KernelAppDependencyProvider.php b/src/Spryker/Client/KernelApp/KernelAppDependencyProvider.php new file mode 100644 index 0000000..e272fed --- /dev/null +++ b/src/Spryker/Client/KernelApp/KernelAppDependencyProvider.php @@ -0,0 +1,81 @@ +addHttpClient($container); + $container = $this->addRequestExpanderPlugins($container); + + return $container; + } + + /** + * @param \Spryker\Client\Kernel\Container $container + * + * @return \Spryker\Client\Kernel\Container + */ + protected function addHttpClient(Container $container): Container + { + $container->set(static::CLIENT_HTTP, function () { + // @codeCoverageIgnoreStart + // The GuzzleClient has to be mocked for testing, no coverage possible. + return new KernelAppToGuzzleHttpClientAdapter(new GuzzleHttpClient()); + // @codeCoverageIgnoreEnd + }); + + return $container; + } + + /** + * @param \Spryker\Client\Kernel\Container $container + * + * @return \Spryker\Client\Kernel\Container + */ + protected function addRequestExpanderPlugins(Container $container): Container + { + $container->set(static::REQUEST_EXPANDER_PLUGINS, function () { + return $this->getRequestExpanderPlugins(); + }); + + return $container; + } + + /** + * @return array<\Spryker\Shared\KernelAppExtension\RequestExpanderPluginInterface> + */ + public function getRequestExpanderPlugins(): array + { + return []; + } +} diff --git a/src/Spryker/Client/KernelApp/KernelAppFactory.php b/src/Spryker/Client/KernelApp/KernelAppFactory.php new file mode 100644 index 0000000..0187efd --- /dev/null +++ b/src/Spryker/Client/KernelApp/KernelAppFactory.php @@ -0,0 +1,47 @@ +getConfig(), + $this->getHttpClient(), + $this->getRequestExpanderPlugins(), + ); + } + + /** + * @return array<\Spryker\Shared\KernelAppExtension\RequestExpanderPluginInterface> + */ + public function getRequestExpanderPlugins(): array + { + return $this->getProvidedDependency(KernelAppDependencyProvider::REQUEST_EXPANDER_PLUGINS); + } + + /** + * @return \Spryker\Client\KernelApp\Dependency\External\KernelAppToHttpClientAdapterInterface + */ + public function getHttpClient(): KernelAppToHttpClientAdapterInterface + { + return $this->getProvidedDependency(KernelAppDependencyProvider::CLIENT_HTTP); + } +} diff --git a/src/Spryker/Client/KernelApp/Request/Request.php b/src/Spryker/Client/KernelApp/Request/Request.php new file mode 100644 index 0000000..f95f545 --- /dev/null +++ b/src/Spryker/Client/KernelApp/Request/Request.php @@ -0,0 +1,87 @@ + + */ + protected array $requestExpanderPlugins; + + protected KernelAppConfig $config; + + /** + * @var \Spryker\Client\KernelApp\Dependency\External\KernelAppToHttpClientAdapterInterface + */ + protected KernelAppToHttpClientAdapterInterface $httpClient; + + /** + * @param \Spryker\Client\KernelApp\KernelAppConfig $config + * @param \Spryker\Client\KernelApp\Dependency\External\KernelAppToHttpClientAdapterInterface $httpClient + * @param array<\Spryker\Shared\KernelAppExtension\RequestExpanderPluginInterface> $acpRequestExpanderPlugins + */ + public function __construct(KernelAppConfig $config, KernelAppToHttpClientAdapterInterface $httpClient, array $acpRequestExpanderPlugins) + { + $this->config = $config; + $this->requestExpanderPlugins = $acpRequestExpanderPlugins; + $this->httpClient = $httpClient; + } + + /** + * @param \Generated\Shared\Transfer\AcpHttpRequestTransfer $acpHttpRequestTransfer + * + * @return \Generated\Shared\Transfer\AcpHttpResponseTransfer + */ + public function request(AcpHttpRequestTransfer $acpHttpRequestTransfer): AcpHttpResponseTransfer + { + $acpHttpRequestTransfer = $this->executeRequestExpanderPlugins($acpHttpRequestTransfer); + $acpHttpRequestTransfer = $this->expandRequest($acpHttpRequestTransfer); + + return $this->httpClient->send($acpHttpRequestTransfer); + } + + /** + * @param \Generated\Shared\Transfer\AcpHttpRequestTransfer $acpHttpRequestTransfer + * + * @return \Generated\Shared\Transfer\AcpHttpRequestTransfer + */ + protected function executeRequestExpanderPlugins(AcpHttpRequestTransfer $acpHttpRequestTransfer): AcpHttpRequestTransfer + { + foreach ($this->requestExpanderPlugins as $acpRequestExpanderPlugin) { + $acpHttpRequestTransfer = $acpRequestExpanderPlugin->expandRequest($acpHttpRequestTransfer); + } + + return $acpHttpRequestTransfer; + } + + /** + * @param \Generated\Shared\Transfer\AcpHttpRequestTransfer $acpHttpRequestTransfer + * + * @return \Generated\Shared\Transfer\AcpHttpRequestTransfer + */ + protected function expandRequest(AcpHttpRequestTransfer $acpHttpRequestTransfer): AcpHttpRequestTransfer + { + return $this->addTenantIdentifier($acpHttpRequestTransfer); + } + + /** + * @param \Generated\Shared\Transfer\AcpHttpRequestTransfer $acpHttpRequestTransfer + * + * @return \Generated\Shared\Transfer\AcpHttpRequestTransfer + */ + protected function addTenantIdentifier(AcpHttpRequestTransfer $acpHttpRequestTransfer): AcpHttpRequestTransfer + { + return $acpHttpRequestTransfer->addHeader('x-tenant-identifier', $this->config->getTenantIdentifier()); + } +} diff --git a/src/Spryker/Client/KernelApp/Request/RequestInterface.php b/src/Spryker/Client/KernelApp/Request/RequestInterface.php new file mode 100644 index 0000000..0e2ed71 --- /dev/null +++ b/src/Spryker/Client/KernelApp/Request/RequestInterface.php @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Spryker/Zed/KernelApp/Business/KernelAppBusinessFactory.php b/src/Spryker/Zed/KernelApp/Business/KernelAppBusinessFactory.php new file mode 100644 index 0000000..75cec68 --- /dev/null +++ b/src/Spryker/Zed/KernelApp/Business/KernelAppBusinessFactory.php @@ -0,0 +1,48 @@ +getConfig(), + $this->getKernelAppClient(), + $this->getRequestExpanderPlugins(), + ); + } + + /** + * @return \Spryker\Client\KernelApp\KernelAppClientInterface + */ + public function getKernelAppClient(): KernelAppClientInterface + { + return $this->getProvidedDependency(KernelAppDependencyProvider::CLIENT_KERNEL_APP); + } + + /** + * @return array<\Spryker\Shared\KernelAppExtension\RequestExpanderPluginInterface> + */ + public function getRequestExpanderPlugins(): array + { + return $this->getProvidedDependency(KernelAppDependencyProvider::REQUEST_EXPANDER_PLUGINS); + } +} diff --git a/src/Spryker/Zed/KernelApp/Business/KernelAppFacade.php b/src/Spryker/Zed/KernelApp/Business/KernelAppFacade.php new file mode 100644 index 0000000..3f90555 --- /dev/null +++ b/src/Spryker/Zed/KernelApp/Business/KernelAppFacade.php @@ -0,0 +1,48 @@ +getFactory()->createRequest()->request($acpHttpRequestTransfer); + } + + /** + * {@inheritDoc} + * + * @api + * + * @param \Generated\Shared\Transfer\AppConfigTransfer $appConfigTransfer + * + * @return void + */ + public function writeAppConfig(AppConfigTransfer $appConfigTransfer): void + { + $this->getEntityManager()->writeAppConfig($appConfigTransfer); + } +} diff --git a/src/Spryker/Zed/KernelApp/Business/KernelAppFacadeInterface.php b/src/Spryker/Zed/KernelApp/Business/KernelAppFacadeInterface.php new file mode 100644 index 0000000..2cfc6e7 --- /dev/null +++ b/src/Spryker/Zed/KernelApp/Business/KernelAppFacadeInterface.php @@ -0,0 +1,46 @@ + + */ + protected array $requestExpanderPlugins; + + /** + * @param \Spryker\Zed\KernelApp\KernelAppConfig $kernelAppConfig + * @param \Spryker\Client\KernelApp\KernelAppClientInterface $kernelAppClient + * @param array<\Spryker\Shared\KernelAppExtension\RequestExpanderPluginInterface> $requestExpanderPlugins + */ + public function __construct( + KernelAppConfig $kernelAppConfig, + KernelAppClientInterface $kernelAppClient, + array $requestExpanderPlugins + ) { + $this->kernelAppConfig = $kernelAppConfig; + $this->kernelAppClient = $kernelAppClient; + $this->requestExpanderPlugins = $requestExpanderPlugins; + } + + /** + * @param \Generated\Shared\Transfer\AcpHttpRequestTransfer $acpHttpRequestTransfer + * + * @return \Generated\Shared\Transfer\AcpHttpResponseTransfer + */ + public function request(AcpHttpRequestTransfer $acpHttpRequestTransfer): AcpHttpResponseTransfer + { + $acpHttpRequestTransfer = $this->executeRequestExpanderPlugins($acpHttpRequestTransfer); + + $internalRequest = SymfonyRequest::createFromGlobals(); + + if ($internalRequest->cookies->has('XDEBUG_SESSION')) { + $acpHttpRequestTransfer->addHeader('Cookie', sprintf('XDEBUG_SESSION=%s', $internalRequest->cookies->get('XDEBUG_SESSION'))); + } + + $acpHttpRequestTransfer = $this->expandWithDefaultHeaders($acpHttpRequestTransfer); + + return $this->kernelAppClient->request($acpHttpRequestTransfer); + } + + /** + * @param \Generated\Shared\Transfer\AcpHttpRequestTransfer $acpHttpRequestTransfer + * + * @return \Generated\Shared\Transfer\AcpHttpRequestTransfer + */ + protected function executeRequestExpanderPlugins( + AcpHttpRequestTransfer $acpHttpRequestTransfer + ): AcpHttpRequestTransfer { + foreach ($this->requestExpanderPlugins as $acpRequestExpanderPlugin) { + $acpHttpRequestTransfer = $acpRequestExpanderPlugin->expandRequest($acpHttpRequestTransfer); + } + + return $acpHttpRequestTransfer; + } + + /** + * @param \Generated\Shared\Transfer\AcpHttpRequestTransfer $acpHttpRequestTransfer + * + * @return \Generated\Shared\Transfer\AcpHttpRequestTransfer + */ + public function expandWithDefaultHeaders(AcpHttpRequestTransfer $acpHttpRequestTransfer): AcpHttpRequestTransfer + { + foreach ($this->kernelAppConfig->getDefaultHeaders() as $headerName => $headerValue) { + if (isset($acpHttpRequestTransfer->getHeaders()[$headerName])) { + continue; + } + + $acpHttpRequestTransfer->addHeader($headerName, $headerValue); + } + + return $acpHttpRequestTransfer; + } +} diff --git a/src/Spryker/Zed/KernelApp/Business/Request/RequestInterface.php b/src/Spryker/Zed/KernelApp/Business/Request/RequestInterface.php new file mode 100644 index 0000000..09f55a3 --- /dev/null +++ b/src/Spryker/Zed/KernelApp/Business/Request/RequestInterface.php @@ -0,0 +1,21 @@ +fromArray($appConfigChangedTransfer->toArray(), true); + + $this->getFacade()->writeAppConfig($appConfigTransfer); + } + + /** + * {@inheritDoc} + * + * @api + * + * @return iterable + */ + public function handles(): iterable + { + return [ + AppConfigUpdatedTransfer::class => [$this, 'onAppConfigUpdated'], + ]; + } +} diff --git a/src/Spryker/Zed/KernelApp/Dependency/Service/KernelAppToUtilEncodingServiceBridge.php b/src/Spryker/Zed/KernelApp/Dependency/Service/KernelAppToUtilEncodingServiceBridge.php new file mode 100644 index 0000000..cfd170d --- /dev/null +++ b/src/Spryker/Zed/KernelApp/Dependency/Service/KernelAppToUtilEncodingServiceBridge.php @@ -0,0 +1,36 @@ +utilEncodingService = $utilEncodingService; + } + + /** + * @param array $value + * @param int|null $options + * @param int|null $depth + * + * @return string|null + */ + public function encodeJson(array $value, ?int $options = null, ?int $depth = null): ?string + { + return $this->utilEncodingService->encodeJson($value, $options, $depth); + } +} diff --git a/src/Spryker/Zed/KernelApp/Dependency/Service/KernelAppToUtilEncodingServiceInterface.php b/src/Spryker/Zed/KernelApp/Dependency/Service/KernelAppToUtilEncodingServiceInterface.php new file mode 100644 index 0000000..fd029c9 --- /dev/null +++ b/src/Spryker/Zed/KernelApp/Dependency/Service/KernelAppToUtilEncodingServiceInterface.php @@ -0,0 +1,20 @@ + $value + * @param int|null $options + * @param int|null $depth + * + * @return string|null + */ + public function encodeJson(array $value, ?int $options = null, ?int $depth = null): ?string; +} diff --git a/src/Spryker/Zed/KernelApp/KernelAppConfig.php b/src/Spryker/Zed/KernelApp/KernelAppConfig.php new file mode 100644 index 0000000..3fbd1c9 --- /dev/null +++ b/src/Spryker/Zed/KernelApp/KernelAppConfig.php @@ -0,0 +1,42 @@ + + */ + public function getDefaultHeaders(): array + { + return [ + static::CONTENT_TYPE_HEADER => static::CONTENT_TYPE_HEADER_VALUE, + ]; + } +} diff --git a/src/Spryker/Zed/KernelApp/KernelAppDependencyProvider.php b/src/Spryker/Zed/KernelApp/KernelAppDependencyProvider.php new file mode 100644 index 0000000..596cd0c --- /dev/null +++ b/src/Spryker/Zed/KernelApp/KernelAppDependencyProvider.php @@ -0,0 +1,112 @@ +addKernelAppClient($container); + $container = $this->addRequestExpanderPlugins($container); + + return $container; + } + + /** + * @param \Spryker\Zed\Kernel\Container $container + * + * @return \Spryker\Zed\Kernel\Container + */ + public function providePersistenceLayerDependencies(Container $container): Container + { + $container = parent::providePersistenceLayerDependencies($container); + $container = $this->addUtilEncodingService($container); + + return $container; + } + + /** + * @param \Spryker\Zed\Kernel\Container $container + * + * @return \Spryker\Zed\Kernel\Container + */ + public function addKernelAppClient(Container $container): Container + { + $container[static::CLIENT_KERNEL_APP] = function (Container $container): KernelAppClientInterface { + return $container->getLocator()->kernelApp()->client(); + }; + + return $container; + } + + /** + * @param \Spryker\Zed\Kernel\Container $container + * + * @return \Spryker\Zed\Kernel\Container + */ + protected function addRequestExpanderPlugins(Container $container): Container + { + $container->set(static::REQUEST_EXPANDER_PLUGINS, function () { + return $this->getRequestExpanderPlugins(); + }); + + return $container; + } + + /** + * @return array<\Spryker\Shared\KernelAppExtension\RequestExpanderPluginInterface> + */ + public function getRequestExpanderPlugins(): array + { + return []; + } + + /** + * @param \Spryker\Zed\Kernel\Container $container + * + * @return \Spryker\Zed\Kernel\Container + */ + protected function addUtilEncodingService(Container $container): Container + { + $container->set(static::SERVICE_UTIL_ENCODING, function (Container $container): KernelAppToUtilEncodingServiceInterface { + return new KernelAppToUtilEncodingServiceBridge($container->getLocator()->utilEncoding()->service()); + }); + + return $container; + } +} diff --git a/src/Spryker/Zed/KernelApp/Persistence/KernelAppEntityManager.php b/src/Spryker/Zed/KernelApp/Persistence/KernelAppEntityManager.php new file mode 100644 index 0000000..d4242fc --- /dev/null +++ b/src/Spryker/Zed/KernelApp/Persistence/KernelAppEntityManager.php @@ -0,0 +1,36 @@ +getAppIdentifierOrFail(); + $appConfigTransfer->getStatusOrFail(); + + $appConfigData = $appConfigTransfer->modifiedToArray(); + $appConfigData[AppConfigTransfer::CONFIG] = $this->getFactory()->getUtilEncodingService()->encodeJson($appConfigData[AppConfigTransfer::CONFIG]); + + $appConfigPropelQuery = $this->getFactory()->createAppConfigPropelQuery(); + $appConfigEntity = $appConfigPropelQuery->filterByAppIdentifier($appConfigTransfer->getAppIdentifier())->findOneOrCreate(); + $appConfigEntity->fromArray($appConfigData); + $appConfigEntity->save(); + } +} diff --git a/src/Spryker/Zed/KernelApp/Persistence/KernelAppEntityManagerInterface.php b/src/Spryker/Zed/KernelApp/Persistence/KernelAppEntityManagerInterface.php new file mode 100644 index 0000000..2c32756 --- /dev/null +++ b/src/Spryker/Zed/KernelApp/Persistence/KernelAppEntityManagerInterface.php @@ -0,0 +1,20 @@ +getProvidedDependency(KernelAppDependencyProvider::SERVICE_UTIL_ENCODING); + } +} diff --git a/src/Spryker/Zed/KernelApp/Persistence/Propel/AbstractSpyAppConfig.php b/src/Spryker/Zed/KernelApp/Persistence/Propel/AbstractSpyAppConfig.php new file mode 100644 index 0000000..851c982 --- /dev/null +++ b/src/Spryker/Zed/KernelApp/Persistence/Propel/AbstractSpyAppConfig.php @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + +
+ +
diff --git a/tests/SprykerTest/AsyncApi/KernelApp/KernelAppTests/AppEvents/AppConfigUpdatedTest.php b/tests/SprykerTest/AsyncApi/KernelApp/KernelAppTests/AppEvents/AppConfigUpdatedTest.php new file mode 100644 index 0000000..177f065 --- /dev/null +++ b/tests/SprykerTest/AsyncApi/KernelApp/KernelAppTests/AppEvents/AppConfigUpdatedTest.php @@ -0,0 +1,67 @@ +toString(); + + $appConfigTransfer = $this->tester->haveAppConfigPersisted([ + AppConfigUpdatedTransfer::APP_IDENTIFIER => $appIdentifier, + AppConfigTransfer::IS_ACTIVE => false, + AppConfigTransfer::CONFIG => ['foo' => 'bar'], + AppConfigTransfer::STATUS => 'NEW', + ]); + + $appConfigTransfer->setIsActive(true); + + $appConfigUpdatedTransfer = $this->tester->haveAppConfigUpdatedTransfer([ + AppConfigUpdatedTransfer::APP_IDENTIFIER => $appIdentifier, + AppConfigTransfer::IS_ACTIVE => true, + AppConfigTransfer::CONFIG => ['foo' => 'bar'], + AppConfigTransfer::STATUS => 'NEW', + ]); + + // Act + $this->tester->runMessageReceiveTest($appConfigUpdatedTransfer, 'app-events'); + + // Assert + $this->tester->assertAppConfigIsPersisted($appIdentifier, $appConfigTransfer); + } +} diff --git a/tests/SprykerTest/AsyncApi/KernelApp/_support/KernelAppAsyncApiTester.php b/tests/SprykerTest/AsyncApi/KernelApp/_support/KernelAppAsyncApiTester.php new file mode 100644 index 0000000..ba58dfe --- /dev/null +++ b/tests/SprykerTest/AsyncApi/KernelApp/_support/KernelAppAsyncApiTester.php @@ -0,0 +1,33 @@ +toString(); + + $this->tester->mockEnvironmentConfig(KernelAppConstants::TENANT_IDENTIFIER, 'tenant-identifier'); + + // Mock GuzzleClient to be able to introspect the RequestInterface and manipulate the returned response + $clientMock = Stub::make(Client::class, [ + 'send' => function (RequestInterface $request) use ($expectedXMas): ResponseInterface { + $this->assertTrue($request->hasHeader('x-mas')); + $xMas = $request->getHeader('x-mas')[0]; + $this->assertSame($expectedXMas, $xMas); + + return Stub::makeEmpty(ResponseInterface::class, [ + 'getBody' => function () { + $streamMock = Stub::makeEmpty(StreamInterface::class, [ + 'getContents' => function () { + return '{"foo": "bar"}'; + }, + ]); + + return $streamMock; + }, + 'getStatusCode' => 200, + ]); + }, + ]); + + $this->tester->setDependency(KernelAppDependencyProvider::CLIENT_HTTP, new KernelAppToGuzzleHttpClientAdapter($clientMock)); + $this->tester->setDependency(KernelAppDependencyProvider::REQUEST_EXPANDER_PLUGINS, [ + new class ($expectedXMas) implements RequestExpanderPluginInterface { + /** + * @var string + */ + protected string $xMas; + + /** + * @param string $xMas + */ + public function __construct(string $xMas) + { + $this->xMas = $xMas; + } + + /** + * @param \Psr\Http\Message\RequestInterface $acpHttpRequestTransfer + * + * @return \Psr\Http\Message\RequestInterface + */ + public function expandRequest(AcpHttpRequestTransfer $acpHttpRequestTransfer): AcpHttpRequestTransfer + { + return $acpHttpRequestTransfer->addHeader('x-mas', $this->xMas); + } + }, + ]); + + // Act + $acpHttpRequestTransfer = new AcpHttpRequestTransfer(); + $acpHttpRequestTransfer + ->setMethod('POST') + ->setUri('www.example.com') + ->setBody('{"foo": "bar"}'); + + $acpHttpResponseTransfer = $kernelAppClient->request($acpHttpRequestTransfer); + + // Assert + $this->assertInstanceOf(AcpHttpResponseTransfer::class, $acpHttpResponseTransfer); + } +} diff --git a/tests/SprykerTest/Client/_support/KernelAppClientTester.php b/tests/SprykerTest/Client/_support/KernelAppClientTester.php new file mode 100644 index 0000000..7450cb1 --- /dev/null +++ b/tests/SprykerTest/Client/_support/KernelAppClientTester.php @@ -0,0 +1,37 @@ +build(); + + return $appConfigUpdatedTransfer; + } + + /** + * @param array $seed + * + * @return \Generated\Shared\Transfer\AppConfigTransfer + */ + public function haveAppConfigPersisted(array $seed = []): AppConfigTransfer + { + $appConfigTransfer = (new AppConfigBuilder($seed))->build(); + $appConfigEntity = new SpyAppConfig(); + + $appConfigData = $appConfigTransfer->toArray(); + $appConfigData[AppConfigTransfer::CONFIG] = json_encode($appConfigData[AppConfigTransfer::CONFIG]); + + $appConfigEntity->fromArray($appConfigData); + $appConfigEntity->save(); + + $this->getDataCleanupHelper()->_addCleanup(function () use ($appConfigEntity): void { + $appConfigEntity->delete(); + }); + + return $appConfigTransfer; + } + + /** + * @param string $appIdentifier + * @param \Generated\Shared\Transfer\AppConfigTransfer|null $appConfigTransfer + * + * @return void + */ + public function assertAppConfigIsPersisted(string $appIdentifier, ?AppConfigTransfer $appConfigTransfer = null): void + { + $spyAppConfigQuery = SpyAppConfigQuery::create(); + $appConfigEntity = $spyAppConfigQuery->findOneByAppIdentifier($appIdentifier); + + $this->assertNotNull($appConfigEntity); + + if ($appConfigTransfer instanceof AppConfigTransfer) { + $persistedAppConfigData = $appConfigEntity->toArray(); + $expectedAppConfigData = $appConfigTransfer->modifiedToArray(); + if (isset($expectedAppConfigData[AppConfigTransfer::CONFIG])) { + $expectedAppConfigData[AppConfigTransfer::CONFIG] = json_encode($expectedAppConfigData[AppConfigTransfer::CONFIG]); + } + + foreach ($expectedAppConfigData as $key => $value) { + $this->assertSame($value, $persistedAppConfigData[$key], sprintf('Expected that "%s" is equal to "%s"', is_bool($value) ? $value ? 'true' : 'false' : $value, is_bool($persistedAppConfigData[$key]) ? $persistedAppConfigData[$key] ? 'true' : 'false' : $persistedAppConfigData[$key])); + } + } + } +} diff --git a/tests/SprykerTest/Zed/KernelApp/Business/KernelAppFacadeTest.php b/tests/SprykerTest/Zed/KernelApp/Business/KernelAppFacadeTest.php new file mode 100644 index 0000000..dece142 --- /dev/null +++ b/tests/SprykerTest/Zed/KernelApp/Business/KernelAppFacadeTest.php @@ -0,0 +1,177 @@ +tester->setConfig(KernelAppConstants::TENANT_IDENTIFIER, Uuid::uuid4()->toString()); + $kernelAppFacade = $this->tester->getFacade(); + $expectedTenantIdentifier = Uuid::uuid4()->toString(); + + $this->tester->mockEnvironmentConfig(KernelAppConstants::TENANT_IDENTIFIER, $expectedTenantIdentifier); + $this->mockGuzzleClient(function (RequestInterface $request) use ($expectedTenantIdentifier): ResponseInterface { + $this->assertTrue($request->hasHeader('x-tenant-identifier')); + $tenantIdentifier = $request->getHeader('x-tenant-identifier')[0]; + $this->assertSame($expectedTenantIdentifier, $tenantIdentifier); + + return $this->mockResponse(); + }); + + // Act + $acpHttpRequestTransfer = $this->createAcpHttpRequestTransfer(); + $acpHttpResponseTransfer = $kernelAppFacade->makeRequest($acpHttpRequestTransfer); + + // Assert + $this->assertInstanceOf(AcpHttpResponseTransfer::class, $acpHttpResponseTransfer); + } + + /** + * @return void + */ + public function testDefaultHeadersAreSetWhenNotPresentInRequest(): void + { + // Arrange + $this->tester->setConfig(KernelAppConstants::TENANT_IDENTIFIER, Uuid::uuid4()->toString()); + $kernelAppFacade = $this->tester->getFacade(); + $expectedContentType = 'application/json'; + + $this->mockGuzzleClient(function (RequestInterface $request) use ($expectedContentType): ResponseInterface { + $this->assertTrue($request->hasHeader('Content-Type')); + $contentType = $request->getHeader('Content-Type')[0]; + $this->assertSame($expectedContentType, $contentType); + + return $this->mockResponse(); + }); + + // Act + $acpHttpRequestTransfer = $this->createAcpHttpRequestTransfer(); + $acpHttpResponseTransfer = $kernelAppFacade->makeRequest($acpHttpRequestTransfer); + + // Assert + $this->assertInstanceOf(AcpHttpResponseTransfer::class, $acpHttpResponseTransfer); + } + + /** + * @return void + */ + public function testDefaultHeadersAreIgnoredWhenPresentInRequest(): void + { + // Arrange + $this->tester->setConfig(KernelAppConstants::TENANT_IDENTIFIER, Uuid::uuid4()->toString()); + $kernelAppFacade = $this->tester->getFacade(); + $expectedContentType = 'application/xml'; + + $this->mockGuzzleClient(function (RequestInterface $request) use ($expectedContentType): ResponseInterface { + $this->assertTrue($request->hasHeader('Content-Type')); + $contentType = $request->getHeader('Content-Type')[0]; + $this->assertSame($expectedContentType, $contentType); + + return $this->mockResponse(); + }); + + // Act + $acpHttpRequestTransfer = $this->createAcpHttpRequestTransfer(['Content-Type' => 'application/xml']); + $acpHttpResponseTransfer = $kernelAppFacade->makeRequest($acpHttpRequestTransfer); + + // Assert + $this->assertInstanceOf(AcpHttpResponseTransfer::class, $acpHttpResponseTransfer); + } + + /** + * @param callable $sendCallback + * + * @return void + */ + protected function mockGuzzleClient(callable $sendCallback): void + { + $clientMock = Stub::make(Client::class, ['send' => $sendCallback]); + $this->tester->setDependency(KernelAppDependencyProvider::CLIENT_HTTP, new KernelAppToGuzzleHttpClientAdapter($clientMock)); + } + + /** + * @return \Psr\Http\Message\ResponseInterface + */ + protected function mockResponse(): ResponseInterface + { + return Stub::makeEmpty(ResponseInterface::class, [ + 'getBody' => function () { + $streamMock = Stub::makeEmpty(StreamInterface::class, [ + 'getContents' => function () { + return '{"foo": "bar"}'; + }, + ]); + + return $streamMock; + }, + 'getStatusCode' => 200, + ]); + } + + /** + * @param array $headers + * @param string $method + * @param string $uri + * @param string $body + * + * @return \Generated\Shared\Transfer\AcpHttpRequestTransfer + */ + protected function createAcpHttpRequestTransfer( + array $headers = [], + string $method = 'POST', + string $uri = 'www.example.com', + string $body = '{"foo": "bar"}' + ): AcpHttpRequestTransfer { + $acpHttpRequestTransfer = new AcpHttpRequestTransfer(); + $acpHttpRequestTransfer + ->setMethod($method) + ->setUri($uri) + ->setBody($body); + + foreach ($headers as $headerName => $headerValue) { + $acpHttpRequestTransfer->addHeader($headerName, $headerValue); + } + + return $acpHttpRequestTransfer; + } +} diff --git a/tests/SprykerTest/Zed/KernelApp/_support/KernelAppBusinessTester.php b/tests/SprykerTest/Zed/KernelApp/_support/KernelAppBusinessTester.php new file mode 100644 index 0000000..780ebc1 --- /dev/null +++ b/tests/SprykerTest/Zed/KernelApp/_support/KernelAppBusinessTester.php @@ -0,0 +1,35 @@ +