From 73da6f60b98a0b2009d9c353a0b2ac427b44c7a9 Mon Sep 17 00:00:00 2001 From: Alexander Schranz Date: Thu, 10 Mar 2022 08:11:10 +0100 Subject: [PATCH] Initialize sulu messenger components (#1) --- .github/workflows/test-application.yaml | 95 ++++++++++++++++ .gitignore | 12 ++ .php-cs-fixer.dist.php | 43 +++++++ README.md | 74 +++++++++++- composer.json | 105 ++++++++++++++++++ config/services.php | 19 ++++ deptrac.yaml | 66 +++++++++++ phpstan.neon | 24 ++++ phpunit.xml.dist | 47 ++++++++ rector.php | 43 +++++++ .../HttpKernel/SuluMessengerBundle.php | 90 +++++++++++++++ .../DoctrineFlushMiddleware.php | 30 +++++ .../FlushMiddleware/EnableFlushStamp.php | 14 +++ .../UnpackExceptionMiddleware.php | 24 ++++ tests/Application/.env | 6 + tests/Application/Kernel.php | 47 ++++++++ tests/Application/bin/console | 4 + tests/Application/bin/console.php | 49 ++++++++ .../Application/config/packages/doctrine.yaml | 11 ++ .../config/packages/framework.yaml | 25 +++++ tests/Application/public/.gitkeep | 0 tests/Functional/.gitkeep | 0 tests/Traits/PrivatePropertyTrait.php | 45 ++++++++ .../DoctrineFlushMiddlewareTest.php | 76 +++++++++++++ .../UnpackExceptionMiddlewareTest.php | 84 ++++++++++++++ tests/bootstrap.php | 21 ++++ tests/generator-config.php | 11 ++ tests/phpstan/console-application.php | 13 +++ tests/phpstan/object-manager.php | 33 ++++++ translations/.gitkeep | 0 30 files changed, 1110 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/test-application.yaml create mode 100644 .gitignore create mode 100644 .php-cs-fixer.dist.php create mode 100644 composer.json create mode 100644 config/services.php create mode 100644 deptrac.yaml create mode 100644 phpstan.neon create mode 100644 phpunit.xml.dist create mode 100644 rector.php create mode 100644 src/Infrastructure/Symfony/HttpKernel/SuluMessengerBundle.php create mode 100644 src/Infrastructure/Symfony/Messenger/FlushMiddleware/DoctrineFlushMiddleware.php create mode 100644 src/Infrastructure/Symfony/Messenger/FlushMiddleware/EnableFlushStamp.php create mode 100644 src/Infrastructure/Symfony/Messenger/UnpackExceptionMiddleware/UnpackExceptionMiddleware.php create mode 100644 tests/Application/.env create mode 100644 tests/Application/Kernel.php create mode 100755 tests/Application/bin/console create mode 100755 tests/Application/bin/console.php create mode 100644 tests/Application/config/packages/doctrine.yaml create mode 100644 tests/Application/config/packages/framework.yaml create mode 100644 tests/Application/public/.gitkeep create mode 100644 tests/Functional/.gitkeep create mode 100644 tests/Traits/PrivatePropertyTrait.php create mode 100644 tests/Unit/Infrastructure/Symfony/Messenger/FlushMiddleware/DoctrineFlushMiddlewareTest.php create mode 100644 tests/Unit/Infrastructure/Symfony/Messenger/UnpackExceptionMiddleware/UnpackExceptionMiddlewareTest.php create mode 100644 tests/bootstrap.php create mode 100644 tests/generator-config.php create mode 100644 tests/phpstan/console-application.php create mode 100644 tests/phpstan/object-manager.php create mode 100644 translations/.gitkeep diff --git a/.github/workflows/test-application.yaml b/.github/workflows/test-application.yaml new file mode 100644 index 0000000..1d8b5ea --- /dev/null +++ b/.github/workflows/test-application.yaml @@ -0,0 +1,95 @@ +name: Test application + +on: + pull_request: + push: + branches: + - '[0-9]+.x' + - '[0-9]+.[0-9]+' + +jobs: + test: + name: 'PHP ${{ matrix.php-version }} (${{ matrix.dependency-versions }}, Coverage ${{ matrix.coverage }})' + runs-on: ubuntu-latest + + env: + DATABASE_URL: mysql://root:root@127.0.0.1:3306/sulu_messenger_test?serverVersion=5.7&charset=utf8mb4 + + strategy: + fail-fast: false + matrix: + include: + - php-version: '8.0' + coverage: false + dependency-versions: 'lowest' + env: + SYMFONY_DEPRECATIONS_HELPER: weak + + - php-version: '8.1' + coverage: true + dependency-versions: 'highest' + env: + SYMFONY_DEPRECATIONS_HELPER: weak + + services: + mysql: + image: mysql:5.7 + env: + MYSQL_ROOT_PASSWORD: root + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5 + + steps: + - name: Checkout project + uses: actions/checkout@v2 + + - name: Install and configure PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: ctype, iconv, mysql + coverage: pcov + + - name: Install composer dependencies + uses: ramsey/composer-install@v2 + with: + dependency-versions: ${{ matrix.dependency-versions }} + composer-options: ${{ matrix.composer-options }} + + - name: Bootstrap test environment + run: composer bootstrap-test-environment + + - name: Execute test cases + if: matrix.coverage == false + run: composer test + + - name: Execute test cases with coverage + if: ${{ matrix.coverage }} + run: composer test-with-coverage + + - name: Check code coverage + if: ${{ matrix.coverage }} + run: composer check-coverage + + lint: + name: "PHP Lint" + runs-on: ubuntu-latest + + steps: + - name: Checkout project + uses: actions/checkout@v2 + + - name: Install and configure PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.1 + extensions: ctype, iconv, mysql + + - name: Install composer dependencies + uses: ramsey/composer-install@v1 + with: + dependency-versions: highest + + - name: Lint Code + run: composer lint diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..20bcd78 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +.idea +*.iml + +/vendor/ +composer.lock + +.php-cs-fixer.cache +.phpunit.result.cache +.deptrac.cache + +/tests/Application/var +/tests/Application/secrets diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..b31feeb --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,43 @@ +in(__DIR__) + ->exclude(['tests/Application/var']) + ->notName('bundles.php'); + +return (new PhpCsFixer\Config()) + ->setRiskyAllowed(true) + ->setRules([ + '@Symfony' => true, + '@Symfony:risky' => true, + 'ordered_imports' => true, + 'concat_space' => ['spacing' => 'one'], + 'array_syntax' => ['syntax' => 'short'], + 'phpdoc_align' => ['align' => 'left'], + 'class_definition' => [ + 'multi_line_extends_each_single_line' => true, + ], + 'linebreak_after_opening_tag' => true, + 'declare_strict_types' => true, + 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'], + 'native_constant_invocation' => true, + 'native_function_casing' => true, + 'native_function_invocation' => ['include' => ['@internal']], + 'no_php4_constructor' => true, + 'no_superfluous_phpdoc_tags' => ['allow_mixed' => true, 'remove_inheritdoc' => true], + 'no_unreachable_default_argument_value' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'php_unit_strict' => true, + 'phpdoc_order' => true, + 'semicolon_after_instruction' => true, + 'strict_comparison' => true, + 'strict_param' => true, + 'array_indentation' => true, + 'multiline_whitespace_before_semicolons' => true, + 'single_line_throw' => false, + 'visibility_required' => ['elements' => ['property', 'method', 'const']], + ]) + ->setFinder($finder); diff --git a/README.md b/README.md index b5e3f64..62d7ccc 100644 --- a/README.md +++ b/README.md @@ -1 +1,73 @@ -# messenger \ No newline at end of file +

Sulu Messenger

+ +

+ + Official Sulu Bundle Badge + +

+ +

+ + GitHub license + + + GitHub tag (latest SemVer) + + + Test workflow status + + + Sulu compatibility + +

+
+ +This library provides the stamps and middlewares which configures the sulu message bus. +It can be used independently in any symfony installation. + +## Installation + +Make sure Composer is installed globally, as explained in the +[installation chapter](https://getcomposer.org/doc/00-intro.md) +of the Composer documentation. + +Open a command console, enter your project directory and execute: + +```bash +composer require sulu/messenger +``` + +Then, enable the bundle by adding it to the list of registered bundles +in the `config/bundles.php` file of your project: + +```php +// config/bundles.php + +return [ + // ... + Sulu\Messenger\Infrastructure\Symfony\HttpKernel\SuluMessengerBundle::class => ['all' => true], +]; +``` + +## Middlewares + +### UnpackExceptionMiddleware + +The `UnpackExceptionMiddleware` will unpack the `HandlerFailedException` which +is created by the Symfony [`HandleMessageMiddleware`](https://github.com/symfony/symfony/blob/c7dbcc954366f92f66360f3960a10dc1ef5f2584/src/Symfony/Component/Messenger/Middleware/HandleMessageMiddleware.php#L129). +This way we make sure that the real exception is thrown out by this message +bus, and a controller can catch or convert it to a specific http status code. +This middleware is always activated in the sulu message bus. + +### DoctrineFlushMiddleware + +The `DoctrineFlushMiddleware` is a Middleware which let us flush the Doctrine +EntityManager by an opt-in flag via the `EnableFlushStamp`. It can be used this way: + +```php +use Sulu\Messenger\Infrastructure\Symfony\Messenger\FlushMiddleware\EnableFlushStamp; + +$this->handle(new Envelope(new YourMessage(), [new EnableFlushStamp()])); +``` + +This middleware is always activated in the sulu message bus. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..eac7612 --- /dev/null +++ b/composer.json @@ -0,0 +1,105 @@ +{ + "name": "sulu/messenger", + "description": "This library provides the stamps, middlewares and the sulu message bus.", + "license": "proprietary", + "type": "sulu-bundle", + "require": { + "php": "8.0.* || 8.1.*", + "doctrine/dbal": "^3.3", + "doctrine/doctrine-bundle": "^2.5", + "doctrine/orm": "^2.11", + "symfony/config": "^5.4 || ^6.0", + "symfony/dependency-injection": "^5.4 || ^6.0", + "symfony/doctrine-bridge": "^5.4 || ^6.0", + "symfony/framework-bundle": "^5.4 || ^6.0", + "symfony/http-kernel": "^5.4 || ^6.0", + "symfony/messenger": "^5.4 || ^6.0" + }, + "require-dev": { + "coduo/php-matcher": "^6.0", + "friendsofphp/php-cs-fixer": "^3.6", + "handcraftedinthealps/code-coverage-checker": "^0.2.5", + "jangregor/phpstan-prophecy": "^1.0", + "nunomaduro/collision": "^5.0 || ^6.0", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-doctrine": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-symfony": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.0", + "phpunit/phpunit": "^9.5", + "qossmic/deptrac-shim": "^0.19.3", + "rector/rector": "^0.12.16", + "schranz/test-generator": "^0.3", + "symfony/browser-kit": "^5.4 || ^6.0", + "symfony/css-selector": "^5.4 || ^6.0", + "symfony/debug-bundle": "^5.4 || ^6.0", + "symfony/error-handler": "^5.4 || ^6.0", + "symfony/phpunit-bridge": "^5.4 || ^6.0", + "thecodingmachine/phpstan-strict-rules": "^1.0", + "symfony/dotenv": "^5.4 || ^6.0", + "symfony/yaml": "^5.4 || ^6.0" + }, + "autoload": { + "psr-4": { + "Sulu\\Messenger\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Sulu\\Messenger\\Tests\\": "tests/" + } + }, + "scripts": { + "bootstrap-test-environment": [ + "@php tests/Application/bin/console cache:clear --env test", + "@php tests/Application/bin/console doctrine:database:drop --force --if-exists --env test", + "@php tests/Application/bin/console doctrine:database:create --env test", + "@php tests/Application/bin/console doctrine:schema:create --env test" + ], + "test": "@phpunit", + "test-with-coverage": "@phpunit --coverage-php Tests/Application/var/reports/coverage.php --coverage-cobertura=Tests/Application/var/cobertura-coverage.xml --coverage-html Tests/Application/var/reports/html --log-junit Tests/Application/var/reports/junit.xml", + "check-coverage": [ + "@php vendor/bin/code-coverage-checker \"Tests/Application/var/reports/coverage.php\" \"line\" \"100.00\" \"src\"" + ], + "lint": [ + "@php-cs", + "@phpstan", + "@lint-rector", + "@lint-twig", + "@lint-yaml", + "@lint-container", + "@lint-composer", + "@lint-doctrine", + "@lint-deptrac" + ], + "phpunit": "@php vendor/bin/phpunit", + "phpstan": [ + "@php tests/Application/bin/console cache:warmup --env=dev", + "@php vendor/bin/phpstan analyze" + ], + "lint-rector": [ + "@php tests/Application/bin/console cache:warmup --env=dev", + "@php vendor/bin/rector process --dry-run" + ], + "rector": [ + "@php vendor/bin/rector process" + ], + "php-cs": "@php vendor/bin/php-cs-fixer fix --verbose --diff --dry-run", + "php-cs-fix": "@php vendor/bin/php-cs-fixer fix", + "lint-composer": "@composer validate --no-check-publish --strict", + "lint-yaml": "@php tests/Application/bin/console lint:yaml tests/Application/config --parse-tags", + "lint-deptrac": "@php vendor/bin/deptrac", + "lint-container": [ + "@php tests/Application/bin/console lint:container --env dev", + "@php tests/Application/bin/console lint:container --env test", + "@php tests/Application/bin/console lint:container --env prod" + ], + "lint-doctrine": [ + "@php tests/Application/bin/console doctrine:schema:validate --skip-sync --env prod" + ] + }, + "config": { + "sort-packages": true + } +} diff --git a/config/services.php b/config/services.php new file mode 100644 index 0000000..1c33c75 --- /dev/null +++ b/config/services.php @@ -0,0 +1,19 @@ +services(); + + $services->set('sulu_messenger.doctrine_flush_middleware') + ->class(DoctrineFlushMiddleware::class) + ->args([service('doctrine.orm.entity_manager')]); + + $services->set('sulu_messenger.unpack_exception_middleware') + ->class(UnpackExceptionMiddleware::class); +}; diff --git a/deptrac.yaml b/deptrac.yaml new file mode 100644 index 0000000..b3fd5db --- /dev/null +++ b/deptrac.yaml @@ -0,0 +1,66 @@ +parameters: + paths: + - src + + layers: + # Infrastructure + - name: Vendor Infrastructure + collectors: + - type: className + regex: ^Redis + - type: className + regex: .*EntityManager.* + - type: className + regex: .*EntityRepository.* + - type: className + regex: .*ObjectManager.* + - type: className + regex: .*ObjectRepository.* + + # Layers + - name: UserInterface + collectors: + - type: directory + regex: UserInterface/.* + - name: Infrastructure + collectors: + - type: directory + regex: Infrastructure/.* + - name: Application + collectors: + - type: directory + regex: Application\/((?!ProcessManager).*)/.* + - name: ProcessManager + collectors: + - type: directory + regex: Application/ProcessManager/.* + - name: Domain + collectors: + - type: directory + regex: Code\/Domain\/((?!Event).*)\/.* # events are excluded because they are accessible by all + - name: Event + collectors: + - type: directory + regex: Code\/Domain\/Event\/.* + + ruleset: + # Ruleset + UserInterface: + - Application + - Domain + - Infrastructure + Infrastructure: + - Vendor Infrastructure + - Application + - Domain + Application: + - Domain + - Event + ProcessManager: + - Domain + - Application + # All Events: + #- OtherContext Event + Domain: ~ + Event: + - Domain diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..f6b4b5a --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,24 @@ +includes: + - vendor/jangregor/phpstan-prophecy/extension.neon + - vendor/phpstan/phpstan-symfony/extension.neon + - vendor/phpstan/phpstan-doctrine/extension.neon + - vendor/phpstan/phpstan-doctrine/rules.neon + - vendor/phpstan/phpstan-phpunit/extension.neon + - vendor/phpstan/phpstan-phpunit/rules.neon + - vendor/phpstan/phpstan-webmozart-assert/extension.neon + - vendor/thecodingmachine/phpstan-strict-rules/phpstan-strict-rules.neon + +parameters: + paths: + - src + - tests + - config + level: max + excludePaths: + - %currentWorkingDirectory%/vendor/* + - %currentWorkingDirectory%/tests/Application/var/* + doctrine: + objectManagerLoader: tests/phpstan/object-manager.php + symfony: + container_xml_path: %currentWorkingDirectory%/tests/Application/var/cache/dev/Sulu_Messenger_Tests_Application_KernelDevDebugContainer.xml + console_application_loader: tests/phpstan/console-application.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..d88a1b4 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + tests/Unit + + + + tests/Functional + + + + + + src + + + + + + + + + + diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..eb75dca --- /dev/null +++ b/rector.php @@ -0,0 +1,43 @@ +parameters(); + $services = $containerConfigurator->services(); + + $parameters->set(Option::PATHS, [__DIR__ . '/src', __DIR__ . '/tests']); + $parameters->set(Option::SKIP, [ + __DIR__ . '/tests/Application/var', + __DIR__ . '/tests/Application/config', + ]); + $parameters->set(Option::PHPSTAN_FOR_RECTOR_PATH, __DIR__ . '/phpstan.neon'); + + // basic rules + $parameters->set(Option::AUTO_IMPORT_NAMES, true); + $parameters->set(Option::IMPORT_DOC_BLOCKS, true); + $parameters->set(Option::IMPORT_SHORT_CLASSES, false); + + $containerConfigurator->import(SetList::CODE_QUALITY); + $containerConfigurator->import(LevelSetList::UP_TO_PHP_80); + + // symfony rules + $parameters = $containerConfigurator->parameters(); + $parameters->set( + Option::SYMFONY_CONTAINER_XML_PATH_PARAMETER, + __DIR__ . '/tests/Application/var/cache/dev/Sulu_Messenger_Tests_Application_KernelDevDebugContainer.xml' + ); + $containerConfigurator->import(SymfonySetList::SYMFONY_CODE_QUALITY); + $containerConfigurator->import(SymfonySetList::SYMFONY_CONSTRUCTOR_INJECTION); + $containerConfigurator->import(SymfonyLevelSetList::UP_TO_SYMFONY_54); + + $containerConfigurator->import(DoctrineSetList::DOCTRINE_CODE_QUALITY); +}; diff --git a/src/Infrastructure/Symfony/HttpKernel/SuluMessengerBundle.php b/src/Infrastructure/Symfony/HttpKernel/SuluMessengerBundle.php new file mode 100644 index 0000000..2dfd709 --- /dev/null +++ b/src/Infrastructure/Symfony/HttpKernel/SuluMessengerBundle.php @@ -0,0 +1,90 @@ + $config + */ + public function getConfiguration(array $config, ContainerBuilder $container): ConfigurationInterface + { + return $this; + } + + public function prepend(ContainerBuilder $container): void + { + if (!$container->hasExtension('framework')) { + throw new \RuntimeException(\sprintf('The "%s" bundle requires "framework" bundle.', self::ALIAS)); + } + + $container->prependExtensionConfig( + 'framework', + [ + 'messenger' => [ + 'default_bus' => 'sulu_message_bus', + 'buses' => [ + 'sulu_message_bus' => [ + 'middleware' => [ + 'sulu_messenger.unpack_exception_middleware', + 'sulu_messenger.doctrine_flush_middleware', + ], + ], + ], + ], + ] + ); + } + + public function getConfigTreeBuilder(): TreeBuilder + { + return new TreeBuilder(self::ALIAS); + } + + /** + * @param array $configs + */ + public function load(array $configs, ContainerBuilder $container): void + { + $loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__, 4) . '/config')); + + $loader->load('services.php'); + } +} diff --git a/src/Infrastructure/Symfony/Messenger/FlushMiddleware/DoctrineFlushMiddleware.php b/src/Infrastructure/Symfony/Messenger/FlushMiddleware/DoctrineFlushMiddleware.php new file mode 100644 index 0000000..bebc6ff --- /dev/null +++ b/src/Infrastructure/Symfony/Messenger/FlushMiddleware/DoctrineFlushMiddleware.php @@ -0,0 +1,30 @@ +next()->handle($envelope, $stack); + + // flush unit-of-work to the database after the root message was handled successfully + if (!empty($envelope->all(EnableFlushStamp::class))) { + $this->entityManager->flush(); + } + + return $envelope; + } +} diff --git a/src/Infrastructure/Symfony/Messenger/FlushMiddleware/EnableFlushStamp.php b/src/Infrastructure/Symfony/Messenger/FlushMiddleware/EnableFlushStamp.php new file mode 100644 index 0000000..6b59dc7 --- /dev/null +++ b/src/Infrastructure/Symfony/Messenger/FlushMiddleware/EnableFlushStamp.php @@ -0,0 +1,14 @@ +next()->handle($envelope, $stack); + } catch (HandlerFailedException $exception) { // @phpstan-ignore-line + throw $exception->getPrevious(); // @phpstan-ignore-line + } + + return $envelope; + } +} diff --git a/tests/Application/.env b/tests/Application/.env new file mode 100644 index 0000000..8de577b --- /dev/null +++ b/tests/Application/.env @@ -0,0 +1,6 @@ +# env +APP_ENV=test +APP_SECRET='s$cretf0rt3st' +SYMFONY_DEPRECATIONS_HELPER=999999 +KERNEL_CLASS='Sulu\Messenger\Tests\Application\Kernel' +DATABASE_URL=mysql://root:ChangeMe@127.0.0.1:3306/sulu_messenger?serverVersion=8.0&charset=utf8mb4 diff --git a/tests/Application/Kernel.php b/tests/Application/Kernel.php new file mode 100644 index 0000000..9a59db5 --- /dev/null +++ b/tests/Application/Kernel.php @@ -0,0 +1,47 @@ + ['all' => true], + DebugBundle::class => ['dev' => true, 'test' => true], + DoctrineBundle::class => ['all' => true], + SuluMessengerBundle::class => ['all' => true], + ]; + + foreach ($bundles as $class => $envs) { + if ($envs[$this->environment] ?? $envs['all'] ?? false) { + yield new $class(); + } + } + } +} diff --git a/tests/Application/bin/console b/tests/Application/bin/console new file mode 100755 index 0000000..a32f095 --- /dev/null +++ b/tests/Application/bin/console @@ -0,0 +1,4 @@ +#!/usr/bin/env php +getParameterOption(['--env', '-e'], null, true)) { + \putenv('APP_ENV=' . $_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env); +} + +if ($input->hasParameterOption('--no-debug', true)) { + \putenv('APP_DEBUG=' . $_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0'); +} + +require \dirname(__DIR__, 2) . '/bootstrap.php'; + +if ($_SERVER['APP_DEBUG']) { + \umask(0000); + + if (\class_exists(Debug::class)) { + (new Debug())->enable(); + } +} + +$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); +$application = new Application($kernel); +$application->run($input); diff --git a/tests/Application/config/packages/doctrine.yaml b/tests/Application/config/packages/doctrine.yaml new file mode 100644 index 0000000..bde8bb1 --- /dev/null +++ b/tests/Application/config/packages/doctrine.yaml @@ -0,0 +1,11 @@ +doctrine: + dbal: + url: '%env(resolve:DATABASE_URL)%' + + # IMPORTANT: You MUST configure your server version, + # either here or in the DATABASE_URL env var (see .env file) + #server_version: '8.0' + orm: + auto_generate_proxy_classes: true + naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware + auto_mapping: true diff --git a/tests/Application/config/packages/framework.yaml b/tests/Application/config/packages/framework.yaml new file mode 100644 index 0000000..e40ab1e --- /dev/null +++ b/tests/Application/config/packages/framework.yaml @@ -0,0 +1,25 @@ +# see https://symfony.com/doc/current/reference/configuration/framework.html +framework: + secret: '%env(APP_SECRET)%' + #csrf_protection: true + #http_method_override: false + + # Enables session support. Note that the session will ONLY be started if you read or write from it. + # Remove or comment this section to explicitly disable session support. + session: + handler_id: null + cookie_secure: auto + cookie_samesite: lax + storage_factory_id: session.storage.factory.native + name: SULUSESSID # This avoids conflicts with other applications running on the same domain + + #esi: true + #fragments: true + php_errors: + log: true + +when@test: + framework: + test: true + session: + storage_factory_id: session.storage.factory.mock_file diff --git a/tests/Application/public/.gitkeep b/tests/Application/public/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/Functional/.gitkeep b/tests/Functional/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/Traits/PrivatePropertyTrait.php b/tests/Traits/PrivatePropertyTrait.php new file mode 100644 index 0000000..b4d7b8a --- /dev/null +++ b/tests/Traits/PrivatePropertyTrait.php @@ -0,0 +1,45 @@ +getProperty($propertyName); + $propertyReflection->setAccessible(true); + + return $propertyReflection->getValue($object); + } + + /** + * @param mixed $value + */ + protected static function setPrivateProperty(object $object, string $propertyName, $value): void + { + $reflection = new \ReflectionClass($object); + try { + $propertyReflection = $reflection->getProperty($propertyName); + self::setValue($propertyReflection, $object, $value); + } catch (\ReflectionException) { + $parent = $reflection->getParentClass(); + if ($parent) { + $propertyReflection = $parent->getProperty($propertyName); + self::setValue($propertyReflection, $object, $value); + } + } + } + + private static function setValue(\ReflectionProperty $propertyReflection, object $object, mixed $value): void + { + $propertyReflection->setAccessible(true); + + $propertyReflection->setValue($object, $value); + } +} diff --git a/tests/Unit/Infrastructure/Symfony/Messenger/FlushMiddleware/DoctrineFlushMiddlewareTest.php b/tests/Unit/Infrastructure/Symfony/Messenger/FlushMiddleware/DoctrineFlushMiddlewareTest.php new file mode 100644 index 0000000..b719780 --- /dev/null +++ b/tests/Unit/Infrastructure/Symfony/Messenger/FlushMiddleware/DoctrineFlushMiddlewareTest.php @@ -0,0 +1,76 @@ + + */ + private ObjectProphecy $entityManager; + + protected function setUp(): void + { + $this->entityManager = $this->prophesize(EntityManagerInterface::class); + $this->middleware = new DoctrineFlushMiddleware( + $this->entityManager->reveal() + ); + } + + public function testHandleWithoutStamp(): void + { + $envelope = $this->createEnvelope(); + $stack = $this->createStack(); + + $this->entityManager->flush() + ->shouldNotBeCalled(); + + $this->assertSame( + $envelope, + $this->middleware->handle($envelope, $stack) + ); + } + + public function testHandleWithStamp(): void + { + $envelope = $this->createEnvelope(); + $envelope = $envelope->with(new EnableFlushStamp()); + $stack = $this->createStack(); + + $this->entityManager->flush() + ->shouldBeCalled(); + + $this->assertSame( + $envelope, + $this->middleware->handle($envelope, $stack) + ); + } + + private function createEnvelope(): Envelope + { + return new Envelope(new \stdClass()); + } + + private function createStack(): StackMiddleware + { + return new StackMiddleware([]); + } +} diff --git a/tests/Unit/Infrastructure/Symfony/Messenger/UnpackExceptionMiddleware/UnpackExceptionMiddlewareTest.php b/tests/Unit/Infrastructure/Symfony/Messenger/UnpackExceptionMiddleware/UnpackExceptionMiddlewareTest.php new file mode 100644 index 0000000..0cd4782 --- /dev/null +++ b/tests/Unit/Infrastructure/Symfony/Messenger/UnpackExceptionMiddleware/UnpackExceptionMiddlewareTest.php @@ -0,0 +1,84 @@ +middleware = new UnpackExceptionMiddleware(); + } + + public function testHandleDefault(): void + { + $envelope = $this->createEnvelope(); + $stack = $this->createStack(); + + $this->assertSame( + $envelope, + $this->middleware->handle($envelope, $stack) + ); + } + + public function testHandleException(): void + { + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('Specific unpacked exception.'); + + $envelope = $this->createEnvelope(); + $stack = $this->createStack(function () use ($envelope) { + throw new HandlerFailedException($envelope, [new \LogicException('Specific unpacked exception.')]); + }); + + $this->assertSame( + $envelope, + $this->middleware->handle($envelope, $stack) + ); + } + + private function createEnvelope(): Envelope + { + return new Envelope(new \stdClass()); + } + + private function createStack(callable $handler = null): StackMiddleware + { + if (!$handler) { + return new StackMiddleware([]); + } + + $middleware = new class($handler) implements MiddlewareInterface { + /** + * @param callable $handler + */ + public function __construct(private $handler) + { + } + + public function handle(Envelope $envelope, StackInterface $stack): Envelope + { + $handler = $this->handler; + $handler(); + + return $envelope; + } + }; + + return new StackMiddleware($middleware); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..0a418b9 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,21 @@ +bootEnv(__DIR__ . '/Application/.env'); + +if (\class_exists(\Locale::class)) { + \Locale::setDefault('en'); +} diff --git a/tests/generator-config.php b/tests/generator-config.php new file mode 100644 index 0000000..192961a --- /dev/null +++ b/tests/generator-config.php @@ -0,0 +1,11 @@ +hooks[] = 'vendor/bin/rector process %s'; +$config->hooks[] = 'vendor/bin/php-cs-fixer fix %s'; + +return $config; diff --git a/tests/phpstan/console-application.php b/tests/phpstan/console-application.php new file mode 100644 index 0000000..eb14451 --- /dev/null +++ b/tests/phpstan/console-application.php @@ -0,0 +1,13 @@ +boot(); + +return new Application($kernel); diff --git a/tests/phpstan/object-manager.php b/tests/phpstan/object-manager.php new file mode 100644 index 0000000..24e0e21 --- /dev/null +++ b/tests/phpstan/object-manager.php @@ -0,0 +1,33 @@ +boot(); + +/** @var ContainerInterface $container */ +$container = $kernel->getContainer(); + +/** @var EntityManager $objectManager */ +$objectManager = $container->get('doctrine')->getManager(); + +// remove ResolveTargetEntityListener from returned EntityManager to not resolve SuluPersistenceBundle classes +// this is a workaround for the following phpstan issue: https://github.com/phpstan/phpstan-doctrine/issues/98 +$resolveTargetEntityListener = \current(\array_filter( + $objectManager->getEventManager()->getListeners('loadClassMetadata'), + static fn ($listener) => $listener instanceof ResolveTargetEntityListener +)); + +if ($resolveTargetEntityListener) { + $objectManager->getEventManager()->removeEventListener([Events::loadClassMetadata], $resolveTargetEntityListener); +} + +return $objectManager; diff --git a/translations/.gitkeep b/translations/.gitkeep new file mode 100644 index 0000000..e69de29