From 7a189a3baefce32a142c219532214910611f7724 Mon Sep 17 00:00:00 2001 From: Maximilian Graf Schimmelmann Date: Mon, 31 Jul 2023 18:39:29 +0200 Subject: [PATCH 01/24] Adapt logic to support PHP8, run on docker, added tests and code style. --- .dockerignore | 10 + .env.dist | 2 + .github/workflows/ci.yml | 108 ++++--- .gitignore | 18 +- LICENSE | 1 + README.md | 58 +++- behat.yml | 14 +- behat.yml.dist | 12 +- composer.json | 50 +++- docker-compose.yml | 48 +++ docker/php/Dockerfile | 91 ++++++ docker/php/etc/999-php-custom.ini | 26 ++ docker/php/etc/auth.json.tmpl | 8 + docker/php/etc/create-auth-file.sh | 5 + docker/php/etc/entrypoint.sh | 5 + docker/php/etc/fetch-magento.sh | 8 + docker/php/etc/install-extension.sh | 11 + docker/php/etc/install-magento.sh | 25 ++ docker/php/etc/run-ecs.sh | 7 + docker/php/etc/run-phpstan.sh | 5 + docker/php/etc/run-tests.sh | 5 + docker/php/etc/test-magento.sh | 8 + docs/conf.py | 6 +- docs/guide/configuration.rst | 22 +- docs/guide/installation.rst | 2 +- docs/guide/quickstart.rst | 8 +- docs/guide/usage.rst | 2 + ...-behat-context-as-constructor-argument.rst | 2 +- ...hat-step-argument-transformer-argument.rst | 2 +- ...o-behat-context-as-behat-step-argument.rst | 2 +- ...-behat-context-as-constructor-argument.rst | 2 +- docs/guide/usage/mocking-dependency.rst | 5 +- ...ry-scenario-to-ensure-data-correctness.rst | 47 +++ docs/index.rst | 9 +- ecs.php | 279 ++++++++++++++++++ .../Context/AbstractMagentoContext.php | 16 + .../Components/Customer/CustomerContext.php | 100 +++++++ .../Input/MagentoCommandInput.php | 24 ++ .../Context/Components/Stock/StockContext.php | 52 ++++ .../Context/Components/Store/StoreContext.php | 51 ++++ .../bootstrap/Context/Hook/DatabaseHook.php | 60 ++++ .../Context/Hook/DatabaseHookInterface.php | 12 + .../bootstrap/Context/Tasks/CacheCleaner.php | 46 +++ .../Context/Tasks/CacheCleanerInterface.php | 10 + .../Context/Tasks/DefaultFixtures.php | 235 +++++++++++++++ .../Tasks/DefaultFixturesInterface.php | 13 + .../Context/Tasks/MagentoPathProvider.php | 23 ++ .../Tasks/MagentoPathProviderInterface.php | 10 + features/bootstrap/Context/Tasks/Purger.php | 31 ++ .../Context/Tasks/PurgerInterface.php | 12 + .../bootstrap/Context/TestRunnerContext.php | 268 ++++++++++++----- .../WithCompiledDITestRunnerContext.php | 12 - .../WithoutCompiledDITestRunnerContext.php | 12 - features/helper_services.feature | 92 ++---- ...ng_service_through_a_step_argument.feature | 6 +- ...ice_through_a_transformer_argument.feature | 5 +- ...ce_through_the_context_constructor.feature | 9 +- ...ture => merging_di_configurations.feature} | 91 ++---- features/mocking.feature | 20 +- features/purging_feature.feature | 87 ++++++ phpstan.neon | 12 + ...legatingSymfonyServiceContainerFactory.php | 46 --- .../Service/MagentoObjectManager.php | 18 -- .../ServiceContainer/Magento2Extension.php | 53 ---- .../ServiceContainer/config/services.xml | 35 --- .../SharedStorage/SharedStorage.php | 34 +++ .../SharedStorageAwareInterface.php | 10 + .../SharedStorage/SharedStorageInterface.php | 22 ++ .../DelegatingSymfonyServiceContainer.php | 54 ++-- ...legatingSymfonyServiceContainerFactory.php | 36 +++ ...ymfonyServiceContainerFactoryInterface.php | 13 + .../Loader/DelegatingLoaderHelper.php | 29 ++ .../DelegatingLoaderHelperInterface.php | 12 + .../Magento2SymfonyServiceContainer.php | 32 +- .../ServiceContainerInterface.php | 12 + .../MagentoObjectManagerInitListener.php} | 141 ++++----- ...entoObjectManagerInitListenerInterface.php | 19 ++ src/Service/MagentoObjectManager.php | 20 ++ src/Service/MagentoObjectManagerInterface.php | 12 + .../ServiceContainer/Config.php | 22 +- src/ServiceContainer/ConfigInterface.php | 16 + src/ServiceContainer/Magento2Extension.php | 70 +++++ .../Magento2ExtensionInterface.php | 19 ++ src/ServiceContainer/config/services.php | 75 +++++ tests/Context/Tasks/CacheCleanerTest.php | 69 +++++ .../Context/Tasks/MagentoPathProviderTest.php | 24 ++ tests/Context/Tasks/PurgerTest.php | 56 ++++ .../DelegatingSymfonyServiceContainerTest.php | 175 +++++++++++ ...tingSymfonyServiceContainerFactoryTest.php | 57 ++++ .../Helper/DelegatingLoaderHelperTest.php | 22 ++ tests/HelperContainer/Helper/test.yml | 0 .../Magento2SymfonyServiceContainerTest.php | 64 ++++ .../MagentoObjectManagerInitListenerTest.php | 166 +++++++++++ tests/ServiceContainer/ConfigTest.php | 32 ++ .../Magento2ExtensionTest.php | 67 +++++ 95 files changed, 3112 insertions(+), 642 deletions(-) create mode 100644 .dockerignore create mode 100644 .env.dist create mode 100644 docker-compose.yml create mode 100644 docker/php/Dockerfile create mode 100644 docker/php/etc/999-php-custom.ini create mode 100644 docker/php/etc/auth.json.tmpl create mode 100644 docker/php/etc/create-auth-file.sh create mode 100644 docker/php/etc/entrypoint.sh create mode 100644 docker/php/etc/fetch-magento.sh create mode 100644 docker/php/etc/install-extension.sh create mode 100644 docker/php/etc/install-magento.sh create mode 100644 docker/php/etc/run-ecs.sh create mode 100644 docker/php/etc/run-phpstan.sh create mode 100644 docker/php/etc/run-tests.sh create mode 100644 docker/php/etc/test-magento.sh create mode 100644 docs/guide/usage/purge-database-after-every-scenario-to-ensure-data-correctness.rst create mode 100644 ecs.php create mode 100644 features/bootstrap/Context/AbstractMagentoContext.php create mode 100644 features/bootstrap/Context/Components/Customer/CustomerContext.php create mode 100644 features/bootstrap/Context/Components/ProcessFactory/Input/MagentoCommandInput.php create mode 100644 features/bootstrap/Context/Components/Stock/StockContext.php create mode 100644 features/bootstrap/Context/Components/Store/StoreContext.php create mode 100644 features/bootstrap/Context/Hook/DatabaseHook.php create mode 100644 features/bootstrap/Context/Hook/DatabaseHookInterface.php create mode 100644 features/bootstrap/Context/Tasks/CacheCleaner.php create mode 100644 features/bootstrap/Context/Tasks/CacheCleanerInterface.php create mode 100644 features/bootstrap/Context/Tasks/DefaultFixtures.php create mode 100644 features/bootstrap/Context/Tasks/DefaultFixturesInterface.php create mode 100644 features/bootstrap/Context/Tasks/MagentoPathProvider.php create mode 100644 features/bootstrap/Context/Tasks/MagentoPathProviderInterface.php create mode 100644 features/bootstrap/Context/Tasks/Purger.php create mode 100644 features/bootstrap/Context/Tasks/PurgerInterface.php delete mode 100644 features/bootstrap/Context/WithCompiledDITestRunnerContext.php delete mode 100644 features/bootstrap/Context/WithoutCompiledDITestRunnerContext.php rename features/{mering_di_configurations.feature => merging_di_configurations.feature} (82%) create mode 100644 features/purging_feature.feature create mode 100644 phpstan.neon delete mode 100644 src/Bex/Behat/Magento2Extension/HelperContainer/Factory/DelegatingSymfonyServiceContainerFactory.php delete mode 100644 src/Bex/Behat/Magento2Extension/Service/MagentoObjectManager.php delete mode 100644 src/Bex/Behat/Magento2Extension/ServiceContainer/Magento2Extension.php delete mode 100644 src/Bex/Behat/Magento2Extension/ServiceContainer/config/services.xml create mode 100644 src/Components/SharedStorage/SharedStorage.php create mode 100644 src/Components/SharedStorage/SharedStorageAwareInterface.php create mode 100644 src/Components/SharedStorage/SharedStorageInterface.php rename src/{Bex/Behat/Magento2Extension => }/HelperContainer/DelegatingSymfonyServiceContainer.php (58%) create mode 100644 src/HelperContainer/Factory/DelegatingSymfonyServiceContainerFactory.php create mode 100644 src/HelperContainer/Factory/DelegatingSymfonyServiceContainerFactoryInterface.php create mode 100644 src/HelperContainer/Loader/DelegatingLoaderHelper.php create mode 100644 src/HelperContainer/Loader/DelegatingLoaderHelperInterface.php rename src/{Bex/Behat/Magento2Extension => }/HelperContainer/Magento2SymfonyServiceContainer.php (50%) create mode 100644 src/HelperContainer/ServiceContainerInterface.php rename src/{Bex/Behat/Magento2Extension/Listener/MagentoObjectManagerInitializer.php => Listener/MagentoObjectManagerInitListener.php} (50%) create mode 100644 src/Listener/MagentoObjectManagerInitListenerInterface.php create mode 100644 src/Service/MagentoObjectManager.php create mode 100644 src/Service/MagentoObjectManagerInterface.php rename src/{Bex/Behat/Magento2Extension => }/ServiceContainer/Config.php (55%) create mode 100644 src/ServiceContainer/ConfigInterface.php create mode 100644 src/ServiceContainer/Magento2Extension.php create mode 100644 src/ServiceContainer/Magento2ExtensionInterface.php create mode 100644 src/ServiceContainer/config/services.php create mode 100644 tests/Context/Tasks/CacheCleanerTest.php create mode 100644 tests/Context/Tasks/MagentoPathProviderTest.php create mode 100644 tests/Context/Tasks/PurgerTest.php create mode 100644 tests/HelperContainer/DelegatingSymfonyServiceContainerTest.php create mode 100644 tests/HelperContainer/Factory/DelegatingSymfonyServiceContainerFactoryTest.php create mode 100644 tests/HelperContainer/Helper/DelegatingLoaderHelperTest.php create mode 100644 tests/HelperContainer/Helper/test.yml create mode 100644 tests/HelperContainer/Magento2SymfonyServiceContainerTest.php create mode 100644 tests/Listener/MagentoObjectManagerInitListenerTest.php create mode 100644 tests/ServiceContainer/ConfigTest.php create mode 100644 tests/ServiceContainer/Magento2ExtensionTest.php diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0b07001 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +./.github +./docs +./docker +./docker/data +./docker-compose.yml +./vendor +!./docker/php/etc +!./docker/php/etc/* +./magento +./test_dir diff --git a/.env.dist b/.env.dist new file mode 100644 index 0000000..de736b1 --- /dev/null +++ b/.env.dist @@ -0,0 +1,2 @@ +MAGENTO_PUBLIC_KEY=xxx +MAGENTO_SECRET_KEY=xxx diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 61b14c6..f1ed687 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,21 +5,24 @@ on: push: branches: - "master" - - "2.x" - - "3.x" jobs: behat: name: "Acceptance Tests" - runs-on: "ubuntu-latest" + strategy: + matrix: + php-version: + - "7.4" + - "8.0" + - "8.2" env: M2_INSTANCE_ROOT_DIR: ${{ github.workspace }}/magento services: mysql: - image: mariadb:10.4 + image: mariadb:10.6 env: MYSQL_USER: magento MYSQL_PASSWORD: magento @@ -29,25 +32,25 @@ jobs: - 3306:3306 options: --tmpfs /tmp:rw --tmpfs /var/lib/mysql:rw --health-cmd="mysqladmin ping" - elasticsearch: - image: elasticsearch:7.12.1 + opensearch: + image: opensearchproject/opensearch:2.7.0 env: - ES_JAVA_OPTS: -Xms512m -Xmx512m discovery.type: single-node + cluster.name: opensearch-cluster + node.name: opensearch-node + bootstrap.memory_lock: true + OPENSEARCH_JAVA_OPTS: -Xms512m -Xmx512m + DISABLE_INSTALL_DEMO_CONFIG: true + DISABLE_SECURITY_PLUGIN: true ports: - - 9200:9200 + - "8892:9200" steps: - - name: "Checkout" - uses: "actions/checkout@v2" - with: - path: 'behat-magento2-extension' - - name: "Install PHP" uses: "shivammathur/setup-php@v2" with: coverage: none - php-version: 7.4 + php-version: "${{ matrix.php-version }}" extensions: bcmath, ctype, curl, dom, gd, hash, iconv, intl, mbstring, openssl, pdo_mysql, simplexml, soap, xsl, zip, sockets ini-values: memory_limit=-1 tools: composer:v2, cs2pr @@ -63,40 +66,79 @@ jobs: path: | ~/.composer/cache magento - key: "magento-2.4.2-with-php-7.4" + key: "magento-2.4.6-with-php-${{ matrix.php-version }}" - - name: "Create Magento 2.4.2 project with testing dependencies" + - name: "Create Magento 2.4.6 project with testing dependencies" run: | - composer create-project --repository=https://repo.magento.com/ magento/project-community-edition=2.4.2 magento - cd magento - composer require tkotosz/test-area-magento2 - composer require --dev behat/behat friends-of-behat/mink-extension behat/mink-goutte-driver - cd - + composer create-project --repository=https://repo.magento.com/ magento/project-community-edition=2.4.6 magento if: hashFiles('magento/composer.json') == '' + - name: "Checkout" + uses: "actions/checkout@v2" + with: + path: 'magento/vendor/seec/behat-magento2-extension' + - name: "Install Magento" run: | rm -f app/etc/env.php mkdir -p pub/static pub/media - bin/magento setup:install --admin-email "kotosy.magento@gmail.com" --admin-firstname "admin" --admin-lastname "admin" --admin-password "admin123" --admin-user "admin" --backend-frontname admin --base-url "http://magento.test" --db-host 127.0.0.1 --db-name magentodb --db-user magento --db-password magento --session-save files --use-rewrites 1 --use-secure 0 --search-engine=elasticsearch7 --elasticsearch-host=127.0.0.1 --elasticsearch-port=9200 -vvv + bin/magento setup:install --admin-email "magento@magento.com" --admin-firstname "admin" --admin-lastname "admin" --admin-password "admin123!#" --admin-user "admin" --backend-frontname admin --base-url "http://magento.test" --db-host 127.0.0.1 --db-name magento --db-user magento --db-password magento --session-save files --use-rewrites 1 --use-secure 0 --opensearch-host="opensearch" --opensearch-port="9200" --timezone="Europe/Amsterdam" -vvv bin/magento setup:upgrade working-directory: 'magento' - name: "Install Behat Magento 2 Extension in the Magento 2 Test Environment" run: | - composer config repositories.behat-m2-extension path ../behat-magento2-extension - composer require --dev bex/behat-magento2-extension:@dev + composer config repositories.behat-m2-extension path vendor/seec/behat-magento2-extension + composer require --dev seec/behat-magento2-extension:@dev working-directory: 'magento' - name: "Install Behat Magento 2 Extension's testing dependencies" - run: | - composer install - working-directory: 'behat-magento2-extension' + run: "composer install --no-interaction --no-progress --no-suggest" + working-directory: 'magento/vendor/seec/behat-magento2-extension' + + - name: "Run full test suite" + run: "vendor/bin/behat --tags=@virtual --stop-on-failure --config behat.yml" + working-directory: 'magento/vendor/seec/behat-magento2-extension' + + code-style: + name: "Check code style" + runs-on: "ubuntu-latest" + strategy: + matrix: + php-version: + - "7.4" + - "8.0" + - "8.2" + + steps: + - name: "Checkout" + uses: "actions/checkout@v2" + + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: none + php-version: "${{ matrix.php-version }}" + ini-values: memory_limit=-1 + tools: composer:v2, cs2pr + + - name: "Cache dependencies" + uses: "actions/cache@v2" + with: + path: | + ~/.composer/cache + vendor + key: "php-${{ matrix.php-version }}" + + - name: "Install dependencies" + run: "composer install --no-interaction --no-progress --no-suggest" + + - name: "EasyCodingStandards for Src" + run: "vendor/bin/ecs check src/ features/ tests/ --no-interaction --no-progress-bar" - - name: "Run tests without compiled DI" - run: "bin/behat -swithout_compiled_di" - working-directory: 'behat-magento2-extension' + - name: "PhpStan for Src and Feature Contexts" + run: "vendor/bin/phpstan analyse --error-format=checkstyle src/ features/ --level=8 | cs2pr" - - name: "Run tests with compiled DI" - run: "bin/behat -swith_compiled_di" - working-directory: 'behat-magento2-extension' + - name: "PhpStan for Test" + run: "vendor/bin/phpstan analyse --error-format=checkstyle tests/ --level=6 | cs2pr" diff --git a/.gitignore b/.gitignore index 12520a9..adf74e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,11 @@ -# Composer packages directory /vendor - -# Composer executable files directory -/bin - -# Installed Composer packages versions /composer.lock - -# documentation local build directory -docs/_build +/docs/_build +/.env +/behat.yml +/php_error.log +/.phpunit.result.cache +/.idea +/magento +/test_dir +/docker/data diff --git a/LICENSE b/LICENSE index 83086e9..486a742 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License Copyright (c) 2019 Tibor Kotosz +Copyright (c) 2023 Maximilian Graf Schimmelmann Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index cb51940..19b4a6d 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,16 @@ -BehatMagento2Extension +Behat Magento2 Extension ====================== -[![License](https://poser.pugx.org/bex/behat-magento2-extension/license)](https://packagist.org/packages/bex/behat-magento2-extension) -[![Latest Stable Version](https://poser.pugx.org/bex/behat-magento2-extension/version)](https://packagist.org/packages/bex/behat-magento2-extension) -![Build Status](https://github.com/tkotosz/BehatMagento2Extension/actions/workflows/ci.yml/badge.svg) +[![License](https://poser.pugx.org/nopenopenope/behat-magento2-extension/license)](https://packagist.org/packages/nopenopenope/behat-magento2-extension) +[![Latest Stable Version](https://poser.pugx.org/nopenopenope/behat-magento2-extension/version)](https://packagist.org/packages/nopenopenope/behat-magento2-extension) +![Build Status](https://github.com/nopenopenope/BehatMagento2Extension/actions/workflows/ci.yml/badge.svg) -The `BehatMagento2Extension` provides a custom service container for Behat which allows to inject Magento services into Behat Contexts and Behat helper services. +This is a fork of the [BehatMagentoExtension](https://github.com/tkotosz/BehatMagento2Extension) which is +compatible with PHP7.4 as well as PHP8 and greater. This should ensure successful end-to-end testing of Magento 2 +projects. + +The `BehatMagento2Extension` provides a custom service container for Behat which allows to inject Magento services into +Behat Contexts and Behat helper services. Installation ------------ @@ -13,7 +18,48 @@ Installation The recommended installation method is through [Composer](https://getcomposer.org): ```bash -composer require --dev bex/behat-magento2-extension +composer require seec/behat-magento2-extension +``` + +Usage +----- + +In order to bootstrap Magento2 into your Behat suite, some modifications to the used behat.yml are required. + +**Note**: If you use the Hooks provided by this package, your Magento Database will be purged and refilled with your +fixtures after each individual test. +This adds extra time to the execution but leaves your database also with DUMMY data. Do *not* use the hooks if you want +to keep your database intact. Do *not* use it on a production server if you don't know what you are doing. + + +Testing +------- + +If you want to contribute to this module, make sure to run tests locally before committing. Docker Compose Containers +are set-up to run all tests for all PHP versions automatically, so testing is very easy. + +```bash +$ cp .env.dist .env // make sure to add your keys to the .env file otherwise testing will not work! +$ docker compose build +$ docker compose up -d +$ docker compose exec php sh +$ install-magento +$ install-extension +$ cd /var/www/html/vendor/seec/behat-magento2-extension +$ php vendor/bin/behat +``` + +Code Quality +------------ + +We aim for a unified code style; thus we enforce ECS and PHPStan onto our code. Make sure to run the following commands +before committing: + +```bash +$ php vendor/bin/ecs check src/ tests/ features/ --fix +$ php vendor/bin/phpstan analyse src/ --level=8 +$ php vendor/bin/phpstan analyse features/ --level=8 +$ php vendor/bin/phpstan analyse tests/ --level=5 ``` Documentation diff --git a/behat.yml b/behat.yml index d660718..5f9bf46 100644 --- a/behat.yml +++ b/behat.yml @@ -1,9 +1,11 @@ default: - suites: - without_compiled_di: - contexts: - - Bex\Behat\Magento2Extension\Acceptance\Context\WithoutCompiledDITestRunnerContext + formatters: + progress: true - with_compiled_di: + suites: + default: contexts: - - Bex\Behat\Magento2Extension\Acceptance\Context\WithCompiledDITestRunnerContext + - SEEC\Behat\Magento2Extension\Features\Bootstrap\Context\TestRunnerContext: + workingDirectory: /var/www/html/vendor/seec/behat-magento2-extension/test_dir + - SEEC\BehatTestRunner\Context\TestRunnerContext: + workingDirectory: /var/www/html/vendor/seec/behat-magento2-extension/test_dir diff --git a/behat.yml.dist b/behat.yml.dist index 448f4b2..5f9bf46 100644 --- a/behat.yml.dist +++ b/behat.yml.dist @@ -1,3 +1,11 @@ default: - extensions: - Bex\Behat\Magento2Extension: ~ + formatters: + progress: true + + suites: + default: + contexts: + - SEEC\Behat\Magento2Extension\Features\Bootstrap\Context\TestRunnerContext: + workingDirectory: /var/www/html/vendor/seec/behat-magento2-extension/test_dir + - SEEC\BehatTestRunner\Context\TestRunnerContext: + workingDirectory: /var/www/html/vendor/seec/behat-magento2-extension/test_dir diff --git a/composer.json b/composer.json index 16993e1..dff8e4e 100644 --- a/composer.json +++ b/composer.json @@ -1,9 +1,9 @@ { - "name": "bex/behat-magento2-extension", + "name": "seec/behat-magento2-extension", "type": "behat-extension", "description": "Magento2 extension for Behat", "keywords": ["magento", "magento2", "tdd","bdd","behat"], - "homepage": "https://github.com/tkotosz/BehatMagento2Extension", + "homepage": "https://github.com/nopenopenope/BehatMagento2Extension", "license": "MIT", "authors": [ { @@ -11,34 +11,58 @@ "email": "kotosy@gmail.com", "homepage": "https://github.com/tkotosz", "role": "Developer" + }, + { + "name": "Maximilian Graf Schimmelmann", + "email": "max@schimmelmann.org", + "homepage": "https://www.schimmelmann.org", + "role": "Developer" } ], + "minimum-stability": "dev", "require": { - "php": ">=7.1", - "behat/behat": "^3.5.0", - "magento/framework": ">=100.1", + "php": "^7.4|^8.1", + "behat/behat": "^3.7", + "magento/framework": "^103.0.6", "container-interop/container-interop": "^1.2", - "symfony/dependency-injection": ">=2.0", - "symfony/event-dispatcher": ">=2.0", + "symfony/dependency-injection": "^6", + "symfony/event-dispatcher": "^6", "magento/module-authorization": "*", "magento/module-user": "*", - "magento/module-backend": "*" + "magento/module-backend": "*", + "friends-of-behat/page-object-extension": "^0.3.2", + "friends-of-behat/suite-settings-extension": "^1.1", + "seec/behat-test-runner": "dev-master", + "friends-of-behat/symfony-extension": "^2.0", + "react/promise": "~2.0" }, "require-dev": { + "pdepend/pdepend": "^2.10", + "phpmd/phpmd": "^2.12", + "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^9.5", - "bex/behat-test-runner": "dev-master" + "sebastian/phpcpd": "^6.0", + "symfony/finder": "^5.4", + "symplify/easy-coding-standard": "^11.3", + "symplify/config-transformer": "^12.0", + "phpstan/phpstan-symfony": "^1.3", + "phpstan/phpstan-webmozart-assert": "1.2.x-dev" }, "config": { - "bin-dir": "bin" + "allow-plugins": { + "magento/composer-dependency-version-audit-plugin": true, + "phpstan/extension-installer": true + } }, "autoload": { - "psr-0": { - "Bex\\Behat\\Magento2Extension": "src/" + "psr-4": { + "SEEC\\Behat\\Magento2Extension\\": "src/", + "SEEC\\Behat\\Magento2Extension\\Features\\Bootstrap\\": "features/bootstrap" } }, "autoload-dev": { "psr-4": { - "Bex\\Behat\\Magento2Extension\\Acceptance\\": "features/bootstrap" + "SEEC\\Behat\\Magento2Extension\\Tests\\Unit\\": "tests" } }, "repositories": [ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f9d51f1 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,48 @@ +version: '3.9' + +services: + php: + build: + context: . + dockerfile: ./docker/php/Dockerfile + args: + XDEBUG_VERSION: xdebug + PHP_VERSION: php:cli-alpine3.18 + MAGENTO_PUBLIC_KEY: ${MAGENTO_PUBLIC_KEY} + MAGENTO_SECRET_KEY: ${MAGENTO_SECRET_KEY} + volumes: + - ./:/var/www/html/vendor/seec/behat-magento2-extension + - ./behat.yml:/var/www/html/behat.yml + - ./features:/var/www/html/features + - ./magento:/var/www/html/vendor/magento-ext + environment: + PHP_IDE_CONFIG: serverName=magento2-behat-extension + extra_hosts: + - "host.docker.internal:host-gateway" + env_file: + - .env + + mysql: + image: mariadb:10.6 + environment: + MYSQL_USER: magento + MYSQL_PASSWORD: magento + MYSQL_DATABASE: magento + MYSQL_ROOT_PASSWORD: magento + volumes: + - ./docker/data/database/:/var/lib/mysql/ + ports: + - "9906:3306" + + opensearch: + image: opensearchproject/opensearch:2.7.0 + environment: + discovery.type: single-node + cluster.name: opensearch-cluster + node.name: opensearch-node + bootstrap.memory_lock: true + OPENSEARCH_JAVA_OPTS: -Xms512m -Xmx512m + DISABLE_INSTALL_DEMO_CONFIG: true + DISABLE_SECURITY_PLUGIN: true + ports: + - "8892:9200" diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile new file mode 100644 index 0000000..9ba59b8 --- /dev/null +++ b/docker/php/Dockerfile @@ -0,0 +1,91 @@ +ARG PHP_VERSION=php:cli-alpine3.18 +ARG XDEBUG_VERSION=xdebug +FROM $PHP_VERSION +ARG XDEBUG_VERSION +ARG MAGENTO_PUBLIC_KEY=1 +ARG MAGENTO_SECRET_KEY=1 + +ENV COMPOSER_ALLOW_SUPERUSER=1 +ENV DOCKER_BUILDKIT=1 + +WORKDIR /var/www/ + +RUN apk add --update --no-cache linux-headers +RUN set -xe \ + && apk update \ + && apk add -u \ + git \ + vim \ + nano \ + gpg-agent \ + openssh-client \ + nano \ + wget \ + gnupg \ + unzip \ + autoconf \ + gcc \ + envsubst \ + libxml2-dev \ + zip \ + gd + +RUN apk add --no-cache mysql-client msmtp perl procps shadow libzip libpng libjpeg-turbo libwebp freetype icu icu-data-full libxslt-dev libgcrypt-dev libxml2-dev pcre-dev ${PHPIZE_DEPS} +RUN apk add --no-cache --virtual build-essentials \ + icu-dev icu-libs zlib-dev g++ make automake autoconf libzip-dev \ + libpng-dev libwebp-dev libjpeg-turbo-dev freetype-dev && \ + docker-php-ext-configure gd --enable-gd --with-freetype --with-jpeg --with-webp && \ + docker-php-ext-install gd && \ + docker-php-ext-install mysqli && \ + docker-php-ext-install pdo_mysql && \ + docker-php-ext-install intl && \ + docker-php-ext-install opcache && \ + docker-php-ext-install exif && \ + docker-php-ext-install xml && \ + docker-php-ext-install zip && \ + docker-php-ext-install bcmath && \ + docker-php-ext-install soap && \ + docker-php-ext-install sockets && \ + docker-php-ext-install xsl && \ + pecl install xdebug && \ + docker-php-ext-enable xdebug && \ + apk del build-essentials pcre-dev ${PHPIZE_DEPS} && rm -rf /usr/src/php* + +COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer + +COPY ./docker/php/etc/auth.json.tmpl /tmp/auth.json.tmpl +COPY ./docker/php/etc/999-php-custom.ini /usr/local/etc/php/conf.d/999-php-custom.ini +COPY ./docker/php/etc/entrypoint.sh /usr/local/bin/entrypoint +COPY ./docker/php/etc/test-magento.sh /usr/local/bin/test-magento +COPY ./docker/php/etc/run-ecs.sh /usr/local/bin/run-ecs +COPY ./docker/php/etc/run-phpstan.sh /usr/local/bin/run-phpstan +COPY ./docker/php/etc/run-tests.sh /usr/local/bin/run-tests +COPY ./docker/php/etc/fetch-magento.sh /usr/local/bin/fetch-magento +COPY ./docker/php/etc/install-magento.sh /usr/local/bin/install-magento +COPY ./docker/php/etc/install-extension.sh /usr/local/bin/install-extension +COPY ./docker/php/etc/create-auth-file.sh /usr/local/bin/create-auth-file + +RUN mkdir -p ~/.composer +RUN chmod +x /usr/local/bin/entrypoint +RUN chmod +x /usr/local/bin/test-magento +RUN chmod +x /usr/local/bin/run-ecs +RUN chmod +x /usr/local/bin/run-phpstan +RUN chmod +x /usr/local/bin/run-tests +RUN chmod +x /usr/local/bin/install-magento +RUN chmod +x /usr/local/bin/fetch-magento +RUN chmod +x /usr/local/bin/install-extension +RUN chmod +x /usr/local/bin/create-auth-file + +RUN create-auth-file +RUN rm -rf /var/www/html/* +RUN fetch-magento + +COPY ./ /var/www/html/vendor/seec/behat-magento2-extension/ +RUN install-extension + +COPY ./behat.yml /var/www/html/behat.yml + +WORKDIR /var/www/html + +ENTRYPOINT ["/usr/local/bin/entrypoint"] +CMD ["tail", "-F", "/var/www/html/php_error.log"] diff --git a/docker/php/etc/999-php-custom.ini b/docker/php/etc/999-php-custom.ini new file mode 100644 index 0000000..498432f --- /dev/null +++ b/docker/php/etc/999-php-custom.ini @@ -0,0 +1,26 @@ +xdebug.idekey=PHPSTORM +xdebug.var_display_max_depth=200 +xdebug.mode=debug +xdebug.client_port=9000 +xdebug.client_host=host.docker.internal +xdebug.start_with_request=yes +xdebug.discover_client_host=1 +xdebug.show_error_trace = 1 +xdebug.max_nesting_level=250 +xdebug.log_level=1 + +magic_quotes_gpc = Off; +register_globals = Off; +file_uploads = On; +default_charset = UTF-8; +memory_limit = 4G; +max_execution_time = 600; +upload_max_filesize = 999M; +safe_mode = Off; +mysql.connect_timeout = 20; +allow_url_fopen = true; +display_errors = 1; +error_reporting = E_ALL; +date.timezone = "Europe/Berlin" +error_log=/var/www/html/php_error.log +pm.max_children = 25 diff --git a/docker/php/etc/auth.json.tmpl b/docker/php/etc/auth.json.tmpl new file mode 100644 index 0000000..efa6999 --- /dev/null +++ b/docker/php/etc/auth.json.tmpl @@ -0,0 +1,8 @@ +{ + "http-basic": { + "repo.magento.com": { + "username": "$MAGENTO_PUBLIC_KEY", + "password": "$MAGENTO_SECRET_KEY" + } + } +} diff --git a/docker/php/etc/create-auth-file.sh b/docker/php/etc/create-auth-file.sh new file mode 100644 index 0000000..5a68bd7 --- /dev/null +++ b/docker/php/etc/create-auth-file.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env sh + +echo "Create auth.json" +/usr/bin/envsubst < /tmp/auth.json.tmpl > ~/.composer/auth.json +cat ~/.composer/auth.json diff --git a/docker/php/etc/entrypoint.sh b/docker/php/etc/entrypoint.sh new file mode 100644 index 0000000..c93f6c7 --- /dev/null +++ b/docker/php/etc/entrypoint.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env sh + +create-auth-file + +exec "$@" diff --git a/docker/php/etc/fetch-magento.sh b/docker/php/etc/fetch-magento.sh new file mode 100644 index 0000000..72a2b64 --- /dev/null +++ b/docker/php/etc/fetch-magento.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env sh + +if [ ! -f /var/www/html/composer.json ]; then + rm -rf /var/www/html + composer create-project --repository=https://repo.magento.com/ magento/project-community-edition=2.4.6 /var/www/html/ + cd /var/www/html + composer require --dev behat/behat friends-of-behat/mink-extension behat/mink-goutte-driver tkotosz/test-area-magento2 +fi diff --git a/docker/php/etc/install-extension.sh b/docker/php/etc/install-extension.sh new file mode 100644 index 0000000..60e5454 --- /dev/null +++ b/docker/php/etc/install-extension.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env sh + +cd /var/www/html +echo "Installing Behat Magento 2 Extension in Developer Magento Installation" +composer config repositories.behat-m2-extension path vendor/seec/behat-magento2-extension +composer require seec/behat-magento2-extension:@dev +cd /var/www/html/vendor/seec/behat-magento2-extension +echo "Installing Extension Composer Dependencies" +composer install +cd /var/www/html +composer dump-autoload -o diff --git a/docker/php/etc/install-magento.sh b/docker/php/etc/install-magento.sh new file mode 100644 index 0000000..d68693d --- /dev/null +++ b/docker/php/etc/install-magento.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env sh + +rm -f app/etc/env.php +mkdir -p pub/static pub/media +$(which php) bin/magento setup:install --admin-email "magento@magento.com" \ + --admin-firstname "admin" \ + --admin-lastname "admin" \ + --admin-password "admin123!#" \ + --admin-user "admin" \ + --backend-frontname admin \ + --base-url "http://magento.test" \ + --db-host mysql \ + --db-name magento \ + --db-user root \ + --db-password magento \ + --session-save files \ + --use-rewrites 1 \ + --use-secure 0 \ + --opensearch-host="opensearch" \ + --opensearch-port="9200" \ + --timezone="Europe/Amsterdam" -vvv +$(which php) bin/magento deploy:mode:set developer +composer dump-autoload +$(which php) bin/magento setup:upgrade +composer config minimum-stability dev diff --git a/docker/php/etc/run-ecs.sh b/docker/php/etc/run-ecs.sh new file mode 100644 index 0000000..e2e914a --- /dev/null +++ b/docker/php/etc/run-ecs.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env sh + +cd /var/www/html/behat-magento2-extension +php vendor/bin/ecs check src/ --fix +php vendor/bin/ecs check features/ --fix +php vendor/bin/ecs check tests/ --fix +chown -R 1000:1000 . diff --git a/docker/php/etc/run-phpstan.sh b/docker/php/etc/run-phpstan.sh new file mode 100644 index 0000000..5c8eb24 --- /dev/null +++ b/docker/php/etc/run-phpstan.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env sh + +php vendor/bin/phpstan analyse src/ --level=8 +php vendor/bin/phpstan analyse features/ --level=6 +php vendor/bin/phpstan analyse tests/ --level=6 diff --git a/docker/php/etc/run-tests.sh b/docker/php/etc/run-tests.sh new file mode 100644 index 0000000..fd40e9b --- /dev/null +++ b/docker/php/etc/run-tests.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env sh + +php vendor/bin/phpunit tests/ +php vendor/bin/behat --config /var/www/html/behat-magento2-extension/behat.yml --suite without_compiled_di --strict --stop-on-failure +php vendor/bin/behat --config /var/www/html/behat-magento2-extension/behat.yml --suite with_compiled_di --strict --stop-on-failure diff --git a/docker/php/etc/test-magento.sh b/docker/php/etc/test-magento.sh new file mode 100644 index 0000000..0eb0800 --- /dev/null +++ b/docker/php/etc/test-magento.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env sh + +create-auth-file +install-magento +install-extension +run-ecs +run-phpstan +run-tests diff --git a/docs/conf.py b/docs/conf.py index 92ff60a..00b0e1d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,8 +18,8 @@ # -- Project information ----------------------------------------------------- project = 'Behat Magento 2 Extension' -copyright = '2020, Tibor Kotosz' -author = 'Tibor Kotosz' +copyright = '2023, Tibor Kotosz, Maximilian Graf Schimmelmann' +author = 'Maximilian Graf Schimmelmann' # -- General configuration --------------------------------------------------- @@ -55,4 +55,4 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] \ No newline at end of file +html_static_path = ['_static'] diff --git a/docs/guide/configuration.rst b/docs/guide/configuration.rst index 572b128..8fe2213 100644 --- a/docs/guide/configuration.rst +++ b/docs/guide/configuration.rst @@ -10,7 +10,7 @@ You can enable the extension in your ``behat.yml`` in following way: default: extensions: - Bex\Behat\Magento2Extension: ~ + SEEC\Behat\Magento2Extension: ~ Configure the Service Container ------------------------------- @@ -21,7 +21,7 @@ In order to be able to access the Magento 2 services from your Behat Contexts yo default: suites: yoursuite: - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' With the above configuration Behat will use the service container provided by this extension which makes all services defined in the Magento 2 DI available to inject into any Context. @@ -34,9 +34,9 @@ Note that you need to pass over the dependencies to your Contexts manually like yoursuite: contexts: - YourContext: - - '@Magento\Catalog\Api\ProductRepositoryInterface' + - '@Magento\Catalog\Api\ProductRepositoryInterface' - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' Enable Autowiring for Contexts ------------------------------ @@ -54,7 +54,7 @@ You can enable this feature by adding ``autowire: true`` to the behat config of contexts: - YourContext - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' Note that the argument resolver is able to autowire services for: - constructor arguments @@ -78,7 +78,7 @@ You can configure the required area in the following way: contexts: - YourContext - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' magento: area: adminhtml @@ -156,7 +156,7 @@ In order to load this custom DI configuration during the test run the test area contexts: - YourContext - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' magento: area: test @@ -193,7 +193,7 @@ But don't worry this extension allows you to register your helper services in a default: extensions: - Bex\Behat\Magento2Extension: + SEEC\Behat\Magento2Extension: services: features/bootstrap/config/services.yml Note: You can use ``yml``, ``xml`` or ``php`` format. For more information see the official documentation of the `Symfony DI component `_. @@ -225,7 +225,7 @@ For more information see the official documentation of the `Symfony DI component - YourContext: - '@Magento\Catalog\Api\ProductRepositoryInterface' - '@SharedService' - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' Alternatively if you are using autowiring (see Enable Autowiring for Contexts section) then you can skip this step since the context arguments will be autowired even from this custom Symfony service container. @@ -314,5 +314,5 @@ If your Magento ``bootstrap.php`` is not available in the default ``app/bootstra default: extensions: - Bex\Behat\Magento2Extension: - bootstrap: path/to/your/bootstrap.php # by default app/bootstrap.php \ No newline at end of file + SEEC\Behat\Magento2Extension: + bootstrap: path/to/your/bootstrap.php # by default app/bootstrap.php diff --git a/docs/guide/installation.rst b/docs/guide/installation.rst index d44d094..4d96267 100644 --- a/docs/guide/installation.rst +++ b/docs/guide/installation.rst @@ -15,4 +15,4 @@ The recommended installation method is through `Composer `_ @@ -19,7 +19,7 @@ Similarly you can install the extension via composer: .. code-block:: bash - $ composer require --dev bex/behat-magento2-extension + $ composer require --dev seec/behat-magento2-extension For more information see the the :doc:`installation section of this documentation `. @@ -32,7 +32,7 @@ You need to enable the extension in the Behat configuration and configure your B default: extensions: - Bex\Behat\Magento2Extension: ~ + SEEC\Behat\Magento2Extension: ~ suites: application: @@ -41,7 +41,7 @@ You need to enable the extension in the Behat configuration and configure your B contexts: - FeatureContext - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' With the above configuration: - The extension is enabled diff --git a/docs/guide/usage.rst b/docs/guide/usage.rst index 0c11eaa..870568f 100644 --- a/docs/guide/usage.rst +++ b/docs/guide/usage.rst @@ -9,3 +9,5 @@ Usage Examples usage/inject-service-to-behat-context-as-behat-step-argument usage/inject-service-to-behat-context-as-behat-step-argument-transformer-argument usage/mocking-dependency + purge-database-after-every-scenario-to-ensure-data-correctness.rst + diff --git a/docs/guide/usage/automatically-inject-service-to-behat-context-as-constructor-argument.rst b/docs/guide/usage/automatically-inject-service-to-behat-context-as-constructor-argument.rst index 1baca01..436ab05 100644 --- a/docs/guide/usage/automatically-inject-service-to-behat-context-as-constructor-argument.rst +++ b/docs/guide/usage/automatically-inject-service-to-behat-context-as-constructor-argument.rst @@ -76,4 +76,4 @@ You can enable the `Behat service autowiring feature contexts: - YourContext - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' diff --git a/docs/guide/usage/inject-service-to-behat-context-as-behat-step-argument.rst b/docs/guide/usage/inject-service-to-behat-context-as-behat-step-argument.rst index 196c5c4..92a5c42 100644 --- a/docs/guide/usage/inject-service-to-behat-context-as-behat-step-argument.rst +++ b/docs/guide/usage/inject-service-to-behat-context-as-behat-step-argument.rst @@ -51,4 +51,4 @@ The `Behat service autowiring feature contexts: - YourContext - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' diff --git a/docs/guide/usage/manually-inject-service-to-behat-context-as-constructor-argument.rst b/docs/guide/usage/manually-inject-service-to-behat-context-as-constructor-argument.rst index 8675a8f..1dd0eb5 100644 --- a/docs/guide/usage/manually-inject-service-to-behat-context-as-constructor-argument.rst +++ b/docs/guide/usage/manually-inject-service-to-behat-context-as-constructor-argument.rst @@ -74,6 +74,6 @@ If you didn't enable the Behat autowire feature then you need to provide your Be contexts: - YourContext: - '@Magento\Catalog\Api\ProductRepositoryInterface' - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' That's all. With the above the Product Repository will be injected to your Behat Context. diff --git a/docs/guide/usage/mocking-dependency.rst b/docs/guide/usage/mocking-dependency.rst index 3ffdf76..75cd471 100644 --- a/docs/guide/usage/mocking-dependency.rst +++ b/docs/guide/usage/mocking-dependency.rst @@ -28,8 +28,7 @@ And you have an implementation for this service: class ConfigProvider implements ConfigProviderInterface { - /** @var ScopeConfigInterface */ - private $scopeConfig; + private ScopeConfigInterface $scopeConfig; public function __construct(ScopeConfigInterface $scopeConfig) { @@ -189,7 +188,7 @@ In order to load this custom DI configuration during the test run the test area contexts: - YourContext - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' magento: area: test diff --git a/docs/guide/usage/purge-database-after-every-scenario-to-ensure-data-correctness.rst b/docs/guide/usage/purge-database-after-every-scenario-to-ensure-data-correctness.rst new file mode 100644 index 0000000..bcb1bd6 --- /dev/null +++ b/docs/guide/usage/purge-database-after-every-scenario-to-ensure-data-correctness.rst @@ -0,0 +1,47 @@ +Purge the Database after each scenario +====================================== + +It can be beneficial to purge the database after each scenario in order to work with the data you want, which will +allow you correct test cases in the long run. Please be aware that this will totally truncate each table. Do not use this feature on any kind of +production application, as it will completely wipe your database and will add fixture data to it. + +You can make usage of the Database Hook with the following `behat.yml` configuration: + +.. code-block:: yaml + + default: + suites: + yoursuite: + autowire: true + + contexts: + - YourContext + - SEEC\Behat\Magento2Extension\Features\Bootstrap\Context\Hook\DatabaseHook + + services: '@seec.magento2_extension.service_container' + +With this Hook, Behat will always purge the database before you run a scenario. There are some exceptions to it, as not all +tables are purged to ensure that the Behat Test suite can continue and run properly. The following tables are not purged by the code: + +.. code-block:: php + + $purger->purge($connection, [ + 'core_config_data', + 'eav_attribute', + 'eav_attribute_group', + 'eav_attribute_label', + 'eav_attribute_option', + 'eav_attribute_option_swatch', + 'eav_attribute_option_value', + 'eav_attribute_set', + 'eav_entity_type', + ]); + +Also, the hook will automatically create a default Stock, Website and Store Group and Store View for you. This is necessary +to use various Repositories throughout the testing scenarios, as you can inject them into your Contexts. It may still +happen that various Repositories do not want to get autowired or injected, but you can use always the `ObjectManager` to +get new classes. In Magento2 development, this is strictly discouraged, but for testing purposes its the right way to go; +in the end we want fresh or singleton instances of classes, which are not affected by other tests. + +However: if you see yourself in a situation where you control a class yourself that does not want to get autowired, consider rewriting the class +rather than use the `ObjectManager`. diff --git a/docs/index.rst b/docs/index.rst index 8f590db..eaaf173 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -2,6 +2,9 @@ Behat Magento 2 Extension ========================= This Behat extension provides provides a custom service container for Behat which allows to inject Magento services into Behat Contexts and Behat helper services. +The module ships some default actions, but users are highly encouraged to create their own logic. + +This module is tested for Magento 2.4.x and PHP 7.4 and above. In the future, support for PHP7.4 will be dropped. Guide ----- @@ -17,8 +20,8 @@ Guide References ---------- -- Github Repository: https://github.com/tkotosz/BehatMagento2Extension +- Github Repository: https://github.com/nopenopenope/BehatMagento2Extension -- Packagist: https://packagist.org/packages/bex/behat-magento2-extension +- Packagist: https://packagist.org/packages/seec/behat-magento2-extension -- Behat Official Documentaion: https://docs.behat.org \ No newline at end of file +- Behat Official Documentaion: https://docs.behat.org diff --git a/ecs.php b/ecs.php new file mode 100644 index 0000000..f7e0ea3 --- /dev/null +++ b/ecs.php @@ -0,0 +1,279 @@ +parallel(); + $ecsConfig->sets([SetList::PSR_12]); + $ecsConfig->rule(CastSpacesFixer::class); + $ecsConfig->rule(ClassAttributesSeparationFixer::class); + $ecsConfig->rule(EncodingFixer::class); + $ecsConfig->rule(EregToPregFixer::class); + $ecsConfig->rule(LowercaseCastFixer::class); + $ecsConfig->rule(LowerCaseConstantSniff::class); + $ecsConfig->rule(LowercaseKeywordsFixer::class); + $ecsConfig->rule(LowercaseStaticReferenceFixer::class); + $ecsConfig->rule(MagicConstantCasingFixer::class); + $ecsConfig->rule(ModernizeTypesCastingFixer::class); + $ecsConfig->rule(NativeFunctionCasingFixer::class); + $ecsConfig->rule(NoAliasFunctionsFixer::class); + $ecsConfig->rule(NoBlankLinesAfterClassOpeningFixer::class); + $ecsConfig->rule(NoMultilineWhitespaceAroundDoubleArrowFixer::class); + $ecsConfig->rule(NonPrintableCharacterFixer::class); + $ecsConfig->rule(NoNullPropertyInitializationFixer::class); + $ecsConfig->rule(NoPhp4ConstructorFixer::class); + $ecsConfig->rule(NormalizeIndexBraceFixer::class); + $ecsConfig->rule(NoShortBoolCastFixer::class); + $ecsConfig->rule(NoUnneededFinalMethodFixer::class); + $ecsConfig->rule(NoWhitespaceBeforeCommaInArrayFixer::class); + $ecsConfig->rule(PowToExponentiationFixer::class); + $ecsConfig->rule(ProtectedToPrivateFixer::class); + $ecsConfig->rule(SelfAccessorFixer::class); + $ecsConfig->rule(ShortScalarCastFixer::class); + $ecsConfig->rule(SingleClassElementPerStatementFixer::class); + $ecsConfig->rule(TrailingCommaInMultilineFixer::class); + $ecsConfig->rule(TrimArraySpacesFixer::class); + $ecsConfig->rule(WhitespaceAfterCommaInArrayFixer::class); + $ecsConfig->rule(NoEmptyCommentFixer::class); + $ecsConfig->rule(NoTrailingWhitespaceInCommentFixer::class); + $ecsConfig->rule(CombineConsecutiveIssetsFixer::class); + $ecsConfig->rule(CombineConsecutiveUnsetsFixer::class); + $ecsConfig->rule(DeclareEqualNormalizeFixer::class); + $ecsConfig->rule(DirConstantFixer::class); + $ecsConfig->rule(ElseifFixer::class); + $ecsConfig->rule(FunctionDeclarationFixer::class); + $ecsConfig->rule(FunctionToConstantFixer::class); + $ecsConfig->rule(FunctionTypehintSpaceFixer::class); + $ecsConfig->rule(IncludeFixer::class); + $ecsConfig->rule(IsNullFixer::class); + $ecsConfig->rule(MethodArgumentSpaceFixer::class); + $ecsConfig->rule(NativeConstantInvocationFixer::class); + $ecsConfig->rule(NoBreakCommentFixer::class); + $ecsConfig->rule(NoLeadingImportSlashFixer::class); + $ecsConfig->rule(NoSpacesAfterFunctionNameFixer::class); + $ecsConfig->rule(NoSuperfluousElseifFixer::class); + $ecsConfig->rule(NoUnneededControlParenthesesFixer::class); + $ecsConfig->rule(NoUnneededCurlyBracesFixer::class); + $ecsConfig->rule(NoUnusedImportsFixer::class); + $ecsConfig->rule(NoUselessElseFixer::class); + $ecsConfig->rule(OrderedImportsFixer::class); + $ecsConfig->rule(ReturnTypeDeclarationFixer::class); + $ecsConfig->rule(SingleImportPerStatementFixer::class); + $ecsConfig->rule(SingleLineAfterImportsFixer::class); + $ecsConfig->rule(SwitchCaseSemicolonToColonFixer::class); + $ecsConfig->rule(SwitchCaseSpaceFixer::class); + $ecsConfig->rule(BinaryOperatorSpacesFixer::class); + $ecsConfig->rule(BlankLineAfterNamespaceFixer::class); + $ecsConfig->rule(NoHomoglyphNamesFixer::class); + $ecsConfig->rule(NoLeadingNamespaceWhitespaceFixer::class); + $ecsConfig->rule(BlankLineAfterOpeningTagFixer::class); + $ecsConfig->rule(BlankLineBeforeStatementFixer::class); + $ecsConfig->rule(DeclareStrictTypesFixer::class); + $ecsConfig->rule(FullOpeningTagFixer::class); + $ecsConfig->rule(IndentationTypeFixer::class); + $ecsConfig->rule(LineEndingFixer::class); + $ecsConfig->rule(NewWithBracesFixer::class); + $ecsConfig->rule(NoBlankLinesAfterPhpdocFixer::class); + $ecsConfig->rule(NoClosingTagFixer::class); + $ecsConfig->rule(NoEmptyPhpdocFixer::class); + $ecsConfig->rule(NoEmptyStatementFixer::class); + $ecsConfig->rule(NoSinglelineWhitespaceBeforeSemicolonsFixer::class); + $ecsConfig->rule(NoSpacesAroundOffsetFixer::class); + $ecsConfig->rule(NoSpacesInsideParenthesisFixer::class); + $ecsConfig->rule(NoTrailingWhitespaceFixer::class); + $ecsConfig->rule(NoWhitespaceInBlankLineFixer::class); + $ecsConfig->rule(ObjectOperatorWithoutWhitespaceFixer::class); + $ecsConfig->rule(PhpdocIndentFixer::class); + $ecsConfig->rule(PhpdocNoAccessFixer::class); + $ecsConfig->rule(PhpdocNoAliasTagFixer::class); + $ecsConfig->rule(PhpdocNoEmptyReturnFixer::class); + $ecsConfig->rule(PhpdocNoPackageFixer::class); + $ecsConfig->rule(PhpdocNoUselessInheritdocFixer::class); + $ecsConfig->rule(PhpdocReturnSelfReferenceFixer::class); + $ecsConfig->rule(PhpdocScalarFixer::class); + $ecsConfig->rule(PhpdocSeparationFixer::class); + $ecsConfig->rule(PhpdocSingleLineVarSpacingFixer::class); + $ecsConfig->rule(PhpdocTrimFixer::class); + $ecsConfig->rule(PhpdocTypesFixer::class); + $ecsConfig->rule(PhpdocVarWithoutNameFixer::class); + $ecsConfig->rule(PhpUnitDedicateAssertFixer::class); + $ecsConfig->rule(PhpUnitFqcnAnnotationFixer::class); + $ecsConfig->rule(SingleBlankLineAtEofFixer::class); + $ecsConfig->rule(SingleQuoteFixer::class); + $ecsConfig->rule(SpaceAfterSemicolonFixer::class); + $ecsConfig->rule(StandardizeNotEqualsFixer::class); + $ecsConfig->rule(TernaryOperatorSpacesFixer::class); + $ecsConfig->rule(TernaryToNullCoalescingFixer::class); + $ecsConfig->rule(UnaryOperatorSpacesFixer::class); + $ecsConfig->ruleWithConfiguration(NoMixedEchoPrintFixer::class, ['use' => 'echo']); + $ecsConfig->ruleWithConfiguration(ArraySyntaxFixer::class, ['syntax' => 'short']); + $ecsConfig->ruleWithConfiguration(ClassDefinitionFixer::class, [ + 'single_item_single_line' => true, + 'multi_line_extends_each_single_line' => true, + ]); + $ecsConfig->ruleWithConfiguration(VisibilityRequiredFixer::class, [ + 'elements' => [ + 'const', + 'property', + 'method', + ], + ]); + $ecsConfig->ruleWithConfiguration(SingleLineCommentStyleFixer::class, [ + 'comment_types' => [ + 'hash', + ] + ]); + $ecsConfig->ruleWithConfiguration(ListSyntaxFixer::class, [ + 'syntax' => 'short', + ]); + $ecsConfig->ruleWithConfiguration(ConcatSpaceFixer::class, [ + 'spacing' => 'one', + ]); + $ecsConfig->ruleWithConfiguration(IncrementStyleFixer::class, [ + 'style' => 'pre' + ]); + $ecsConfig->ruleWithConfiguration(NoExtraBlankLinesFixer::class, [ + 'tokens' => [ + 'break', + 'case', + 'continue', + 'curly_brace_block', + 'default', + 'extra', + 'parenthesis_brace_block', + 'return', + 'square_brace_block', + 'switch', + 'throw', + 'use', + ], + ]); + $ecsConfig->ruleWithConfiguration(PhpdocTypesOrderFixer::class, [ + 'null_adjustment' => 'always_last', + 'sort_algorithm' => 'none', + ]); + $ecsConfig->ruleWithConfiguration(NoSuperfluousPhpdocTagsFixer::class, [ + 'allow_mixed' => true, + ]); + + $parameters = $ecsConfig->parameters(); + $parameters->set(Option::PARALLEL_TIMEOUT_IN_SECONDS, 120); + $parameters->set('skip', [ + VisibilityRequiredFixer::class => ['*Spec.php'], + ]); +}; diff --git a/features/bootstrap/Context/AbstractMagentoContext.php b/features/bootstrap/Context/AbstractMagentoContext.php new file mode 100644 index 0000000..d207a19 --- /dev/null +++ b/features/bootstrap/Context/AbstractMagentoContext.php @@ -0,0 +1,16 @@ +sharedStorage = $sharedStorage; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + $this->encryptor = $encryptor; + $this->storeRepository = $storeRepository; + } + + private function getCustomerRepository(): CustomerRepositoryInterface + { + return $this->getObjectManager()->get(CustomerRepositoryInterface::class); + } + + /** + * @Given I have a customer with this data: + * @Given there is a customer with this data; + * @Given there is a customer in store with code :storeCode with this data: + */ + public function thereIsACustomerWIthThisData(TableNode $node, ?string $storeCode = null): void + { + /** @var CustomerInterface $customer */ + $customer = $this->getObjectManager()->create(CustomerInterface::class); + $password = null; + $data = $node->getRowsHash(); + + /** @var StoreInterface $store */ + $store = $storeCode === null + ? $this->sharedStorage->get('store') + : $this->storeRepository->get($storeCode); + + $customer->setStoreId($store->getId()); + $customer->setWebsiteId($store->getWebsiteId()); + + foreach ($data as $key => $value) { + if ($key === 'password') { + $password = $this->encryptor->getHash($value, true); + + continue; + } + $method = sprintf('set%s', ucfirst($key)); + Assert::true(method_exists($customer, $method), sprintf('Method %s does not exist in class %s', $method, get_class($customer))); + $customer->$method($value); + } + + $this->getCustomerRepository()->save($customer, $password); + $this->sharedStorage->set('customer', $customer); + } + + /** + * @Given there is :amount customer existing + * @Given there are :amount customers existing + */ + public function thereAreXCustomerExisting(int $amount): void + { + $searchCriteria = $this->searchCriteriaBuilder->create(); + $collection = $this->getCustomerRepository()->getList($searchCriteria)->getItems(); + $total = count($collection); + Assert::same($total, $amount, sprintf('Expected %s customers, got %s', $amount, $total)); + } + + /** + * @Given there is a customer with email :email existing + */ + public function thereIsACustomerWithEmailExisting(string $email): void + { + $customer = $this->getCustomerRepository()->get($email); + Assert::notNull($customer); + } +} diff --git a/features/bootstrap/Context/Components/ProcessFactory/Input/MagentoCommandInput.php b/features/bootstrap/Context/Components/ProcessFactory/Input/MagentoCommandInput.php new file mode 100644 index 0000000..eb5e6e7 --- /dev/null +++ b/features/bootstrap/Context/Components/ProcessFactory/Input/MagentoCommandInput.php @@ -0,0 +1,24 @@ +setExecutor((new PhpExecutableFinder())->find() ?: null); + $this->setExecutorParameters('-dmemory_limit=-1'); + $this->setCommand('bin/magento'); + $this->setParameters($command); + $this->setExtraParameters($commandParameters); + $this->setDirectory($workingDirectory); + } +} diff --git a/features/bootstrap/Context/Components/Stock/StockContext.php b/features/bootstrap/Context/Components/Stock/StockContext.php new file mode 100644 index 0000000..1f4f399 --- /dev/null +++ b/features/bootstrap/Context/Components/Stock/StockContext.php @@ -0,0 +1,52 @@ +stockRepository = $stockRepository; + $this->sharedStorage = $sharedStorage; + $this->searchCriteriaBuilder = $searchCriteriaFactory; + } + + /** + * @Given the application has a default stock entity + * @Given the application has a stock entity with name :name + */ + public function theApplicationHasStockWithName(string $name = 'Default'): void + { + $filter = $this->searchCriteriaBuilder->addFilter('name', $name)->create(); + $existingStock = $this->stockRepository->getList($filter); + if ($existingStock->getTotalCount() > 0) { + $stock = $existingStock->getItems()[0]; + } else { + /** @var StockINterface $stock */ + $stock = $this->getObjectManager()->create(StockInterface::class); + $stock->setName($name); + $this->stockRepository->save($stock); + } + + Assert::isInstanceOf($stock, StockInterface::class); + $this->sharedStorage->set('stock', $existingStock); + } +} diff --git a/features/bootstrap/Context/Components/Store/StoreContext.php b/features/bootstrap/Context/Components/Store/StoreContext.php new file mode 100644 index 0000000..303b2b5 --- /dev/null +++ b/features/bootstrap/Context/Components/Store/StoreContext.php @@ -0,0 +1,51 @@ +sharedStorage = $sharedStorage; + $this->fixtureFactory = $fixtureFactory; + $this->resourceConnection = $resourceConnection->getConnection(); + } + + /** + * @Given a frontend store-view exists + * @Given a frontend store-view exists with code :code + * @Given a frontend store-view exists with code :code and name :name + */ + public function aFrontendStoreViewExistsWithCodeAndName(string $code = 'test_code', string $name = 'Test Name'): void + { + $this->fixtureFactory->setSharedStorage($this->sharedStorage); + $this->fixtureFactory->createDefaults($this->resourceConnection, $code, $name); + } + + /** + * @Given a backend store-view exists + */ + public function aBackendStoreViewExists(): void + { + $this->fixtureFactory->setSharedStorage($this->sharedStorage); + $this->fixtureFactory->createDefaults($this->resourceConnection, 'admin', 'Admin'); + } +} diff --git a/features/bootstrap/Context/Hook/DatabaseHook.php b/features/bootstrap/Context/Hook/DatabaseHook.php new file mode 100644 index 0000000..45c835e --- /dev/null +++ b/features/bootstrap/Context/Hook/DatabaseHook.php @@ -0,0 +1,60 @@ +resource = $resource; + $this->fixturesManager = $defaultFixtures ?? new DefaultFixtures(); + $this->cacheCleaner = $cacheCleaner ?? new CacheCleaner(); + $this->purger = $purger ?? new Purger(); + } + + /** + * @BeforeScenario + */ + public function purgeAndPrefillWithFixtures(BeforeScenarioScope $scope): void + { + $connection = $this->resource->getConnection(); + + $this->cacheCleaner->clean(); + $this->purger->purge($connection, [ + 'core_config_data', + 'eav_attribute', + 'eav_attribute_group', + 'eav_attribute_label', + 'eav_attribute_option', + 'eav_attribute_option_swatch', + 'eav_attribute_option_value', + 'eav_attribute_set', + 'eav_entity_type', + ]); + $this->fixturesManager->createDefaults($connection); + } +} diff --git a/features/bootstrap/Context/Hook/DatabaseHookInterface.php b/features/bootstrap/Context/Hook/DatabaseHookInterface.php new file mode 100644 index 0000000..96526cd --- /dev/null +++ b/features/bootstrap/Context/Hook/DatabaseHookInterface.php @@ -0,0 +1,12 @@ +magentoPathProvider = $magentoPathProvider ?? new MagentoPathProvider(); + $this->fileSystem = $filesystem ?? new Filesystem(); + $this->finder = $finder ?? new Finder(); + } + + public function clean(bool $cleanObjectManager = true): void + { + $directory = $this->magentoPathProvider->getMagentoRootDirectory(); + $cacheFolder = $this->finder->in(sprintf('%s/var/cache/', $directory)); + foreach ($cacheFolder as $cacheFolderItem) { + if (str_starts_with($cacheFolderItem->getRelativePathname(), '.') === false) { + $this->fileSystem->remove($cacheFolderItem->getPathname()); + } + } + + if ($cleanObjectManager) { + /** @var Config $objectManager */ + $objectManager = ObjectManager::getInstance()->get(Config::class); + $objectManager->clean(); + } + } +} diff --git a/features/bootstrap/Context/Tasks/CacheCleanerInterface.php b/features/bootstrap/Context/Tasks/CacheCleanerInterface.php new file mode 100644 index 0000000..4253b26 --- /dev/null +++ b/features/bootstrap/Context/Tasks/CacheCleanerInterface.php @@ -0,0 +1,10 @@ +sharedStorage = $sharedStorage; + } + + private function getSharedStorage(): ?SharedStorageInterface + { + return $this->sharedStorage; + } + + private function getRepository(string $class): object + { + Assert::interfaceExists($class); + + return $this->getObjectManager()->create($class); + } + + public function createDefaults(AdapterInterface $connection, string $code = 'test_code', string $name = 'Test Name'): void + { + $stock = $this->getOrCreateStock($name, $connection); + $website = $this->getOrCreateWebsite($code, $name); + $group = $this->getOrCreateGroup($code, $name, $website, $connection); + $store = $this->getOrCreateStore($code, $name, $website, $group); + + if ($this->getSharedStorage() instanceof SharedStorage) { + $this->getSharedStorage()->set('stock', $stock); + $this->getSharedStorage()->set('website', $website); + $this->getSharedStorage()->set('group', $group); + $this->getSharedStorage()->set('store', $store); + } + } + + private function getObjectManager(): ObjectManager + { + return ObjectManager::getInstance(); + } + + private function getOrCreateWebsite(string $code, string $name): WebsiteInterface + { + $existing = $this->getExistingEntity(WebsiteInterface::class, $code); + if ($existing !== null) { + Assert::isInstanceOf($existing, WebsiteInterface::class); + + return $existing; + } + + /** @var WebsiteInterface $website */ + $website = $this->getObjectManager()->create(WebsiteInterface::class); + $website->setCode($code); + $website->setName($name); + $website->setDefaultGroupId(1); + $website->setData('is_default', true); /** @phpstan-ignore-line */ + $this->getObjectManager()->create(Website::class)->save($website); + Assert::isInstanceOf($website, WebsiteInterface::class); + + return $website; + } + + private function getOrCreateGroup(string $code, string $name, WebsiteInterface $website, AdapterInterface $connection): GroupInterface + { + $existing = $this->getExistingEntity(GroupInterface::class, $code); + if ($existing !== null) { + Assert::isInstanceOf($existing, GroupInterface::class); + + return $existing; + } + + $group = null; + + try { + /** @var GroupInterface $group */ + $group = $this->getObjectManager()->create(GroupInterface::class); + $group->setCode($code); + $group->setName($name); + $group->setRootCategoryId(1); + $group->setDefaultStoreId(1); + $group->setWebsiteId($website->getId()); + + $this->getObjectManager()->create(Group::class)->save($group); + } catch (NoSuchEntityException $e) { + } catch (Throwable $e) { + $connection->insert('store_group', [ + 'group_id' => 1, + 'website_id' => $website->getId(), + 'code' => $code, + 'name' => $name, + 'root_category_id' => 1, + 'default_store_id' => 1, + ]); + /** @var GroupRepositoryInterface $repository */ + $repository = $this->getObjectManager()->create(GroupRepositoryInterface::class); + $group = $repository->get(1); + } + + Assert::isInstanceOf($group, GroupInterface::class); + + $website->setDefaultGroupId($group->getId()); + $this->getObjectManager()->create(Website::class)->save($website); + + return $group; + } + + private function getOrCreateStore(string $code, string $name, WebsiteInterface $website, GroupInterface $group): StoreInterface + { + $existing = $this->getExistingEntity(StoreInterface::class, $code); + if ($existing !== null) { + Assert::isInstanceOf($existing, StoreInterface::class); + + return $existing; + } + + /** @var StoreInterface $store */ + $store = $this->getObjectManager()->create(StoreInterface::class); + $store->setCode($code); + $store->setName($name); + $store->setWebsiteId($website->getId()); + $store->setStoreGroupId($group->getId()); + $store->setIsActive(1); + + $this->getObjectManager()->create(Store::class)->save($store); + Assert::isInstanceOf($store, StoreInterface::class); + + $group->setDefaultStoreId($store->getId()); + $this->getObjectManager()->create(Group::class)->save($group); + + return $store; + } + + public function getOrCreateStock(string $name, AdapterInterface $connection): StockInterface + { + $existing = $this->getExistingEntity(StockInterface::class, $name); + if ($existing !== null) { + Assert::isInstanceOf($existing, StockInterface::class); + + return $existing; + } + + /** @var StockRepositoryInterface $repo */ + $repo = $this->getRepository(StockRepositoryInterface::class); + + try { + /** @var StockInterface $stock */ + $stock = $this->getObjectManager()->create(StockInterface::class); + $stock->setName($name); + $repo->save($stock); + } catch (DomainException $e) { + $connection->insert('inventory_stock', ['stock_id' => 1, 'name' => $name]); + $stock = $repo->get(1); + } + + Assert::isInstanceOf($stock, StockInterface::class); + + return $stock; + } + + /** + * @return GroupInterface|StoreInterface|WebsiteInterface|StockInterface|null + */ + private function getExistingEntity(string $interface, string $identifier): ?object + { + $checkBy = 'getCode'; + switch ($interface) { + case WebsiteInterface::class: + /** @var WebsiteRepositoryInterface $repository */ + $repository = $this->getRepository(WebsiteRepositoryInterface::class); + $repository->clean(); + + break; + case GroupInterface::class: + /** @var GroupRepositoryInterface $repository */ + $repository = $this->getRepository(GroupRepositoryInterface::class); + $repository->clean(); + + break; + case StoreInterface::class: + /** @var StoreRepositoryInterface $repository */ + $repository = $this->getRepository(StoreRepositoryInterface::class); + $repository->clean(); + + break; + case StockInterface::class: + /** @var StockRepositoryInterface $repository */ + $repository = $this->getRepository(StockRepositoryInterface::class); + $checkBy = 'getName'; + + break; + default: + throw new \InvalidArgumentException('Interface not found'); + } + + $existingList = is_array($repository->getList()) + ? $repository->getList() + : $repository->getList()->getItems(); + + foreach ($existingList as $existing) { + if ($existing->$checkBy() === $identifier) { + Assert::isInstanceOf($existing, $interface); + + return $existing; + } + } + + return null; + } +} diff --git a/features/bootstrap/Context/Tasks/DefaultFixturesInterface.php b/features/bootstrap/Context/Tasks/DefaultFixturesInterface.php new file mode 100644 index 0000000..8d058f4 --- /dev/null +++ b/features/bootstrap/Context/Tasks/DefaultFixturesInterface.php @@ -0,0 +1,13 @@ +query('SET FOREIGN_KEY_CHECKS = 0'); + $tables = $connection->getTables(); + foreach ($tables as $table) { + if (in_array($table, $excludedTables)) { + continue; + } + + $count = $connection->select()->from($table, 'COUNT(*)'); + $result = (int) $connection->fetchOne($count); + if ($result === 0) { + continue; + } + + $sql = sprintf('TRUNCATE TABLE %s', $table); + $connection->query($sql); + } + $connection->query('SET FOREIGN_KEY_CHECKS = 1'); + } +} diff --git a/features/bootstrap/Context/Tasks/PurgerInterface.php b/features/bootstrap/Context/Tasks/PurgerInterface.php new file mode 100644 index 0000000..b0517b3 --- /dev/null +++ b/features/bootstrap/Context/Tasks/PurgerInterface.php @@ -0,0 +1,12 @@ +filesystem = $fileSystem ?: new Filesystem(); + $this->processFactory = $processFactory ?: new ProcessFactory(); + $this->magentoPathProvider = $magentoPathProvider ?? new MagentoPathProvider(); + $this->cacheCleaner = $cacheCleaner ?? new CacheCleaner($this->magentoPathProvider, $this->filesystem); + parent::__construct($fileSystem, $processFactory, $workingDirectoryService, $workingDirectory); } - /** - * @Then I should see the tests passing - */ - public function iShouldSeeTheTestsPassing() + public function createWorkingDirectory(): void { - $this->iShouldNotSeeAFailingTest(); + $this->determineFreshWorkingDirectoryFlag(); + parent::createWorkingDirectory(); + + $this->filesystem->copy( + sprintf('%s/app/etc/config.php', $this->getMagentoRootDirectory()), + '/tmp/config.php.backup', + true + ); } - public function createWorkingDirectory() + public function clearWorkingDirectory(): void { - parent::createWorkingDirectory(); + parent::clearWorkingDirectory(); + $this->removeEmptyWorkingDirectory(); + $this->removeModuleFolders(); + $this->revertMagentoConfig(); + } - $this->filesystem->copy($this->workingDirectory . '/app/etc/config.php', '/tmp/config.php.backup', true); + private function setModulePath(string $modulePath): void + { + $this->modulePath = $modulePath; } - public function clearWorkingDirectory() + private function getModulePath(): ?string { - parent::clearWorkingDirectory(); + return $this->modulePath; + } - $this->filesystem->copy('/tmp/config.php.backup', $this->workingDirectory . '/app/etc/config.php', true); - $this->filesystem->remove('/tmp/config.php.backup'); - $this->runMagentoCommand('cache:clear'); + /** + * @Given I have no Magento module called :moduleName + */ + public function iHaveNoMagentoModuleCalledX(string $moduleName): void + { + [$vendor, $module] = explode('_', $moduleName); + $modulePath = sprintf('%s/app/code/%s/%s', $this->getMagentoRootDirectory(), $vendor, $module); + + $this->filesystem->remove($modulePath); + Assert::false($this->filesystem->exists($modulePath)); } /** * @Given I have a Magento module called :moduleName */ - public function iHaveAMagentoModuleCalled(string $moduleName) + public function iHaveAMagentoModuleCalled(string $moduleName): void { [$vendor, $module] = explode('_', $moduleName); - $this->modulePath = $this->workingDirectory . '/app/code/' . $vendor . '/' . $module; - $registrationFile = $this->modulePath . '/registration.php'; - $moduleFile = $this->modulePath . '/etc/module.xml'; + $modulePath = sprintf('%s/app/code/%s/%s', $this->getMagentoRootDirectory(), $vendor, $module); + $registrationFile = sprintf('%s/registration.php', $modulePath); + $moduleFile = sprintf('%s/etc/module.xml', $modulePath); + $this->setModulePath($modulePath); $registrationFileContent = <<filesystem->dumpFile($registrationFile, $registrationFileContent); - $this->filesystem->dumpFile($moduleFile, $moduleFileContent); - - $this->files[] = $registrationFile; - $this->files[] = $moduleFile; - - $this->runMagentoCommand('module:enable', $moduleName); + $this->createFile($registrationFile, $registrationFileContent); + $this->createFile($moduleFile, $moduleFileContent); + //$this->runMagentoCommand('module:enable', $moduleName); } /** * @Given I have an interface :fqcn defined in this module: * @Given I have a class :fqcn defined in this module: */ - public function iHaveAnInterfaceDefinedInThisModule(string $fqcn, PyStringNode $content) + public function iHaveAnInterfaceDefinedInThisModule(string $fqcn, PyStringNode $content): void { - $file = $this->workingDirectory . '/app/code/' . str_replace('\\', '/', $fqcn) . '.php'; - - $this->filesystem->dumpFile($file, $content->getRaw()); - - $this->files[] = $file; + $file = sprintf('%s/app/code/%s.php', $this->getMagentoRootDirectory(), str_replace('\\', '/', $fqcn)); + $this->createFile($file, $content->getRaw()); } /** * @Given I have a global Magento DI configuration in this module: */ - public function iHaveAGlobalMagentoDiConfigurationInThisModule(PyStringNode $content) + public function iHaveAGlobalMagentoDiConfigurationInThisModule(PyStringNode $content): void { - $file = $this->modulePath . '/etc/di.xml'; - - $this->filesystem->dumpFile($file, $content->getRaw()); - - $this->files[] = $file; + $file = sprintf('%s/etc/di.xml', $this->getModulePath()); + $this->createFile($file, $content->getRaw()); $this->runMagentoCommand('cache:clean'); } @@ -114,70 +151,141 @@ public function iHaveAGlobalMagentoDiConfigurationInThisModule(PyStringNode $con /** * @Given I have a frontend Magento DI configuration in this module: */ - public function iHaveAFrontendMagentoDIConfigurationInThisModule(PyStringNode $content) + public function iHaveAFrontendMagentoDIConfigurationInThisModule(PyStringNode $content): void { - $file = $this->modulePath . '/etc/frontend/di.xml'; - - $this->filesystem->dumpFile($file, $content->getRaw()); - - $this->files[] = $file; + $file = sprintf('%s/etc/frontend/di.xml', $this->getModulePath()); + $this->createFile($file, $content->getRaw()); } /** * @Given I have an adminhtml Magento DI configuration in this module: */ - public function iHaveAnAdminhtmlMagentoDIConfigurationInThisModule(PyStringNode $content) + public function iHaveAnAdminhtmlMagentoDIConfigurationInThisModule(PyStringNode $content): void { - $file = $this->modulePath . '/etc/adminhtml/di.xml'; - - $this->filesystem->dumpFile($file, $content->getRaw()); - - $this->files[] = $file; + $file = sprintf('%s/etc/adminhtml/di.xml', $this->modulePath); + $this->createFile($file, $content->getRaw()); } /** * @Given I have a test Magento DI configuration in this module: */ - public function iHaveATestMagentoDiConfigurationInThisModule(PyStringNode $content) + public function iHaveATestMagentoDiConfigurationInThisModule(PyStringNode $content): void { - $file = $this->modulePath . '/etc/test/di.xml'; - - $this->filesystem->dumpFile($file, $content->getRaw()); - - $this->files[] = $file; + $file = sprintf('%s/etc/test/di.xml', $this->modulePath); + $this->createFile($file, $content->getRaw()); } /** * @Given I have the helper service configuration: */ - public function iHaveTheHelperServiceConfiguration(PyStringNode $content) + public function iHaveTheHelperServiceConfiguration(PyStringNode $content): void { - $file = $this->workingDirectory . '/features/bootstrap/config/services.yml'; - - $this->filesystem->dumpFile($file, $content->getRaw()); - - $this->files[] = $file; + $file = sprintf('%s/features/bootstrap/config/services.yml', $this->getWorkingDirectory()); + $this->createFile($file, $content->getRaw()); } /** * @Given /^the behat helper service class file "([^"]*)" contains:$/ */ - public function theBehatHelperServiceClassFileContains(string $className, PyStringNode $content) + public function theBehatHelperServiceClassFileContains(string $className, PyStringNode $content): void { - $file = $this->workingDirectory . '/features/bootstrap/' . str_replace('\\', '/', $className) . '.php'; + $file = sprintf( + '%s/features/bootstrap/%s.php', + $this->getWorkingDirectory(), + str_replace('\\', '/', $className) + ); + $this->createFile($file, $content->getRaw()); + } - $this->filesystem->dumpFile($file, $content->getRaw()); + /** + * @Given I compile the DI + */ + public function iCompileTheDi(): void + { + $this->iRunTheMagentoCommand('setup:di:compile'); + } - $this->files[] = $file; + /** + * @Given I :clearOrFlush the cache + */ + public function iCleanOrFlushTheCache(string $cleanOrFlush): void + { + Assert::inArray($cleanOrFlush, ['clean', 'flush'], 'Can only clean or flush the cache'); + $this->iRunTheMagentoCommand(sprintf('cache:%s', $cleanOrFlush)); + } + + /** + * @Given I run the Magento command :command + * @Given I run the Magento command :command with arguments :arguments + */ + public function iRunTheMagentoCommand(string $command, ?string $arguments = null): void + { + $this->runMagentoCommand($command, $arguments); } - protected function runMagentoCommand(string $command, string $arguments = '') + protected function runMagentoCommand(?string $command = null, ?string $arguments = null): void { - $magentoProcess = new Process( - sprintf('%s %s %s', 'bin/magento', $command, !empty($arguments) ? escapeshellarg($arguments) : ''), - $this->workingDirectory + $magentoProcess = $this->processFactory->createFromInput( + new MagentoCommandInput($command, $arguments, $this->getMagentoRootDirectory()) ); - $magentoProcess->setTimeout(120); + + $this->addProcess($magentoProcess); + $magentoProcess->setTimeout(600); $magentoProcess->run(); + Assert::same( + $magentoProcess->getExitCode(), + 0, + sprintf( + 'Expected Exit Code of Magento Process to be 0, got %d with message %s', + $magentoProcess->getExitCode(), + $magentoProcess->getErrorOutput() + ) + ); + } + + private function getMagentoRootDirectory(): string + { + if ($this->magentoRootDirectory === null) { + $this->magentoRootDirectory = $this->magentoPathProvider->getMagentoRootDirectory(); + } + + return $this->magentoRootDirectory; + } + + private function revertMagentoConfig(): void + { + if ($this->filesystem->exists('/tmp/config.php.backup')) { + $this->filesystem->copy( + '/tmp/config.php.backup', + sprintf('%s/app/etc/config.php', $this->getMagentoRootDirectory()), + true + ); + $this->filesystem->remove('/tmp/config.php.backup'); + $this->cacheCleaner->clean(false); + } + } + + private function removeModuleFolders(): void + { + $modulePath = $this->getModulePath(); + if ($modulePath && $this->filesystem->exists($modulePath)) { + $this->filesystem->remove($modulePath); + } + } + + private function determineFreshWorkingDirectoryFlag(): void + { + $directory = $this->getWorkingDirectory(); + Assert::string($directory, 'Working directory is not a string'); + $this->isFreshWorkingDirectory = $this->filesystem->exists($directory) === false; + } + + private function removeEmptyWorkingDirectory(): void + { + $directory = $this->getWorkingDirectory(); + Assert::string($directory, 'Working directory is not a string'); + if ($this->isFreshWorkingDirectory === true) { + $this->filesystem->remove($directory); + } } } diff --git a/features/bootstrap/Context/WithCompiledDITestRunnerContext.php b/features/bootstrap/Context/WithCompiledDITestRunnerContext.php deleted file mode 100644 index fd38b42..0000000 --- a/features/bootstrap/Context/WithCompiledDITestRunnerContext.php +++ /dev/null @@ -1,12 +0,0 @@ -runMagentoCommand('setup:di:compile'); - parent::iRunBehat($parameters, $phpParameters); - } -} diff --git a/features/bootstrap/Context/WithoutCompiledDITestRunnerContext.php b/features/bootstrap/Context/WithoutCompiledDITestRunnerContext.php deleted file mode 100644 index af60bd7..0000000 --- a/features/bootstrap/Context/WithoutCompiledDITestRunnerContext.php +++ /dev/null @@ -1,12 +0,0 @@ -runMagentoCommand('cache:clear'); - parent::iRunBehat($parameters, $phpParameters); - } -} diff --git a/features/helper_services.feature b/features/helper_services.feature index 019badd..2beadcf 100644 --- a/features/helper_services.feature +++ b/features/helper_services.feature @@ -1,16 +1,19 @@ +@virtual Feature: Using helper services to access services outside of Magento As a developer In order to write Behat tests easily I should be able to inject services from an additional helper service container - Scenario: Inject simple helper service to Context + Background: Given I have the feature: """ Feature: My awesome feature Scenario: Given a helper service has been successfully injected as argument to this step """ - And I have the context: + + Scenario: Inject simple helper service to Context + Given I have the context: """ another()); Assert::assertInstanceOf(OrderRepositoryInterface::class, $sharedService->orderRepository()); - Assert::assertInstanceOf(Mink::class, $sharedService->mink()); Assert::assertNotEmpty($sharedService->basePath()); } } @@ -104,32 +99,23 @@ Feature: Using helper services to access services outside of Magento """ anotherSharedService = $anotherSharedService; $this->orderRepository = $orderRepository; - $this->mink = $mink; $this->basePath = $basePath; } @@ -143,11 +129,6 @@ Feature: Using helper services to access services outside of Magento return $this->orderRepository; } - public function mink(): Mink - { - return $this->mink; - } - public function basePath(): string { return $this->basePath; @@ -180,7 +161,6 @@ Feature: Using helper services to access services outside of Magento arguments: - '@AnotherSharedService' - '@Magento\Sales\Api\OrderRepositoryInterface' - - '@mink' - '%paths.base%' """ And I have the configuration: @@ -191,35 +171,23 @@ Feature: Using helper services to access services outside of Magento autowire: true contexts: - FeatureContext - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' extensions: - Bex\Behat\Magento2Extension: + SEEC\Behat\Magento2Extension: services: features/bootstrap/config/services.yml - Behat\MinkExtension: - base_url: 'http://example.com' - sessions: - default: - goutte: ~ """ When I run Behat Then I should see the tests passing Scenario: Autowire helper service dependencies - Given I have the feature: - """ - Feature: My awesome feature - Scenario: - Given a helper service has been successfully injected as argument to this step - """ - And I have the context: + Given I have the context: """ another()); Assert::assertInstanceOf(OrderRepositoryInterface::class, $sharedService->orderRepository()); - Assert::assertInstanceOf(Mink::class, $sharedService->mink()); Assert::assertNotEmpty($sharedService->basePath()); } } @@ -246,27 +213,19 @@ Feature: Using helper services to access services outside of Magento class SharedService { - /** @var AnotherSharedService */ - private $anotherSharedService; - - /** @var OrderRepositoryInterface */ - private $orderRepository; + private AnotherSharedService $anotherSharedService; - /** @var Mink */ - private $mink; + private OrderRepositoryInterface $orderRepository; - /** @var string */ - private $basePath; + private string $basePath; public function __construct( AnotherSharedService $anotherSharedService, OrderRepositoryInterface $orderRepository, - Mink $mink, string $basePath ) { $this->anotherSharedService = $anotherSharedService; $this->orderRepository = $orderRepository; - $this->mink = $mink; $this->basePath = $basePath; } @@ -280,11 +239,6 @@ Feature: Using helper services to access services outside of Magento return $this->orderRepository; } - public function mink(): Mink - { - return $this->mink; - } - public function basePath(): string { return $this->basePath; @@ -316,7 +270,6 @@ Feature: Using helper services to access services outside of Magento SharedService: class: SharedService arguments: - $mink: '@mink' $basePath: '%paths.base%' """ And I have the configuration: @@ -327,16 +280,11 @@ Feature: Using helper services to access services outside of Magento autowire: true contexts: - FeatureContext - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' extensions: - Bex\Behat\Magento2Extension: + SEEC\Behat\Magento2Extension: services: features/bootstrap/config/services.yml - Behat\MinkExtension: - base_url: 'http://example.com' - sessions: - default: - goutte: ~ """ When I run Behat Then I should see the tests passing diff --git a/features/injecting_service_through_a_step_argument.feature b/features/injecting_service_through_a_step_argument.feature index 2a0bba8..ff52053 100644 --- a/features/injecting_service_through_a_step_argument.feature +++ b/features/injecting_service_through_a_step_argument.feature @@ -1,3 +1,4 @@ +@virtual Feature: Injecting service from Magento DI to Behat Context through a Behat Step argument As a developer In order to write Behat tests easily @@ -37,10 +38,11 @@ Feature: Injecting service from Magento DI to Behat Context through a Behat Step autowire: true contexts: - FeatureContext - services: '@bex.magento2_extension.service_container' + - SEEC\Behat\Magento2Extension\Features\Bootstrap\Context\Hook\DatabaseHook + services: '@seec.magento2_extension.service_container' extensions: - Bex\Behat\Magento2Extension: ~ + SEEC\Behat\Magento2Extension: ~ """ When I run Behat Then I should see the tests passing diff --git a/features/injecting_service_through_a_transformer_argument.feature b/features/injecting_service_through_a_transformer_argument.feature index bdf403e..cbfd7f9 100644 --- a/features/injecting_service_through_a_transformer_argument.feature +++ b/features/injecting_service_through_a_transformer_argument.feature @@ -1,3 +1,4 @@ +@virtual Feature: Injecting service from Magento DI to Behat Context through a Transformer argument As a developer In order to write Behat tests easily @@ -52,10 +53,10 @@ Feature: Injecting service from Magento DI to Behat Context through a Transforme autowire: true contexts: - FeatureContext - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' extensions: - Bex\Behat\Magento2Extension: ~ + SEEC\Behat\Magento2Extension: ~ """ When I run Behat Then I should see the tests passing diff --git a/features/injecting_service_through_the_context_constructor.feature b/features/injecting_service_through_the_context_constructor.feature index 009632a..b7e2264 100644 --- a/features/injecting_service_through_the_context_constructor.feature +++ b/features/injecting_service_through_the_context_constructor.feature @@ -1,3 +1,4 @@ +@virtual Feature: Injecting service from Magento DI to Behat Context through the constructor As a developer In order to write Behat tests easily @@ -47,10 +48,10 @@ Feature: Injecting service from Magento DI to Behat Context through the construc contexts: - FeatureContext: - '@Magento\Catalog\Api\ProductRepositoryInterface' - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' extensions: - Bex\Behat\Magento2Extension: ~ + SEEC\Behat\Magento2Extension: ~ """ When I run Behat Then I should see the tests passing @@ -64,10 +65,10 @@ Feature: Injecting service from Magento DI to Behat Context through the construc autowire: true contexts: - FeatureContext - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' extensions: - Bex\Behat\Magento2Extension: ~ + SEEC\Behat\Magento2Extension: ~ """ When I run Behat Then I should not see a failing test diff --git a/features/mering_di_configurations.feature b/features/merging_di_configurations.feature similarity index 82% rename from features/mering_di_configurations.feature rename to features/merging_di_configurations.feature index bda7ff1..d3168ae 100644 --- a/features/mering_di_configurations.feature +++ b/features/merging_di_configurations.feature @@ -1,7 +1,18 @@ +@virtual @merging Feature: Merging DI configurations Background: - Given I have a Magento module called "Acme_FooBar" + Given I have the feature: + """ + Feature: FooBar + + Scenario: Fake Foo with Real Bar + Given The foo service is "Acme\FooBar\Test\Service\FakeFoo" + And The bar service is "Acme\FooBar\Service\Bar" + Then The merge is correct + """ + And I have no Magento module called "Acme_FooBar" + And I have a Magento module called "Acme_FooBar" And I have an interface "Acme\FooBar\Service\FooInterface" defined in this module: """ foo); } /** @Given The bar service is :expected */ - public function checkBar($expected, FooBar $foobar) + public function checkBar($expected, FooBar $foobar): void { Assert::assertInstanceof($expected, $foobar->bar); } /** @Then The merge is correct */ - public function yay() {} + public function yay(): void {} } """ + And I flush the cache + And I run the Magento command "module:enable" with arguments "Acme_FooBar" Scenario: Merging global and test area correctly Given I have a global Magento DI configuration in this module: @@ -139,15 +149,6 @@ Feature: Merging DI configurations """ - And I have the feature: - """ - Feature: FooBar - - Scenario: Fake Foo with Real Bar - Given The foo service is "Acme\FooBar\Test\Service\FakeFoo" - And The bar service is "Acme\FooBar\Service\Bar" - Then The merge is correct - """ And I have the configuration: """ default: @@ -156,12 +157,12 @@ Feature: Merging DI configurations autowire: true contexts: - FeatureContext - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' magento: area: test extensions: - Bex\Behat\Magento2Extension: ~ + SEEC\Behat\Magento2Extension: ~ """ When I run Behat Then I should see the tests passing @@ -190,15 +191,6 @@ Feature: Merging DI configurations """ - And I have the feature: - """ - Feature: FooBar - - Scenario: Fake Foo with Real Bar - Given The foo service is "Acme\FooBar\Test\Service\FakeFoo" - And The bar service is "Acme\FooBar\Service\Bar" - Then The merge is correct - """ And I have the configuration: """ default: @@ -207,12 +199,12 @@ Feature: Merging DI configurations autowire: true contexts: - FeatureContext - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' magento: area: [frontend, test] extensions: - Bex\Behat\Magento2Extension: ~ + SEEC\Behat\Magento2Extension: ~ """ When I run Behat Then I should see the tests passing @@ -241,15 +233,6 @@ Feature: Merging DI configurations """ - And I have the feature: - """ - Feature: FooBar - - Scenario: Fake Foo with Real Bar - Given The foo service is "Acme\FooBar\Test\Service\FakeFoo" - And The bar service is "Acme\FooBar\Service\Bar" - Then The merge is correct - """ And I have the configuration: """ default: @@ -258,12 +241,12 @@ Feature: Merging DI configurations autowire: true contexts: - FeatureContext - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' magento: area: [adminhtml, test] extensions: - Bex\Behat\Magento2Extension: ~ + SEEC\Behat\Magento2Extension: ~ """ When I run Behat Then I should see the tests passing @@ -298,15 +281,6 @@ Feature: Merging DI configurations """ - And I have the feature: - """ - Feature: FooBar - - Scenario: Fake Foo with Real Bar - Given The foo service is "Acme\FooBar\Test\Service\FakeFoo" - And The bar service is "Acme\FooBar\Service\Bar" - Then The merge is correct - """ And I have the configuration: """ default: @@ -315,12 +289,12 @@ Feature: Merging DI configurations autowire: true contexts: - FeatureContext - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' magento: area: [adminhtml, test] extensions: - Bex\Behat\Magento2Extension: ~ + SEEC\Behat\Magento2Extension: ~ """ When I run Behat Then I should see the tests passing @@ -351,15 +325,6 @@ Feature: Merging DI configurations """ - And I have the feature: - """ - Feature: FooBar - - Scenario: Fake Foo with Real Bar - Given The foo service is "Acme\FooBar\Test\Service\FakeFoo" - And The bar service is "Acme\FooBar\Service\Bar" - Then The merge is correct - """ And I have the configuration: """ default: @@ -368,12 +333,12 @@ Feature: Merging DI configurations autowire: true contexts: - FeatureContext - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' magento: area: [adminhtml, test] extensions: - Bex\Behat\Magento2Extension: ~ + SEEC\Behat\Magento2Extension: ~ """ When I run Behat - Then I should see the tests passing \ No newline at end of file + Then I should see the tests passing diff --git a/features/mocking.feature b/features/mocking.feature index 25a8add..c93b951 100644 --- a/features/mocking.feature +++ b/features/mocking.feature @@ -1,3 +1,4 @@ +@virtual @mocking Feature: Mocking As a developer In order to write Behat tests easily @@ -220,6 +221,7 @@ Feature: Mocking } } """ + And I run the Magento command "module:enable" with arguments "Acme_Awesome" Scenario: Override global service dependency using preference Given I have a global Magento DI configuration in this module: @@ -244,12 +246,12 @@ Feature: Mocking autowire: true contexts: - FeatureContext - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' magento: area: test extensions: - Bex\Behat\Magento2Extension: ~ + SEEC\Behat\Magento2Extension: ~ """ When I run Behat Then I should see the tests passing @@ -281,12 +283,12 @@ Feature: Mocking autowire: true contexts: - FeatureContext - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' magento: area: test extensions: - Bex\Behat\Magento2Extension: ~ + SEEC\Behat\Magento2Extension: ~ """ When I run Behat Then I should see the tests passing @@ -314,12 +316,12 @@ Feature: Mocking autowire: true contexts: - FeatureContext - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' magento: area: [frontend, test] extensions: - Bex\Behat\Magento2Extension: ~ + SEEC\Behat\Magento2Extension: ~ """ When I run Behat Then I should see the tests passing @@ -351,12 +353,12 @@ Feature: Mocking autowire: true contexts: - FeatureContext - services: '@bex.magento2_extension.service_container' + services: '@seec.magento2_extension.service_container' magento: area: [frontend, test] extensions: - Bex\Behat\Magento2Extension: ~ + SEEC\Behat\Magento2Extension: ~ """ When I run Behat - Then I should see the tests passing \ No newline at end of file + Then I should see the tests passing diff --git a/features/purging_feature.feature b/features/purging_feature.feature new file mode 100644 index 0000000..0612632 --- /dev/null +++ b/features/purging_feature.feature @@ -0,0 +1,87 @@ +@purge @fixtureCreation +Feature: Using helper services to access services outside of Magento + As a developer + In order to write Behat tests easily + I should be able to inject services from an additional helper service container + + Background: + Given I have the context: + """ + foo()); + } + } + """ + And the behat helper service class file "SharedService" contains: + """ + basePath = $basePath; - } - - public function create(Config $config, array $symfonyServiceContainers): DelegatingSymfonyServiceContainer - { - $container = new DelegatingSymfonyServiceContainer($symfonyServiceContainers); - - if (($file = $config->getServicesPath()) !== null) { - $fileLocator = new FileLocator([$this->basePath]); - $loader = new DelegatingLoader( - new LoaderResolver([ - new XmlFileLoader($container, $fileLocator), - new YamlFileLoader($container, $fileLocator), - new PhpFileLoader($container, $fileLocator), - ]) - ); - $loader->load($file); - } - - $container->compile(); - - return $container; - } -} diff --git a/src/Bex/Behat/Magento2Extension/Service/MagentoObjectManager.php b/src/Bex/Behat/Magento2Extension/Service/MagentoObjectManager.php deleted file mode 100644 index 25346aa..0000000 --- a/src/Bex/Behat/Magento2Extension/Service/MagentoObjectManager.php +++ /dev/null @@ -1,18 +0,0 @@ -get($id); - } - - public function create(string $id) - { - return ObjectManager::getInstance()->create($id); - } -} diff --git a/src/Bex/Behat/Magento2Extension/ServiceContainer/Magento2Extension.php b/src/Bex/Behat/Magento2Extension/ServiceContainer/Magento2Extension.php deleted file mode 100644 index 5adb93d..0000000 --- a/src/Bex/Behat/Magento2Extension/ServiceContainer/Magento2Extension.php +++ /dev/null @@ -1,53 +0,0 @@ -children() - ->scalarNode(Config::CONFIG_KEY_MAGENTO_BOOTSTRAP) - ->defaultValue(getcwd() . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'bootstrap.php') - ->end() - ->scalarNode(Config::CONFIG_KEY_SERVICES) - ->defaultValue(null) - ->end() - ->end(); - } - - public function load(ContainerBuilder $container, array $config) - { - $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/config')); - $loader->load('services.xml'); - $extensionConfig = new Config($config); - $container->set(self::SERVICE_ID_EXTENSION_CONFIG, $extensionConfig); - $container->set('bex.behat_service_container', $container); - } - - public function process(ContainerBuilder $container) - { - // nothing to do here - } -} diff --git a/src/Bex/Behat/Magento2Extension/ServiceContainer/config/services.xml b/src/Bex/Behat/Magento2Extension/ServiceContainer/config/services.xml deleted file mode 100644 index 8887b93..0000000 --- a/src/Bex/Behat/Magento2Extension/ServiceContainer/config/services.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - %paths.base% - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Components/SharedStorage/SharedStorage.php b/src/Components/SharedStorage/SharedStorage.php new file mode 100644 index 0000000..a0d2442 --- /dev/null +++ b/src/Components/SharedStorage/SharedStorage.php @@ -0,0 +1,34 @@ +data[$key] = $value; + } + + public function get(string $key) + { + Assert::keyExists($this->data, $key); + + return $this->data[$key]; + } + + public function has(string $key): bool + { + return isset($this->data[$key]); + } + + public function setClipboard(array $clipboard): void + { + $this->data = array_merge($this->data, $clipboard); + } +} diff --git a/src/Components/SharedStorage/SharedStorageAwareInterface.php b/src/Components/SharedStorage/SharedStorageAwareInterface.php new file mode 100644 index 0000000..32ce620 --- /dev/null +++ b/src/Components/SharedStorage/SharedStorageAwareInterface.php @@ -0,0 +1,10 @@ +fallbackContainers = $symfonyServiceContainers; } - public function has($id) + public function has(string $id): bool { - if (!$this->isSupportedServiceId($id)) { + if ($this->isPageObject($id)) { return false; } @@ -38,49 +44,45 @@ public function has($id) return false; } - public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE) + public function get(string $id, int $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE): ?object { - if (!$this->isSupportedServiceId($id)) { + if ($this->isPageObject($id)) { return null; } try { return parent::get($id); } catch (ServiceNotFoundException $e) { - // no-op continue } foreach ($this->fallbackContainers as $serviceContainer) { try { return $serviceContainer->get($id); - } catch (\Exception $e) { - // no-op continue + } catch (Throwable $e) { } } throw new ServiceNotFoundException($id); } - public function getDefinition($id) + public function getDefinition(string $id): Definition { try { return parent::getDefinition($id); } catch (ServiceNotFoundException $e) { - // no-op continue } foreach ($this->fallbackContainers as $serviceContainer) { try { return $serviceContainer->getDefinition($id); } catch (ServiceNotFoundException $e) { - // no-op continue } } throw new ServiceNotFoundException($id); } - public function compile(bool $resolveEnvPlaceholders = false) + public function compile(bool $resolveEnvPlaceholders = false): void { foreach ($this->fallbackContainers as $serviceContainer) { $this->parameterBag->add($serviceContainer->getParameterBag()->all()); @@ -89,26 +91,10 @@ public function compile(bool $resolveEnvPlaceholders = false) parent::compile($resolveEnvPlaceholders); } - private function isSupportedServiceId($id) + private function isPageObject(string $id): bool { - if (is_null($id)) { - return false; - } - - // If the Page Object Extension is used then let it handle the autowiring - // @see \SensioLabs\Behat\PageObjectExtension\Context\Argument\PageObjectArgumentResolver::resolveArguments - if ($this->isPageObject($id)) { - return false; - } + Assert::notNull($id); - return true; - } - - private function isPageObject($id) - { - return ( - is_subclass_of($id, '\SensioLabs\Behat\PageObjectExtension\PageObject\Page') || - is_subclass_of($id, '\SensioLabs\Behat\PageObjectExtension\PageObject\Element') - ); + return is_subclass_of($id, Page::class) || is_subclass_of($id, Element::class); } } diff --git a/src/HelperContainer/Factory/DelegatingSymfonyServiceContainerFactory.php b/src/HelperContainer/Factory/DelegatingSymfonyServiceContainerFactory.php new file mode 100644 index 0000000..cb92570 --- /dev/null +++ b/src/HelperContainer/Factory/DelegatingSymfonyServiceContainerFactory.php @@ -0,0 +1,36 @@ +basePath = $basePath; + $this->loaderHelper = $loaderHelper; + } + + public function create(ConfigInterface $config, array $symfonyServiceContainers): DelegatingSymfonyServiceContainer + { + $container = new DelegatingSymfonyServiceContainer($symfonyServiceContainers); + if (($file = $config->getServicesPath()) !== null) { + $this->loaderHelper->loadFiles($container, $file, $this->basePath); + } + + $container->compile(); + + return $container; + } +} diff --git a/src/HelperContainer/Factory/DelegatingSymfonyServiceContainerFactoryInterface.php b/src/HelperContainer/Factory/DelegatingSymfonyServiceContainerFactoryInterface.php new file mode 100644 index 0000000..f78a2f1 --- /dev/null +++ b/src/HelperContainer/Factory/DelegatingSymfonyServiceContainerFactoryInterface.php @@ -0,0 +1,13 @@ +load($file); + } +} diff --git a/src/HelperContainer/Loader/DelegatingLoaderHelperInterface.php b/src/HelperContainer/Loader/DelegatingLoaderHelperInterface.php new file mode 100644 index 0000000..bd966ce --- /dev/null +++ b/src/HelperContainer/Loader/DelegatingLoaderHelperInterface.php @@ -0,0 +1,12 @@ +magentoObjectManager = $magentoObjectManager; } - public function has($id) + public function has($id): bool { try { - $this->magentoObjectManager->get($id); + $this->get($id); + return true; - } catch (\Exception $e) { + } catch (\Throwable $e) { return false; } } - public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE) + public function get(string $id, int $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE): ?object { try { return $this->magentoObjectManager->get($id); - } catch (\Exception $e) { + } catch (\Throwable $e) { throw new ServiceNotFoundException($id, null, $e); } } - public function getDefinition($id) + public function getDefinition(string $id): Definition { return (new Definition($id, [$id]))->setFactory([new Reference('magento2.object_manager'), 'get']); } diff --git a/src/HelperContainer/ServiceContainerInterface.php b/src/HelperContainer/ServiceContainerInterface.php new file mode 100644 index 0000000..71ba8bd --- /dev/null +++ b/src/HelperContainer/ServiceContainerInterface.php @@ -0,0 +1,12 @@ +config = $config; } - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ - SuiteTested::BEFORE => 'initApplication' + SuiteTested::BEFORE => 'initApplication', ]; } - public function initApplication(BeforeSuiteTested $event) + public function initApplication(SuiteTested $event): void { - $areas = $event->getSuite()->getSettings()['magento']['area'] ?? Area::AREA_GLOBAL; + $areas = $this->getAreas($event); - if (is_string($areas)) { - $areas = [$areas]; - } + // fix issues with Target component + $target = new Target(self::class); $bootstrapPath = $this->config->getMagentoBootstrapPath(); - - if (!file_exists($bootstrapPath)) { - throw new \RuntimeException(sprintf("Magento's bootstrap file was not found at path '%s'", $bootstrapPath)); - } - + Assert::fileExists($bootstrapPath, sprintf("Magento's bootstrap file was not found at path '%s'", $bootstrapPath)); include $bootstrapPath; - $params = $_SERVER; - - // TODO Can we remove this? - $params[Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS] = [ - DirectoryList::PUB => [DirectoryList::URL_PATH => ''], - DirectoryList::MEDIA => [DirectoryList::URL_PATH => 'media'], - DirectoryList::STATIC_VIEW => [DirectoryList::URL_PATH => 'static'], - DirectoryList::UPLOAD => [DirectoryList::URL_PATH => 'media/upload'], + $_SERVER[Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS] = [ + DirectoryList::PUB => [DirectoryListAlias::URL_PATH => ''], + DirectoryList::MEDIA => [DirectoryListAlias::URL_PATH => 'media'], + DirectoryList::STATIC_VIEW => [DirectoryListAlias::URL_PATH => 'static'], + DirectoryList::UPLOAD => [DirectoryListAlias::URL_PATH => 'media/upload'], ]; - $bootstrap = Bootstrap::create(BP, $params); + Bootstrap::create(BP, $_SERVER); /** @phpstan-ignore-line */ $magentoObjectManager = ObjectManager::getInstance(); $configLoader = $magentoObjectManager->get(ConfigLoaderInterface::class); @@ -82,7 +69,7 @@ public function initApplication(BeforeSuiteTested $event) ); } - $bootstrap = Bootstrap::create(BP, $params); + Bootstrap::create(BP, $_SERVER); /** @phpstan-ignore-line */ $magentoObjectManager = ObjectManager::getInstance(); $magentoObjectManager->configure($config); @@ -90,43 +77,21 @@ public function initApplication(BeforeSuiteTested $event) $appState = $magentoObjectManager->get(State::class); $appState->setAreaCode($mainArea); - // TODO can we remove this? - if ($appState->getAreaCode() === Area::AREA_ADMINHTML) { - $registry = $magentoObjectManager->get(Registry::class); - $registry->register('isSecureArea', true); - $roleCollection = $magentoObjectManager->get(Collection::class); - $roleCollection->setRolesFilter(); - - $adminRole = $roleCollection->getFirstItem(); - - $userFactory = $magentoObjectManager->get(UserFactory::class); - $user = $userFactory->create(); - - $reflectedUser = new \ReflectionObject($user); - $aclRoleProperty = $reflectedUser->getProperty('_role'); - $aclRoleProperty->setAccessible(true); - $aclRoleProperty->setValue($user, $adminRole); - - $session = $magentoObjectManager->get(Session::class); - $session->setUser($user); - } + $this->handleAdminAreaBootstrapping($appState, $magentoObjectManager); } - // TODO replace this with one of the nice array diff packages :D - // copied from http://php.net/manual/en/function.array-diff.php#91756 - private function arrayRecursiveDiff($original, $excluded): array + public function arrayRecursiveDiff(array $original, array $excluded): array { $aReturn = []; - foreach ($original as $key => $value) { if (array_key_exists($key, $excluded)) { if (is_array($value)) { $recursiveDiff = $this->arrayRecursiveDiff($value, $excluded[$key]); - if (count($recursiveDiff)) { $aReturn[$key] = $recursiveDiff; } - } else { - if ($value != $excluded[$key]) { - $aReturn[$key] = $value; + if (count($recursiveDiff) > 0) { + $aReturn[$key] = $recursiveDiff; } + } elseif ($value !== $excluded[$key]) { + $aReturn[$key] = $value; } } else { $aReturn[$key] = $value; @@ -135,4 +100,40 @@ private function arrayRecursiveDiff($original, $excluded): array return $aReturn; } + + private function getAreas(SuiteTested $event): array + { + $areas = $event->getSuite()->getSettings()['magento']['area'] ?? Area::AREA_GLOBAL; + + if (is_string($areas)) { + $areas = [$areas]; + } + + if (!is_array($areas)) { + $areas = [$areas]; + } + + return $areas; + } + + public function handleAdminAreaBootstrapping(State $appState, ObjectManager $magentoObjectManager): void + { + if ($appState->getAreaCode() === Area::AREA_ADMINHTML) { + $registry = $magentoObjectManager->get(Registry::class); + $registry->register('isSecureArea', true); + $roleCollection = $magentoObjectManager->get(Collection::class); + $roleCollection->setRolesFilter(); + + /** @var Role $adminRole */ + $adminRole = $roleCollection->getFirstItem(); + + /** @var User $user */ + $user = $magentoObjectManager->get(User::class); + $user->setRoleId($adminRole->getId()); /** @phpstan-ignore-line */ + + /** @var Session $session */ + $session = $magentoObjectManager->get(Session::class); + $session->setUser($user); + } + } } diff --git a/src/Listener/MagentoObjectManagerInitListenerInterface.php b/src/Listener/MagentoObjectManagerInitListenerInterface.php new file mode 100644 index 0000000..6355b28 --- /dev/null +++ b/src/Listener/MagentoObjectManagerInitListenerInterface.php @@ -0,0 +1,19 @@ +get($id); + } + + public function create(string $id): object + { + return ObjectManager::getInstance()->create($id); + } +} diff --git a/src/Service/MagentoObjectManagerInterface.php b/src/Service/MagentoObjectManagerInterface.php new file mode 100644 index 0000000..3109067 --- /dev/null +++ b/src/Service/MagentoObjectManagerInterface.php @@ -0,0 +1,12 @@ +magentoBootstrapPath = $config[self::CONFIG_KEY_MAGENTO_BOOTSTRAP]; diff --git a/src/ServiceContainer/ConfigInterface.php b/src/ServiceContainer/ConfigInterface.php new file mode 100644 index 0000000..4cf22cc --- /dev/null +++ b/src/ServiceContainer/ConfigInterface.php @@ -0,0 +1,16 @@ +children() + ->scalarNode(ConfigInterface::CONFIG_KEY_MAGENTO_BOOTSTRAP) + ->defaultValue($this->getMagentoBootstrapPath()) + ->end() + ->scalarNode(ConfigInterface::CONFIG_KEY_SERVICES) + ->defaultValue(null) + ->end() + ->end(); + } + + public function load(ContainerBuilder $container, array $config): void + { + $locator = new FileLocator(__DIR__ . '/config'); + $loader = new PhpFileLoader($container, $locator); + Assert::fileExists($locator->locate('services.php')); + $loader->load('services.php'); + $extensionConfig = new Config($config); + $container->addCompilerPass(new RegisterListenersPass()); + $container->set(self::SERVICE_ID_EXTENSION_CONFIG, $extensionConfig); + $container->set(self::BEHAT_CONTAINER_KEY, $container); + } + + public function process(TaggedContainerInterface $container): void + { + } + + private function getMagentoBootstrapPath(): string + { + $path = sprintf('%s/app/bootstrap.php', getcwd()); + $prefix = getcwd() ?: '/'; + $limit = count(explode('/', $prefix)); + $i = 0; + while (!file_exists($path)) { + Assert::lessThan(++$i, $limit, 'Could not find Magento root directory'); + $prefix = sprintf('%s/..', $prefix); + $path = sprintf('%s/app/bootstrap.php', $prefix); + } + + return $path; + } +} diff --git a/src/ServiceContainer/Magento2ExtensionInterface.php b/src/ServiceContainer/Magento2ExtensionInterface.php new file mode 100644 index 0000000..363b89a --- /dev/null +++ b/src/ServiceContainer/Magento2ExtensionInterface.php @@ -0,0 +1,19 @@ +services(); + + $services->set('seec.magento2_extension.config', Config::class); + + $services->set('magento2.object_manager', MagentoObjectManager::class) + ->public(); + + $services->set( + 'seec.behat.magento2_extension.helper_container.loader.delegating_loader_helper', + DelegatingLoaderHelper::class + )->public(); + + $services->set( + 'seec.behat.magento2_extension.delegating_symfony_service_container_factory', + DelegatingSymfonyServiceContainerFactory::class + ) + ->public() + ->args([ + '%paths.base%', + service('seec.behat.magento2_extension.helper_container.loader.delegating_loader_helper'), + ]); + + $services->set('seec.magento2_extension.magento2_service_container', Magento2SymfonyServiceContainer::class) + ->public() + ->args([ + service('magento2.object_manager'), + ]) + ->share(false); + + $services->set('seec.behat_service_container', ContainerBuilder::class); + + $services->set('seec.magento2_extension.service_container', DelegatingSymfonyServiceContainer::class) + ->public() + ->tag('helper_container.container') + ->args([ + service('seec.magento2_extension.config'), + [ + service('seec.behat_service_container'), + service('seec.magento2_extension.magento2_service_container'), + ], + ]) + ->factory([ + service('seec.behat.magento2_extension.delegating_symfony_service_container_factory'), + 'create', + ]) + ->share(false); + + $services->set( + 'seec.magento2_extension.object_manager_initializer_listener', + MagentoObjectManagerInitListener::class + ) + ->tag('event_dispatcher.subscriber') + ->args([service('seec.magento2_extension.config')]); + + $services->set('seec.magento2_extension.shared_storage', SharedStorage::class) + ->public() + ->share(); +}; diff --git a/tests/Context/Tasks/CacheCleanerTest.php b/tests/Context/Tasks/CacheCleanerTest.php new file mode 100644 index 0000000..0a91105 --- /dev/null +++ b/tests/Context/Tasks/CacheCleanerTest.php @@ -0,0 +1,69 @@ +pathProvider = $this->createMock(MagentoPathProviderInterface::class); + $this->fileSystem = $this->createMock(Filesystem::class); + $this->finder = $this->createMock(Finder::class); + $this->cacheCleaner = new CacheCleaner($this->pathProvider, $this->fileSystem, $this->finder); + } + + public function test_it_will_attempt_to_clear_the_cache_correctly(): void + { + $this->pathProvider->expects($this->once()) + ->method('getMagentoRootDirectory') + ->willReturn('/var/www/html'); + + $file1 = $this->createMock(SplFileInfo::class); + $file1->expects($this->once()) + ->method('getRelativePathname') + ->willReturn('.'); + $file1->expects($this->never()) + ->method('getPathname'); + + $file2 = $this->createMock(SplFileInfo::class); + $file2->expects($this->once()) + ->method('getRelativePathname') + ->willReturn('test'); + $file2->expects($this->once()) + ->method('getPathname') + ->willReturn('/var/www/html/var/cache/test'); + + $this->finder->expects($this->once()) + ->method('in') + ->with('/var/www/html/var/cache/') + ->willReturn([$file1, $file2]); + + $this->fileSystem->expects($this->once()) + ->method('remove') + ->with('/var/www/html/var/cache/test'); + + $this->cacheCleaner->clean(false); + } +} diff --git a/tests/Context/Tasks/MagentoPathProviderTest.php b/tests/Context/Tasks/MagentoPathProviderTest.php new file mode 100644 index 0000000..ee49076 --- /dev/null +++ b/tests/Context/Tasks/MagentoPathProviderTest.php @@ -0,0 +1,24 @@ +magentoPathProvider = new MagentoPathProvider(); + } + + public function test_it_can_get_magento_root_directory(): void + { + $this->assertDirectoryExists($this->magentoPathProvider->getMagentoRootDirectory()); + } +} diff --git a/tests/Context/Tasks/PurgerTest.php b/tests/Context/Tasks/PurgerTest.php new file mode 100644 index 0000000..c934f8c --- /dev/null +++ b/tests/Context/Tasks/PurgerTest.php @@ -0,0 +1,56 @@ +purger = new Purger(); + } + + public function test_it_will_attempt_to_purge_all_tables(): void + { + $mockConnection = $this->createMock(AdapterInterface::class); + $mockConnection->expects($this->once()) + ->method('getTables') + ->willReturn(['table1', 'table2', 'table3']); + $mockConnection->expects($this->exactly(3)) + ->method('query') + ->with(...$this->withConsecutive( + ['SET FOREIGN_KEY_CHECKS = 0'], + ['TRUNCATE TABLE table1'], + ['SET FOREIGN_KEY_CHECKS = 1'] + )); + + $mockSelect = $this->createMock(Select::class); + $mockConnection->expects($this->exactly(2)) + ->method('select') + ->willReturn($mockSelect); + + $mockSelect->expects($this->exactly(2)) + ->method('from') + ->with(...$this->withConsecutive( + ['table1', 'COUNT(*)'], + ['table2', 'COUNT(*)'] + )); + $mockConnection->expects($this->exactly(2)) + ->method('fetchOne') + ->willReturnOnConsecutiveCalls('999', '0'); + + $this->purger->purge($mockConnection, ['table3']); + } +} diff --git a/tests/HelperContainer/DelegatingSymfonyServiceContainerTest.php b/tests/HelperContainer/DelegatingSymfonyServiceContainerTest.php new file mode 100644 index 0000000..a31b02a --- /dev/null +++ b/tests/HelperContainer/DelegatingSymfonyServiceContainerTest.php @@ -0,0 +1,175 @@ +container = new DelegatingSymfonyServiceContainer([]); + } + + public function test_has_returns_true_if_service_exists(): void + { + $mockSession = $this->createMock(Session::class); + $this->container->set('test_class', new TestClass($mockSession, [])); + $this->assertTrue($this->container->has('test_class')); + $this->assertInstanceOf(TestClass::class, $this->container->get('test_class')); + } + + public function test_has_returns_false_if_service_does_not_exist(): void + { + $this->assertFalse($this->container->has('my_service')); + } + + public function test_get_returns_service_if_it_exists(): void + { + $mockSession = $this->createMock(Session::class); + $service = new TestClass($mockSession, []); + $this->container->set('test_class', $service); + $this->assertSame($service, $this->container->get('test_class')); + } + + public function test_it_throws_an_error_if_service_does_not_exist(): void + { + $this->expectException(ServiceNotFoundException::class); + $this->assertNull($this->container->get('my_service')); + } + + public function test_getDefinition_returns_definition_if_service_exists(): void + { + $definition = $this->createMock(Definition::class); + $this->container->setDefinition(TestClass::class, $definition); + + $this->assertSame($definition, $this->container->getDefinition(TestClass::class)); + } + + public function test_getDefinition_throws_exception_if_service_does_not_exist(): void + { + $this->expectException(ServiceNotFoundException::class); + + $this->container->getDefinition('my_service'); + } + + public function test_compile_merges_parameter_bags_of_fallback_containers(): void + { + $fallbackContainer1 = new SymfonyServiceContainer(); + $fallbackContainer1->setParameter('param1', 'value1'); + + $fallbackContainer2 = new SymfonyServiceContainer(); + $fallbackContainer2->setParameter('param2', 'value2'); + + $fallbackContainers = [$fallbackContainer1, $fallbackContainer2]; + $container = new DelegatingSymfonyServiceContainer($fallbackContainers); + $container->compile(); + + $this->assertSame(['param1' => 'value1', 'param2' => 'value2'], $container->getParameterBag()->all()); + } + + public function test_get_returns_service_from_fallback_containers(): void + { + $mockSession = $this->createMock(Session::class); + $fallbackContainer1 = new SymfonyServiceContainer(); + $fallbackService1 = new TestClass($mockSession); + $fallbackContainer1->set('some_other_class', $fallbackService1); + + $fallbackContainer2 = new SymfonyServiceContainer(); + $fallbackService2 = new TestClass($mockSession); + $fallbackContainer2->set('test_class', $fallbackService2); + + $fallbackContainers = [$fallbackContainer1, $fallbackContainer2]; + $container = new DelegatingSymfonyServiceContainer($fallbackContainers); + + $this->assertSame($fallbackService2, $container->get('test_class')); + } + + public function test_get_returns_null_if_service_does_not_exist_in_fallback_containers(): void + { + $fallbackContainer1 = new SymfonyServiceContainer(); + $fallbackContainer2 = new SymfonyServiceContainer(); + + $fallbackContainers = [$fallbackContainer1, $fallbackContainer2]; + + $container = new DelegatingSymfonyServiceContainer($fallbackContainers); + + $this->expectException(ServiceNotFoundException::class); + $this->expectExceptionMessage('You have requested a non-existent service "test_class".'); + $this->assertNull($container->get('test_class')); + } + + public function test_getDefinition_returns_definition_from_fallback_containers(): void + { + $fallbackContainer1 = new SymfonyServiceContainer(); + $fallbackDefinition1 = new Definition(\stdClass::class); + $fallbackContainer1->setDefinition('my_service', $fallbackDefinition1); + + $fallbackContainer2 = new SymfonyServiceContainer(); + $fallbackDefinition2 = new Definition(\stdClass::class); + $fallbackContainer2->setDefinition('my_service', $fallbackDefinition2); + + $fallbackContainers = [$fallbackContainer1, $fallbackContainer2]; + $container = new DelegatingSymfonyServiceContainer($fallbackContainers); + + $this->assertSame($fallbackDefinition1, $container->getDefinition('my_service')); + } + + public function test_getDefinition_throws_exception_if_definition_does_not_exist_in_fallback_containers(): void + { + $fallbackContainer1 = new SymfonyServiceContainer(); + $fallbackContainer2 = new SymfonyServiceContainer(); + $fallbackContainers = [$fallbackContainer1, $fallbackContainer2]; + $container = new DelegatingSymfonyServiceContainer($fallbackContainers); + + $this->expectException(ServiceNotFoundException::class); + $container->getDefinition('my_service'); + } + + public function test_has_returns_false_if_service_does_not_exist_in_any_container(): void + { + $fallbackContainer1 = new SymfonyServiceContainer(); + $fallbackContainer2 = new SymfonyServiceContainer(); + $fallbackContainers = [$fallbackContainer1, $fallbackContainer2]; + $container = new DelegatingSymfonyServiceContainer($fallbackContainers); + + $this->assertFalse($container->has(TestClass::class)); + } + + public function test_has_returns_true_if_service_exists_in_fallback_containers(): void + { + $fallbackContainer1 = new SymfonyServiceContainer(); + $fallbackContainer2 = new SymfonyServiceContainer(); + $fallbackContainers = [$fallbackContainer1, $fallbackContainer2]; + $container = new DelegatingSymfonyServiceContainer($fallbackContainers); + $fallbackContainer1->set('test_class', new \stdClass()); + + $this->assertTrue($container->has('test_class')); + } + + public function test_it_returns_null_when_class_should_be_handled_by_autowiring(): void + { + $mockSession = $this->createMock(Session::class); + $service = new TestClass($mockSession, []); + $this->container->set('test_class', $service); + $this->assertNull($this->container->get(TestClass::class)); + } +} diff --git a/tests/HelperContainer/Factory/DelegatingSymfonyServiceContainerFactoryTest.php b/tests/HelperContainer/Factory/DelegatingSymfonyServiceContainerFactoryTest.php new file mode 100644 index 0000000..60978cd --- /dev/null +++ b/tests/HelperContainer/Factory/DelegatingSymfonyServiceContainerFactoryTest.php @@ -0,0 +1,57 @@ +basePath = '/path/to/base'; + $this->helper = $this->createMock(DelegatingLoaderHelperInterface::class); + $this->factory = new DelegatingSymfonyServiceContainerFactory($this->basePath, $this->helper); + } + + public function test_it_will_not_configure_created_class_when_config_contains_nothing_to_use(): void + { + /** @var ConfigInterface|MockObject $config */ + $config = $this->createMock(ConfigInterface::class); + $symfonyServiceContainers = []; + $container = $this->factory->create($config, $symfonyServiceContainers); + $this->helper->expects($this->never())->method('loadFiles'); + $this->assertInstanceOf(DelegatingSymfonyServiceContainer::class, $container); + } + + public function test_it_will_correctly_create_new_class(): void + { + /** @var ConfigInterface|MockObject $config */ + $config = $this->createMock(ConfigInterface::class); + $symfonyServiceContainers = []; + $config->expects($this->once()) + ->method('getServicesPath') + ->willReturn('/var/log/test.yml'); + + $this->helper->expects($this->once()) + ->method('loadFiles') + ->with($this->isInstanceOf(DelegatingSymfonyServiceContainer::class), '/var/log/test.yml', $this->basePath); + + $container = $this->factory->create($config, $symfonyServiceContainers); + $this->assertInstanceOf(DelegatingSymfonyServiceContainer::class, $container); + } +} diff --git a/tests/HelperContainer/Helper/DelegatingLoaderHelperTest.php b/tests/HelperContainer/Helper/DelegatingLoaderHelperTest.php new file mode 100644 index 0000000..675cbd0 --- /dev/null +++ b/tests/HelperContainer/Helper/DelegatingLoaderHelperTest.php @@ -0,0 +1,22 @@ +createMock(ContainerBuilder::class); + $file = 'test.yml'; + $basePath = __DIR__; + $helper = new DelegatingLoaderHelper(); + $helper->loadFiles($container, $file, $basePath); + $this->assertTrue(true); + } +} diff --git a/tests/HelperContainer/Helper/test.yml b/tests/HelperContainer/Helper/test.yml new file mode 100644 index 0000000..e69de29 diff --git a/tests/HelperContainer/Magento2SymfonyServiceContainerTest.php b/tests/HelperContainer/Magento2SymfonyServiceContainerTest.php new file mode 100644 index 0000000..fd4666d --- /dev/null +++ b/tests/HelperContainer/Magento2SymfonyServiceContainerTest.php @@ -0,0 +1,64 @@ +magentoObjectManager = $this->createMock(MagentoObjectManagerInterface::class); + $this->container = new Magento2SymfonyServiceContainer($this->magentoObjectManager); + } + + public function test_it_is_a_container(): void + { + $this->assertInstanceOf(ContainerInterface::class, $this->container); + } + + public function serviceProvider(): array + { + return [ + 'it has the service available' => [true], + 'it has the service not available' => [false], + ]; + } + + /** @dataProvider serviceProvider */ + public function test_it_can_evaluate_if_it_has_an_service(bool $expectation): void + { + if ($expectation) { + $this->magentoObjectManager->expects($this->once()) + ->method('get') + ->with('some_service') + ->willReturn(new stdClass()); + } else { + $this->magentoObjectManager->expects($this->once()) + ->method('get') + ->with('some_service') + ->willThrowException(new \Exception()); + } + + $this->assertSame($expectation, $this->container->has('some_service')); + } + + public function test_it_can_correctly_get_the_definition(): void + { + $this->assertInstanceOf(Definition::class, $this->container->getDefinition('some_service')); + } +} diff --git a/tests/Listener/MagentoObjectManagerInitListenerTest.php b/tests/Listener/MagentoObjectManagerInitListenerTest.php new file mode 100644 index 0000000..c1421db --- /dev/null +++ b/tests/Listener/MagentoObjectManagerInitListenerTest.php @@ -0,0 +1,166 @@ +config = $this->createMock(ConfigInterface::class); + $this->listener = new MagentoObjectManagerInitListener($this->config); + } + + public function test_it_is_an_event_subscriber(): void + { + $this->assertInstanceOf(EventSubscriberInterface::class, $this->listener); + } + + public function test_it_is_subscribed_to_the_correct_events(): void + { + $this->assertSame(['tester.suite_tested.before' => 'initApplication'], $this->listener::getSubscribedEvents()); + } + + public function test_it_will_throw_an_error_when_bootstrap_file_cannot_be_found(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("Magento's bootstrap file was not found at path 'some_path'"); + $this->config->expects($this->once()) + ->method('getMagentoBootstrapPath') + ->willReturn('some_path'); + + $mockEvent = $this->createMock(SuiteTested::class); + $mockSuite = $this->createMock(Suite::class); + $mockSuite->expects($this->once()) + ->method('getSettings') + ->willReturn(['magento' => ['area' => 'test']]); + $mockEvent->expects($this->once()) + ->method('getSuite') + ->willReturn($mockSuite); + + $this->listener->initApplication($mockEvent); + } + + public function test_it_will_create_an_admin_user_on_demand_when_bootstrapping_admin_area(): void + { + $objectManager = $this->createMock(ObjectManager::class); + $appState = $this->createMock(State::class); + $appState->expects($this->once()) + ->method('getAreaCode') + ->willReturn('adminhtml'); + $registryMock = $this->getMockBuilder(Registry::class) + ->disableOriginalConstructor() + ->getMock(); + $collectionMock = $this->getMockBuilder(Collection::class) + ->disableOriginalConstructor() + ->getMock(); + + $user = $this->getMockBuilder(User::class) + ->disableOriginalConstructor() + ->addMethods(['setRoleId']) + ->getMock(); + + $sessionMock = $this->getMockBuilder(Session::class) + ->addMethods(['setUser']) + ->disableOriginalConstructor() + ->getMock(); + + $objectManager->expects($this->exactly(4)) + ->method('get') + ->with(...$this->withConsecutive( + [Registry::class], + [Collection::class], + ['Magento\User\Model\User'], + [Session::class] + )) + ->willReturnOnConsecutiveCalls( + $registryMock, + $collectionMock, + $user, + $sessionMock + ); + + $registryMock->expects($this->once()) + ->method('register') + ->with('isSecureArea', true); + $collectionMock->expects($this->once()) + ->method('setRolesFilter'); + + $roleMock = $this->createMock(Role::class); + $collectionMock->expects($this->once()) + ->method('getFirstItem') + ->willReturn($roleMock); + $roleMock->expects($this->once()) + ->method('getId') + ->willReturn(1); + + $sessionMock->expects($this->once()) + ->method('setUser') + ->with($user); + + $this->listener->handleAdminAreaBootstrapping($appState, $objectManager); + } + + public function testArrayRecursiveDiff(): void + { + $original = [ + 'key1' => 'value1', + 'key2' => 'value2', + 'key3' => [ + 'key4' => 'value4', + 'key5' => 'value5', + ], + 'key6' => [ + 'key7' => 'value7', + 'key8' => 'value8', + ], + ]; + + $excluded = [ + 'key1' => 'value1', + 'key3' => [ + 'key5' => 'value5', + ], + 'key6' => [ + 'key8' => 'value8', + ], + ]; + + $result = $this->listener->arrayRecursiveDiff($original, $excluded); + $this->assertEquals([ + 'key2' => 'value2', + 'key3' => [ + 'key4' => 'value4', + ], + 'key6' => [ + 'key7' => 'value7', + ], + ], $result); + } +} diff --git a/tests/ServiceContainer/ConfigTest.php b/tests/ServiceContainer/ConfigTest.php new file mode 100644 index 0000000..ac8b81a --- /dev/null +++ b/tests/ServiceContainer/ConfigTest.php @@ -0,0 +1,32 @@ +config = new Config([ + ConfigInterface::CONFIG_KEY_SERVICES => 'services', + ConfigInterface::CONFIG_KEY_MAGENTO_BOOTSTRAP => 'bootstrap', + ]); + } + + public function test_it_can_get_service_parameter(): void + { + $this->assertSame('services', $this->config->getServicesPath()); + } + + public function test_it_can_get_bootstrap_parameter(): void + { + $this->assertSame('bootstrap', $this->config->getMagentoBootstrapPath()); + } +} diff --git a/tests/ServiceContainer/Magento2ExtensionTest.php b/tests/ServiceContainer/Magento2ExtensionTest.php new file mode 100644 index 0000000..529a946 --- /dev/null +++ b/tests/ServiceContainer/Magento2ExtensionTest.php @@ -0,0 +1,67 @@ +extension = new Magento2Extension(); + } + + public function test_it_can_get_config_key(): void + { + $this->assertSame('seec_magento2', $this->extension->getConfigKey()); + } + + public function test_it_can_configure(): void + { + $builder = new ArrayNodeDefinition('root', new NodeBuilder()); + $this->extension->configure($builder); + $children = $builder->getChildNodeDefinitions(); + $this->assertArrayHasKey(ConfigInterface::CONFIG_KEY_MAGENTO_BOOTSTRAP, $children); + $this->assertArrayHasKey(ConfigInterface::CONFIG_KEY_SERVICES, $children); + } + + public function test_it_has_noop_functions(): void + { + $manager = new ExtensionManager([]); + $this->extension->initialize($manager); + $containerBuilder = $this->createMock(TaggedContainerInterface::class); + $this->extension->process($containerBuilder); + $this->assertTrue(true); + } + + public function test_it_can_correctly_load_config_into_container(): void + { + $containerBuilder = $this->createMock(ContainerBuilder::class); + $containerBuilder->expects($this->exactly(2)) + ->method('set') + ->with(...$this->withConsecutive( + ['seec.magento2_extension.config', $this->isInstanceOf(ConfigInterface::class)], + ['seec.behat_service_container', $containerBuilder] + )); + + $this->extension->load($containerBuilder, [ + 'bootstrap' => 'bootstrap', + 'services' => 'services', + ]); + } +} From ad2048735fb4e0850204645c0b0e1230032f1a19 Mon Sep 17 00:00:00 2001 From: Maximilian Graf Schimmelmann Date: Mon, 31 Jul 2023 18:43:59 +0200 Subject: [PATCH 02/24] Add missing composer.auth declaration to workflow logic. --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f1ed687..1268ca3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,6 +114,10 @@ jobs: - name: "Checkout" uses: "actions/checkout@v2" + - name: "Setup Composer Auth" + run: "echo $COMPOSER_AUTH_JSON > ~/.composer/auth.json" + env: + COMPOSER_AUTH_JSON: ${{ secrets.COMPOSER_AUTH_JSON }} - name: "Install PHP" uses: "shivammathur/setup-php@v2" From 002f2a23202a14acd63632849a7ffb8aeb887b36 Mon Sep 17 00:00:00 2001 From: Maximilian Graf Schimmelmann Date: Mon, 31 Jul 2023 23:46:07 +0200 Subject: [PATCH 03/24] Attempt to make package multi-stage-able. --- composer.json | 2 +- docker-compose.yml | 21 +++++++++++++++++++++ docker/php/Dockerfile | 4 ++-- docker/php/etc/fetch-magento.sh | 14 +++++++++++++- 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index dff8e4e..752ac78 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "require": { "php": "^7.4|^8.1", "behat/behat": "^3.7", - "magento/framework": "^103.0.6", + "magento/framework": "^100|^103", "container-interop/container-interop": "^1.2", "symfony/dependency-injection": "^6", "symfony/event-dispatcher": "^6", diff --git a/docker-compose.yml b/docker-compose.yml index f9d51f1..437d119 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,6 +22,27 @@ services: env_file: - .env + php-7.4: + build: + context: . + dockerfile: ./docker/php/Dockerfile + args: + XDEBUG_VERSION: xdebug-3.1.5 + PHP_VERSION: php:7.4.33-zts-alpine3.16 + MAGENTO_PUBLIC_KEY: ${MAGENTO_PUBLIC_KEY} + MAGENTO_SECRET_KEY: ${MAGENTO_SECRET_KEY} + volumes: + - ./:/var/www/html/vendor/seec/behat-magento2-extension + - ./behat.yml:/var/www/html/behat.yml + - ./features:/var/www/html/features + - ./magento:/var/www/html/vendor/magento-ext + environment: + PHP_IDE_CONFIG: serverName=magento2-behat-extension + extra_hosts: + - "host.docker.internal:host-gateway" + env_file: + - .env + mysql: image: mariadb:10.6 environment: diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile index 9ba59b8..577d5a2 100644 --- a/docker/php/Dockerfile +++ b/docker/php/Dockerfile @@ -25,7 +25,7 @@ RUN set -xe \ unzip \ autoconf \ gcc \ - envsubst \ + gettext \ libxml2-dev \ zip \ gd @@ -47,7 +47,7 @@ RUN apk add --no-cache --virtual build-essentials \ docker-php-ext-install soap && \ docker-php-ext-install sockets && \ docker-php-ext-install xsl && \ - pecl install xdebug && \ + pecl install $XDEBUG_VERSION && \ docker-php-ext-enable xdebug && \ apk del build-essentials pcre-dev ${PHPIZE_DEPS} && rm -rf /usr/src/php* diff --git a/docker/php/etc/fetch-magento.sh b/docker/php/etc/fetch-magento.sh index 72a2b64..0fb6891 100644 --- a/docker/php/etc/fetch-magento.sh +++ b/docker/php/etc/fetch-magento.sh @@ -1,8 +1,20 @@ #!/usr/bin/env sh +PHP_VERSION=$(php -v | tac | tail -n 1 | cut -d " " -f 2 | cut -c 1-3) +echo "$PHP_VERSION" + if [ ! -f /var/www/html/composer.json ]; then rm -rf /var/www/html - composer create-project --repository=https://repo.magento.com/ magento/project-community-edition=2.4.6 /var/www/html/ + if [ "$PHP_VERSION" = "7.4" ]; then + composer create-project --no-install --repository=https://repo.magento.com/ magento/project-community-edition=2.4.3-p3 /var/www/html/ + else + composer create-project --no-install --repository=https://repo.magento.com/ magento/project-community-edition=2.4.6 /var/www/html/ + fi cd /var/www/html + composer config --no-plugins allow-plugins.magento/* true + composer config --no-plugins allow-plugins.php-http/discovery true + composer config --no-plugins allow-plugins.laminas/laminas-dependency-plugin true + composer config --no-plugins allow-plugins.dealerdirect/phpcodesniffer-composer-installer true composer require --dev behat/behat friends-of-behat/mink-extension behat/mink-goutte-driver tkotosz/test-area-magento2 + composer install fi From d0c0e9975c52b4a9cb8c9fe16993fd2e452d448e Mon Sep 17 00:00:00 2001 From: Maximilian Graf Schimmelmann Date: Tue, 1 Aug 2023 00:02:26 +0200 Subject: [PATCH 04/24] Bump composer php version to php7.4 + php8.0 and above --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 752ac78..ecd88bd 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ ], "minimum-stability": "dev", "require": { - "php": "^7.4|^8.1", + "php": "^7.4|^8.0", "behat/behat": "^3.7", "magento/framework": "^100|^103", "container-interop/container-interop": "^1.2", From 354fe650e4abbbd1385001cdddc5f0c4de1fcfa4 Mon Sep 17 00:00:00 2001 From: Maximilian Graf Schimmelmann Date: Tue, 1 Aug 2023 00:05:05 +0200 Subject: [PATCH 05/24] Bump composer magento framework version to 101|103 and above --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ecd88bd..183c49d 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "require": { "php": "^7.4|^8.0", "behat/behat": "^3.7", - "magento/framework": "^100|^103", + "magento/framework": "^101|^103", "container-interop/container-interop": "^1.2", "symfony/dependency-injection": "^6", "symfony/event-dispatcher": "^6", From 0f8d94b1aa864955c2b7dfa09ea18fe89110eaff Mon Sep 17 00:00:00 2001 From: Maximilian Graf Schimmelmann Date: Tue, 1 Aug 2023 00:07:27 +0200 Subject: [PATCH 06/24] Adapt Github Workflow file and bump php version again --- .github/workflows/ci.yml | 4 ++-- composer.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1268ca3..b990014 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: matrix: php-version: - "7.4" - - "8.0" + - "8.1" - "8.2" env: @@ -107,7 +107,7 @@ jobs: matrix: php-version: - "7.4" - - "8.0" + - "8.1" - "8.2" steps: diff --git a/composer.json b/composer.json index 183c49d..3fb6c1b 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ ], "minimum-stability": "dev", "require": { - "php": "^7.4|^8.0", + "php": "^7.4|^8.1", "behat/behat": "^3.7", "magento/framework": "^101|^103", "container-interop/container-interop": "^1.2", From b7cb31540f36eae4073435b16a5658a504f31a8d Mon Sep 17 00:00:00 2001 From: Maximilian Graf Schimmelmann Date: Tue, 1 Aug 2023 13:03:50 +0200 Subject: [PATCH 07/24] Remove PHP7.4 compatibility for the good. --- .github/workflows/ci.yml | 7 ++++--- README.md | 5 ++--- composer.json | 4 ++-- docker-compose.yml | 5 ++--- docker/php/etc/fetch-magento.sh | 11 ++--------- 5 files changed, 12 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b990014..825c502 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,6 @@ jobs: strategy: matrix: php-version: - - "7.4" - "8.1" - "8.2" @@ -101,12 +100,11 @@ jobs: working-directory: 'magento/vendor/seec/behat-magento2-extension' code-style: - name: "Check code style" + name: "CodeStyle + UnitTests" runs-on: "ubuntu-latest" strategy: matrix: php-version: - - "7.4" - "8.1" - "8.2" @@ -146,3 +144,6 @@ jobs: - name: "PhpStan for Test" run: "vendor/bin/phpstan analyse --error-format=checkstyle tests/ --level=6 | cs2pr" + + - name: "Run unit tests" + run: "vendor/bin/phpunit tests/" diff --git a/README.md b/README.md index 19b4a6d..bc9e2ed 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,8 @@ Behat Magento2 Extension [![Latest Stable Version](https://poser.pugx.org/nopenopenope/behat-magento2-extension/version)](https://packagist.org/packages/nopenopenope/behat-magento2-extension) ![Build Status](https://github.com/nopenopenope/BehatMagento2Extension/actions/workflows/ci.yml/badge.svg) -This is a fork of the [BehatMagentoExtension](https://github.com/tkotosz/BehatMagento2Extension) which is -compatible with PHP7.4 as well as PHP8 and greater. This should ensure successful end-to-end testing of Magento 2 -projects. +This is a fork of the [BehatMagentoExtension](https://github.com/tkotosz/BehatMagento2Extension), which is +compatible with PHP8.1 and greater. This should ensure successful end-to-end testing of Magento 2 projects. The `BehatMagento2Extension` provides a custom service container for Behat which allows to inject Magento services into Behat Contexts and Behat helper services. diff --git a/composer.json b/composer.json index 3fb6c1b..aae0b97 100644 --- a/composer.json +++ b/composer.json @@ -21,9 +21,9 @@ ], "minimum-stability": "dev", "require": { - "php": "^7.4|^8.1", + "php": "^8.1", "behat/behat": "^3.7", - "magento/framework": "^101|^103", + "magento/framework": "^103", "container-interop/container-interop": "^1.2", "symfony/dependency-injection": "^6", "symfony/event-dispatcher": "^6", diff --git a/docker-compose.yml b/docker-compose.yml index 437d119..749a670 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,13 +22,12 @@ services: env_file: - .env - php-7.4: + php-8.1: build: context: . dockerfile: ./docker/php/Dockerfile args: - XDEBUG_VERSION: xdebug-3.1.5 - PHP_VERSION: php:7.4.33-zts-alpine3.16 + PHP_VERSION: php:8.1.20-zts-alpine3.18 MAGENTO_PUBLIC_KEY: ${MAGENTO_PUBLIC_KEY} MAGENTO_SECRET_KEY: ${MAGENTO_SECRET_KEY} volumes: diff --git a/docker/php/etc/fetch-magento.sh b/docker/php/etc/fetch-magento.sh index 0fb6891..05d1cb4 100644 --- a/docker/php/etc/fetch-magento.sh +++ b/docker/php/etc/fetch-magento.sh @@ -1,20 +1,13 @@ #!/usr/bin/env sh -PHP_VERSION=$(php -v | tac | tail -n 1 | cut -d " " -f 2 | cut -c 1-3) -echo "$PHP_VERSION" - if [ ! -f /var/www/html/composer.json ]; then rm -rf /var/www/html - if [ "$PHP_VERSION" = "7.4" ]; then - composer create-project --no-install --repository=https://repo.magento.com/ magento/project-community-edition=2.4.3-p3 /var/www/html/ - else - composer create-project --no-install --repository=https://repo.magento.com/ magento/project-community-edition=2.4.6 /var/www/html/ - fi + composer create-project --no-install --repository=https://repo.magento.com/ magento/project-community-edition=2.4.3-p3 /var/www/html/ cd /var/www/html composer config --no-plugins allow-plugins.magento/* true composer config --no-plugins allow-plugins.php-http/discovery true composer config --no-plugins allow-plugins.laminas/laminas-dependency-plugin true composer config --no-plugins allow-plugins.dealerdirect/phpcodesniffer-composer-installer true - composer require --dev behat/behat friends-of-behat/mink-extension behat/mink-goutte-driver tkotosz/test-area-magento2 + composer require --dev behat/behat friends-of-behat/mink-extension behat/mink-goutte-driver composer install fi From 11cd66f3e7ea37704f34f1feb936bdf1a5ad6ee0 Mon Sep 17 00:00:00 2001 From: Maximilian Graf Schimmelmann Date: Tue, 1 Aug 2023 17:41:50 +0200 Subject: [PATCH 08/24] Moved ECS ruleset to PHP8 declarations and applied new rules to classes. --- ecs.php | 7 ++++++- .../Components/Customer/CustomerContext.php | 20 ++++--------------- .../Input/MagentoCommandInput.php | 2 +- .../Context/Components/Stock/StockContext.php | 15 +++----------- .../Context/Components/Store/StoreContext.php | 11 ++-------- .../bootstrap/Context/Hook/DatabaseHook.php | 2 +- .../bootstrap/Context/Tasks/CacheCleaner.php | 2 +- .../bootstrap/Context/TestRunnerContext.php | 14 ++++++------- .../DelegatingSymfonyServiceContainer.php | 11 +++------- ...legatingSymfonyServiceContainerFactory.php | 10 ++-------- .../Loader/DelegatingLoaderHelper.php | 2 +- .../Magento2SymfonyServiceContainer.php | 5 +---- .../MagentoObjectManagerInitListener.php | 10 ++++------ src/ServiceContainer/config/services.php | 6 +++--- tests/Context/Tasks/CacheCleanerTest.php | 9 +++------ tests/Context/Tasks/PurgerTest.php | 4 ++-- ...tingSymfonyServiceContainerFactoryTest.php | 3 +-- .../Magento2SymfonyServiceContainerTest.php | 3 +-- .../MagentoObjectManagerInitListenerTest.php | 7 +++---- .../Magento2ExtensionTest.php | 2 +- 20 files changed, 50 insertions(+), 95 deletions(-) diff --git a/ecs.php b/ecs.php index f7e0ea3..db10619 100644 --- a/ecs.php +++ b/ecs.php @@ -146,7 +146,6 @@ $ecsConfig->rule(SelfAccessorFixer::class); $ecsConfig->rule(ShortScalarCastFixer::class); $ecsConfig->rule(SingleClassElementPerStatementFixer::class); - $ecsConfig->rule(TrailingCommaInMultilineFixer::class); $ecsConfig->rule(TrimArraySpacesFixer::class); $ecsConfig->rule(WhitespaceAfterCommaInArrayFixer::class); $ecsConfig->rule(NoEmptyCommentFixer::class); @@ -220,6 +219,12 @@ $ecsConfig->rule(TernaryOperatorSpacesFixer::class); $ecsConfig->rule(TernaryToNullCoalescingFixer::class); $ecsConfig->rule(UnaryOperatorSpacesFixer::class); + $ecsConfig->ruleWithConfiguration(TrailingCommaInMultilineFixer::class, [ + 'elements' => [ + 'arguments', + 'parameters', + ], + ]); $ecsConfig->ruleWithConfiguration(NoMixedEchoPrintFixer::class, ['use' => 'echo']); $ecsConfig->ruleWithConfiguration(ArraySyntaxFixer::class, ['syntax' => 'short']); $ecsConfig->ruleWithConfiguration(ClassDefinitionFixer::class, [ diff --git a/features/bootstrap/Context/Components/Customer/CustomerContext.php b/features/bootstrap/Context/Components/Customer/CustomerContext.php index ee99fd6..469b0e4 100644 --- a/features/bootstrap/Context/Components/Customer/CustomerContext.php +++ b/features/bootstrap/Context/Components/Customer/CustomerContext.php @@ -17,24 +17,12 @@ final class CustomerContext extends AbstractMagentoContext { - private SharedStorage $sharedStorage; - - private SearchCriteriaBuilder $searchCriteriaBuilder; - - private EncryptorInterface $encryptor; - - private StoreRepositoryInterface $storeRepository; - public function __construct( - SharedStorage $sharedStorage, - SearchCriteriaBuilder $searchCriteriaBuilder, - StoreRepositoryInterface $storeRepository, - EncryptorInterface $encryptor + private readonly SharedStorage $sharedStorage, + private readonly SearchCriteriaBuilder $searchCriteriaBuilder, + private readonly StoreRepositoryInterface $storeRepository, + private readonly EncryptorInterface $encryptor, ) { - $this->sharedStorage = $sharedStorage; - $this->searchCriteriaBuilder = $searchCriteriaBuilder; - $this->encryptor = $encryptor; - $this->storeRepository = $storeRepository; } private function getCustomerRepository(): CustomerRepositoryInterface diff --git a/features/bootstrap/Context/Components/ProcessFactory/Input/MagentoCommandInput.php b/features/bootstrap/Context/Components/ProcessFactory/Input/MagentoCommandInput.php index eb5e6e7..5d88f2d 100644 --- a/features/bootstrap/Context/Components/ProcessFactory/Input/MagentoCommandInput.php +++ b/features/bootstrap/Context/Components/ProcessFactory/Input/MagentoCommandInput.php @@ -12,7 +12,7 @@ final class MagentoCommandInput extends AbstractInput public function __construct( string $command = null, string $commandParameters = null, - string $workingDirectory = null + string $workingDirectory = null, ) { $this->setExecutor((new PhpExecutableFinder())->find() ?: null); $this->setExecutorParameters('-dmemory_limit=-1'); diff --git a/features/bootstrap/Context/Components/Stock/StockContext.php b/features/bootstrap/Context/Components/Stock/StockContext.php index 1f4f399..63a83a1 100644 --- a/features/bootstrap/Context/Components/Stock/StockContext.php +++ b/features/bootstrap/Context/Components/Stock/StockContext.php @@ -13,20 +13,11 @@ final class StockContext extends AbstractMagentoContext { - private StockRepositoryInterface $stockRepository; - - private SharedStorage $sharedStorage; - - private SearchCriteriaBuilder $searchCriteriaBuilder; - public function __construct( - StockRepositoryInterface $stockRepository, - SearchCriteriaBuilder $searchCriteriaFactory, - SharedStorage $sharedStorage + private readonly StockRepositoryInterface $stockRepository, + private readonly SearchCriteriaBuilder $searchCriteriaBuilder, + private readonly SharedStorage $sharedStorage, ) { - $this->stockRepository = $stockRepository; - $this->sharedStorage = $sharedStorage; - $this->searchCriteriaBuilder = $searchCriteriaFactory; } /** diff --git a/features/bootstrap/Context/Components/Store/StoreContext.php b/features/bootstrap/Context/Components/Store/StoreContext.php index 303b2b5..26b0eb7 100644 --- a/features/bootstrap/Context/Components/Store/StoreContext.php +++ b/features/bootstrap/Context/Components/Store/StoreContext.php @@ -9,23 +9,16 @@ use SEEC\Behat\Magento2Extension\Components\SharedStorage\SharedStorage; use SEEC\Behat\Magento2Extension\Features\Bootstrap\Context\AbstractMagentoContext; use SEEC\Behat\Magento2Extension\Features\Bootstrap\Context\Tasks\DefaultFixtures; -use SEEC\Behat\Magento2Extension\Features\Bootstrap\Context\Tasks\DefaultFixturesInterface; final class StoreContext extends AbstractMagentoContext { - private SharedStorage $sharedStorage; - private AdapterInterface $resourceConnection; - private DefaultFixturesInterface $fixtureFactory; - public function __construct( ResourceConnection $resourceConnection, - DefaultFixtures $fixtureFactory, - SharedStorage $sharedStorage + private readonly DefaultFixtures $fixtureFactory, + private readonly SharedStorage $sharedStorage, ) { - $this->sharedStorage = $sharedStorage; - $this->fixtureFactory = $fixtureFactory; $this->resourceConnection = $resourceConnection->getConnection(); } diff --git a/features/bootstrap/Context/Hook/DatabaseHook.php b/features/bootstrap/Context/Hook/DatabaseHook.php index 45c835e..f5c24ad 100644 --- a/features/bootstrap/Context/Hook/DatabaseHook.php +++ b/features/bootstrap/Context/Hook/DatabaseHook.php @@ -28,7 +28,7 @@ public function __construct( ResourceConnection $resource, DefaultFixtures $defaultFixtures = null, CacheCleaner $cacheCleaner = null, - Purger $purger = null + Purger $purger = null, ) { $this->resource = $resource; $this->fixturesManager = $defaultFixtures ?? new DefaultFixtures(); diff --git a/features/bootstrap/Context/Tasks/CacheCleaner.php b/features/bootstrap/Context/Tasks/CacheCleaner.php index cbb6396..4776414 100644 --- a/features/bootstrap/Context/Tasks/CacheCleaner.php +++ b/features/bootstrap/Context/Tasks/CacheCleaner.php @@ -20,7 +20,7 @@ final class CacheCleaner implements CacheCleanerInterface public function __construct( MagentoPathProviderInterface $magentoPathProvider = null, Filesystem $filesystem = null, - Finder $finder = null + Finder $finder = null, ) { $this->magentoPathProvider = $magentoPathProvider ?? new MagentoPathProvider(); $this->fileSystem = $filesystem ?? new Filesystem(); diff --git a/features/bootstrap/Context/TestRunnerContext.php b/features/bootstrap/Context/TestRunnerContext.php index 8e3f4e2..da8d4ed 100644 --- a/features/bootstrap/Context/TestRunnerContext.php +++ b/features/bootstrap/Context/TestRunnerContext.php @@ -40,7 +40,7 @@ public function __construct( ?WorkingDirectoryServiceInterface $workingDirectoryService = null, ?MagentoPathProviderInterface $magentoPathProvider = null, ?CacheCleaner $cacheCleaner = null, - ?string $workingDirectory = null + ?string $workingDirectory = null, ) { $this->filesystem = $fileSystem ?: new Filesystem(); $this->processFactory = $processFactory ?: new ProcessFactory(); @@ -57,7 +57,7 @@ public function createWorkingDirectory(): void $this->filesystem->copy( sprintf('%s/app/etc/config.php', $this->getMagentoRootDirectory()), '/tmp/config.php.backup', - true + true, ); } @@ -192,7 +192,7 @@ public function theBehatHelperServiceClassFileContains(string $className, PyStri $file = sprintf( '%s/features/bootstrap/%s.php', $this->getWorkingDirectory(), - str_replace('\\', '/', $className) + str_replace('\\', '/', $className), ); $this->createFile($file, $content->getRaw()); } @@ -226,7 +226,7 @@ public function iRunTheMagentoCommand(string $command, ?string $arguments = null protected function runMagentoCommand(?string $command = null, ?string $arguments = null): void { $magentoProcess = $this->processFactory->createFromInput( - new MagentoCommandInput($command, $arguments, $this->getMagentoRootDirectory()) + new MagentoCommandInput($command, $arguments, $this->getMagentoRootDirectory()), ); $this->addProcess($magentoProcess); @@ -238,8 +238,8 @@ protected function runMagentoCommand(?string $command = null, ?string $arguments sprintf( 'Expected Exit Code of Magento Process to be 0, got %d with message %s', $magentoProcess->getExitCode(), - $magentoProcess->getErrorOutput() - ) + $magentoProcess->getErrorOutput(), + ), ); } @@ -258,7 +258,7 @@ private function revertMagentoConfig(): void $this->filesystem->copy( '/tmp/config.php.backup', sprintf('%s/app/etc/config.php', $this->getMagentoRootDirectory()), - true + true, ); $this->filesystem->remove('/tmp/config.php.backup'); $this->cacheCleaner->clean(false); diff --git a/src/HelperContainer/DelegatingSymfonyServiceContainer.php b/src/HelperContainer/DelegatingSymfonyServiceContainer.php index 8a5a383..351ed5e 100644 --- a/src/HelperContainer/DelegatingSymfonyServiceContainer.php +++ b/src/HelperContainer/DelegatingSymfonyServiceContainer.php @@ -14,15 +14,10 @@ final class DelegatingSymfonyServiceContainer extends SymfonyServiceContainer implements ServiceContainerInterface { - /** - * @var SymfonyServiceContainer[] - */ - private array $fallbackContainers; - - public function __construct(array $symfonyServiceContainers) - { + public function __construct( + private readonly array $fallbackContainers, + ) { parent::__construct(); - $this->fallbackContainers = $symfonyServiceContainers; } public function has(string $id): bool diff --git a/src/HelperContainer/Factory/DelegatingSymfonyServiceContainerFactory.php b/src/HelperContainer/Factory/DelegatingSymfonyServiceContainerFactory.php index cb92570..430f3c3 100644 --- a/src/HelperContainer/Factory/DelegatingSymfonyServiceContainerFactory.php +++ b/src/HelperContainer/Factory/DelegatingSymfonyServiceContainerFactory.php @@ -10,16 +10,10 @@ final class DelegatingSymfonyServiceContainerFactory implements DelegatingSymfonyServiceContainerFactoryInterface { - private string $basePath; - - private DelegatingLoaderHelperInterface $loaderHelper; - public function __construct( - string $basePath, - DelegatingLoaderHelperInterface $loaderHelper + private readonly string $basePath, + private readonly DelegatingLoaderHelperInterface $loaderHelper, ) { - $this->basePath = $basePath; - $this->loaderHelper = $loaderHelper; } public function create(ConfigInterface $config, array $symfonyServiceContainers): DelegatingSymfonyServiceContainer diff --git a/src/HelperContainer/Loader/DelegatingLoaderHelper.php b/src/HelperContainer/Loader/DelegatingLoaderHelper.php index e5c7f7a..1684a09 100644 --- a/src/HelperContainer/Loader/DelegatingLoaderHelper.php +++ b/src/HelperContainer/Loader/DelegatingLoaderHelper.php @@ -22,7 +22,7 @@ public function loadFiles(ContainerBuilder $container, string $file, string $bas new XmlFileLoader($container, $fileLocator), new YamlFileLoader($container, $fileLocator), new PhpFileLoader($container, $fileLocator), - ]) + ]), ); $delegatingLoader->load($file); } diff --git a/src/HelperContainer/Magento2SymfonyServiceContainer.php b/src/HelperContainer/Magento2SymfonyServiceContainer.php index 770c5c4..97257c1 100644 --- a/src/HelperContainer/Magento2SymfonyServiceContainer.php +++ b/src/HelperContainer/Magento2SymfonyServiceContainer.php @@ -12,13 +12,10 @@ final class Magento2SymfonyServiceContainer extends SymfonyServiceContainer implements ServiceContainerInterface { - private MagentoObjectManagerInterface $magentoObjectManager; - public function __construct( - MagentoObjectManagerInterface $magentoObjectManager + private readonly MagentoObjectManagerInterface $magentoObjectManager, ) { parent::__construct(); - $this->magentoObjectManager = $magentoObjectManager; } public function has($id): bool diff --git a/src/Listener/MagentoObjectManagerInitListener.php b/src/Listener/MagentoObjectManagerInitListener.php index f227fb3..afa1a4e 100644 --- a/src/Listener/MagentoObjectManagerInitListener.php +++ b/src/Listener/MagentoObjectManagerInitListener.php @@ -23,11 +23,9 @@ final class MagentoObjectManagerInitListener implements MagentoObjectManagerInitListenerInterface { - private ConfigInterface $config; - - public function __construct(ConfigInterface $config) - { - $this->config = $config; + public function __construct( + private readonly ConfigInterface $config, + ) { } public static function getSubscribedEvents(): array @@ -65,7 +63,7 @@ public function initApplication(SuiteTested $event): void foreach ($areas as $area) { $config = array_replace_recursive( $config, - $this->arrayRecursiveDiff($configLoader->load($area), $configLoader->load(Area::AREA_GLOBAL)) + $this->arrayRecursiveDiff($configLoader->load($area), $configLoader->load(Area::AREA_GLOBAL)), ); } diff --git a/src/ServiceContainer/config/services.php b/src/ServiceContainer/config/services.php index f1e1b2f..315c065 100644 --- a/src/ServiceContainer/config/services.php +++ b/src/ServiceContainer/config/services.php @@ -24,12 +24,12 @@ $services->set( 'seec.behat.magento2_extension.helper_container.loader.delegating_loader_helper', - DelegatingLoaderHelper::class + DelegatingLoaderHelper::class, )->public(); $services->set( 'seec.behat.magento2_extension.delegating_symfony_service_container_factory', - DelegatingSymfonyServiceContainerFactory::class + DelegatingSymfonyServiceContainerFactory::class, ) ->public() ->args([ @@ -64,7 +64,7 @@ $services->set( 'seec.magento2_extension.object_manager_initializer_listener', - MagentoObjectManagerInitListener::class + MagentoObjectManagerInitListener::class, ) ->tag('event_dispatcher.subscriber') ->args([service('seec.magento2_extension.config')]); diff --git a/tests/Context/Tasks/CacheCleanerTest.php b/tests/Context/Tasks/CacheCleanerTest.php index 0a91105..38bf108 100644 --- a/tests/Context/Tasks/CacheCleanerTest.php +++ b/tests/Context/Tasks/CacheCleanerTest.php @@ -17,14 +17,11 @@ final class CacheCleanerTest extends TestCase { private CacheCleanerInterface $cacheCleaner; - /** @var MagentoPathProviderInterface|MockObject|object */ - private object $pathProvider; + private MagentoPathProviderInterface|MockObject $pathProvider; - /** @var Filesystem|MockObject|object */ - private object $fileSystem; + private Filesystem|MockObject $fileSystem; - /** @var Finder|MockObject|object */ - private object $finder; + private Finder|MockObject $finder; public function setUp(): void { diff --git a/tests/Context/Tasks/PurgerTest.php b/tests/Context/Tasks/PurgerTest.php index c934f8c..e11a30c 100644 --- a/tests/Context/Tasks/PurgerTest.php +++ b/tests/Context/Tasks/PurgerTest.php @@ -33,7 +33,7 @@ public function test_it_will_attempt_to_purge_all_tables(): void ->with(...$this->withConsecutive( ['SET FOREIGN_KEY_CHECKS = 0'], ['TRUNCATE TABLE table1'], - ['SET FOREIGN_KEY_CHECKS = 1'] + ['SET FOREIGN_KEY_CHECKS = 1'], )); $mockSelect = $this->createMock(Select::class); @@ -45,7 +45,7 @@ public function test_it_will_attempt_to_purge_all_tables(): void ->method('from') ->with(...$this->withConsecutive( ['table1', 'COUNT(*)'], - ['table2', 'COUNT(*)'] + ['table2', 'COUNT(*)'], )); $mockConnection->expects($this->exactly(2)) ->method('fetchOne') diff --git a/tests/HelperContainer/Factory/DelegatingSymfonyServiceContainerFactoryTest.php b/tests/HelperContainer/Factory/DelegatingSymfonyServiceContainerFactoryTest.php index 60978cd..2942ded 100644 --- a/tests/HelperContainer/Factory/DelegatingSymfonyServiceContainerFactoryTest.php +++ b/tests/HelperContainer/Factory/DelegatingSymfonyServiceContainerFactoryTest.php @@ -18,8 +18,7 @@ final class DelegatingSymfonyServiceContainerFactoryTest extends TestCase private string $basePath; - /** object|MockObject|DelegatingLoaderHelperInterface */ - private object $helper; + private DelegatingLoaderHelperInterface|MockObject $helper; public function setUp(): void { diff --git a/tests/HelperContainer/Magento2SymfonyServiceContainerTest.php b/tests/HelperContainer/Magento2SymfonyServiceContainerTest.php index fd4666d..e250b10 100644 --- a/tests/HelperContainer/Magento2SymfonyServiceContainerTest.php +++ b/tests/HelperContainer/Magento2SymfonyServiceContainerTest.php @@ -15,8 +15,7 @@ final class Magento2SymfonyServiceContainerTest extends TestCase { - /** @var MockObject|MagentoObjectManagerInterface|object */ - private object $magentoObjectManager; + private MagentoObjectManagerInterface|MockObject $magentoObjectManager; private ServiceContainerInterface $container; diff --git a/tests/Listener/MagentoObjectManagerInitListenerTest.php b/tests/Listener/MagentoObjectManagerInitListenerTest.php index c1421db..cd2b1e8 100644 --- a/tests/Listener/MagentoObjectManagerInitListenerTest.php +++ b/tests/Listener/MagentoObjectManagerInitListenerTest.php @@ -28,8 +28,7 @@ final class MagentoObjectManagerInitListenerTest extends TestCase private MagentoObjectManagerInitListenerInterface $listener; - /** @var ConfigInterface|MockObject */ - private object $config; + private ConfigInterface|MockObject $config; public function setUp(): void { @@ -97,13 +96,13 @@ public function test_it_will_create_an_admin_user_on_demand_when_bootstrapping_a [Registry::class], [Collection::class], ['Magento\User\Model\User'], - [Session::class] + [Session::class], )) ->willReturnOnConsecutiveCalls( $registryMock, $collectionMock, $user, - $sessionMock + $sessionMock, ); $registryMock->expects($this->once()) diff --git a/tests/ServiceContainer/Magento2ExtensionTest.php b/tests/ServiceContainer/Magento2ExtensionTest.php index 529a946..4d9d8c8 100644 --- a/tests/ServiceContainer/Magento2ExtensionTest.php +++ b/tests/ServiceContainer/Magento2ExtensionTest.php @@ -56,7 +56,7 @@ public function test_it_can_correctly_load_config_into_container(): void ->method('set') ->with(...$this->withConsecutive( ['seec.magento2_extension.config', $this->isInstanceOf(ConfigInterface::class)], - ['seec.behat_service_container', $containerBuilder] + ['seec.behat_service_container', $containerBuilder], )); $this->extension->load($containerBuilder, [ From 5d0c0bc4f362a6c8b1ba9cde9bf56ba4c4610b72 Mon Sep 17 00:00:00 2001 From: Maximilian Graf Schimmelmann Date: Tue, 1 Aug 2023 17:42:11 +0200 Subject: [PATCH 09/24] Move PHPStan to Behat step as magento is built in that step and required for static analysis. --- .github/workflows/ci.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 825c502..f5e41d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: env: MYSQL_USER: magento MYSQL_PASSWORD: magento - MYSQL_DATABASE: magentodb + MYSQL_DATABASE: magento MYSQL_ROOT_PASSWORD: root ports: - 3306:3306 @@ -95,6 +95,12 @@ jobs: run: "composer install --no-interaction --no-progress --no-suggest" working-directory: 'magento/vendor/seec/behat-magento2-extension' + - name: "PhpStan for Src and Feature Contexts" + run: "vendor/bin/phpstan analyse --error-format=checkstyle src/ features/ --level=8 | cs2pr" + + - name: "PhpStan for Test" + run: "vendor/bin/phpstan analyse --error-format=checkstyle tests/ --level=6 | cs2pr" + - name: "Run full test suite" run: "vendor/bin/behat --tags=@virtual --stop-on-failure --config behat.yml" working-directory: 'magento/vendor/seec/behat-magento2-extension' @@ -136,14 +142,8 @@ jobs: - name: "Install dependencies" run: "composer install --no-interaction --no-progress --no-suggest" - - name: "EasyCodingStandards for Src" + - name: "EasyCodingStandards for Src, Features and Tests" run: "vendor/bin/ecs check src/ features/ tests/ --no-interaction --no-progress-bar" - - name: "PhpStan for Src and Feature Contexts" - run: "vendor/bin/phpstan analyse --error-format=checkstyle src/ features/ --level=8 | cs2pr" - - - name: "PhpStan for Test" - run: "vendor/bin/phpstan analyse --error-format=checkstyle tests/ --level=6 | cs2pr" - - name: "Run unit tests" run: "vendor/bin/phpunit tests/" From 482e019dc8cc5e348da6b1c85a1390cfa74092f5 Mon Sep 17 00:00:00 2001 From: Maximilian Graf Schimmelmann Date: Tue, 1 Aug 2023 20:27:57 +0200 Subject: [PATCH 10/24] Adapt ci.yml --- .../Context/Tasks/MagentoPathProviderTest.php | 24 ------------------- 1 file changed, 24 deletions(-) delete mode 100644 tests/Context/Tasks/MagentoPathProviderTest.php diff --git a/tests/Context/Tasks/MagentoPathProviderTest.php b/tests/Context/Tasks/MagentoPathProviderTest.php deleted file mode 100644 index ee49076..0000000 --- a/tests/Context/Tasks/MagentoPathProviderTest.php +++ /dev/null @@ -1,24 +0,0 @@ -magentoPathProvider = new MagentoPathProvider(); - } - - public function test_it_can_get_magento_root_directory(): void - { - $this->assertDirectoryExists($this->magentoPathProvider->getMagentoRootDirectory()); - } -} From 3b6822101957ba13ff8bf309cb199dfad2391b16 Mon Sep 17 00:00:00 2001 From: Maximilian Graf Schimmelmann Date: Wed, 2 Aug 2023 11:14:23 +0200 Subject: [PATCH 11/24] Adapt cache clean logic, change some app logic to fit expected behavior, improve docker scripts --- docker-compose.yml | 20 ----------------- docker/php/etc/fetch-magento.sh | 4 ++-- docker/php/etc/run-ecs.sh | 2 +- docker/php/etc/run-phpstan.sh | 1 + docker/php/etc/run-tests.sh | 4 ++-- .../bootstrap/Context/Tasks/CacheCleaner.php | 11 ++++------ .../Context/Tasks/MagentoPathProvider.php | 5 +---- .../bootstrap/Context/TestRunnerContext.php | 2 +- src/ServiceContainer/Magento2Extension.php | 13 +++-------- tests/Context/Tasks/CacheCleanerTest.php | 22 +++---------------- 10 files changed, 18 insertions(+), 66 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 749a670..f9d51f1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,26 +22,6 @@ services: env_file: - .env - php-8.1: - build: - context: . - dockerfile: ./docker/php/Dockerfile - args: - PHP_VERSION: php:8.1.20-zts-alpine3.18 - MAGENTO_PUBLIC_KEY: ${MAGENTO_PUBLIC_KEY} - MAGENTO_SECRET_KEY: ${MAGENTO_SECRET_KEY} - volumes: - - ./:/var/www/html/vendor/seec/behat-magento2-extension - - ./behat.yml:/var/www/html/behat.yml - - ./features:/var/www/html/features - - ./magento:/var/www/html/vendor/magento-ext - environment: - PHP_IDE_CONFIG: serverName=magento2-behat-extension - extra_hosts: - - "host.docker.internal:host-gateway" - env_file: - - .env - mysql: image: mariadb:10.6 environment: diff --git a/docker/php/etc/fetch-magento.sh b/docker/php/etc/fetch-magento.sh index 05d1cb4..66516d6 100644 --- a/docker/php/etc/fetch-magento.sh +++ b/docker/php/etc/fetch-magento.sh @@ -2,12 +2,12 @@ if [ ! -f /var/www/html/composer.json ]; then rm -rf /var/www/html - composer create-project --no-install --repository=https://repo.magento.com/ magento/project-community-edition=2.4.3-p3 /var/www/html/ + composer create-project --no-install --repository=https://repo.magento.com/ magento/project-community-edition=2.4.6 /var/www/html/ cd /var/www/html composer config --no-plugins allow-plugins.magento/* true composer config --no-plugins allow-plugins.php-http/discovery true composer config --no-plugins allow-plugins.laminas/laminas-dependency-plugin true composer config --no-plugins allow-plugins.dealerdirect/phpcodesniffer-composer-installer true - composer require --dev behat/behat friends-of-behat/mink-extension behat/mink-goutte-driver + composer require --dev behat/behat tkotosz/test-area-magento2 composer install fi diff --git a/docker/php/etc/run-ecs.sh b/docker/php/etc/run-ecs.sh index e2e914a..553e1fb 100644 --- a/docker/php/etc/run-ecs.sh +++ b/docker/php/etc/run-ecs.sh @@ -1,6 +1,6 @@ #!/usr/bin/env sh -cd /var/www/html/behat-magento2-extension +cd /var/www/html/vendor/seec/behat-magento2-extension php vendor/bin/ecs check src/ --fix php vendor/bin/ecs check features/ --fix php vendor/bin/ecs check tests/ --fix diff --git a/docker/php/etc/run-phpstan.sh b/docker/php/etc/run-phpstan.sh index 5c8eb24..79763fe 100644 --- a/docker/php/etc/run-phpstan.sh +++ b/docker/php/etc/run-phpstan.sh @@ -1,5 +1,6 @@ #!/usr/bin/env sh +cd /var/www/html/vendor/seec/behat-magento2-extension php vendor/bin/phpstan analyse src/ --level=8 php vendor/bin/phpstan analyse features/ --level=6 php vendor/bin/phpstan analyse tests/ --level=6 diff --git a/docker/php/etc/run-tests.sh b/docker/php/etc/run-tests.sh index fd40e9b..7426109 100644 --- a/docker/php/etc/run-tests.sh +++ b/docker/php/etc/run-tests.sh @@ -1,5 +1,5 @@ #!/usr/bin/env sh +cd /var/www/html/vendor/seec/behat-magento2-extension php vendor/bin/phpunit tests/ -php vendor/bin/behat --config /var/www/html/behat-magento2-extension/behat.yml --suite without_compiled_di --strict --stop-on-failure -php vendor/bin/behat --config /var/www/html/behat-magento2-extension/behat.yml --suite with_compiled_di --strict --stop-on-failure +php vendor/bin/behat --stop-on-failure diff --git a/features/bootstrap/Context/Tasks/CacheCleaner.php b/features/bootstrap/Context/Tasks/CacheCleaner.php index 4776414..9affcbd 100644 --- a/features/bootstrap/Context/Tasks/CacheCleaner.php +++ b/features/bootstrap/Context/Tasks/CacheCleaner.php @@ -19,7 +19,7 @@ final class CacheCleaner implements CacheCleanerInterface public function __construct( MagentoPathProviderInterface $magentoPathProvider = null, - Filesystem $filesystem = null, + FileSystem $filesystem = null, Finder $finder = null, ) { $this->magentoPathProvider = $magentoPathProvider ?? new MagentoPathProvider(); @@ -30,12 +30,9 @@ public function __construct( public function clean(bool $cleanObjectManager = true): void { $directory = $this->magentoPathProvider->getMagentoRootDirectory(); - $cacheFolder = $this->finder->in(sprintf('%s/var/cache/', $directory)); - foreach ($cacheFolder as $cacheFolderItem) { - if (str_starts_with($cacheFolderItem->getRelativePathname(), '.') === false) { - $this->fileSystem->remove($cacheFolderItem->getPathname()); - } - } + $this->finder->directories(); + $cacheFolder = $this->finder->in(sprintf('%s/var/cache', $directory)); + $this->fileSystem->remove($cacheFolder); if ($cleanObjectManager) { /** @var Config $objectManager */ diff --git a/features/bootstrap/Context/Tasks/MagentoPathProvider.php b/features/bootstrap/Context/Tasks/MagentoPathProvider.php index dc429d6..981536f 100644 --- a/features/bootstrap/Context/Tasks/MagentoPathProvider.php +++ b/features/bootstrap/Context/Tasks/MagentoPathProvider.php @@ -4,8 +4,6 @@ namespace SEEC\Behat\Magento2Extension\Features\Bootstrap\Context\Tasks; -use Webmozart\Assert\Assert; - final class MagentoPathProvider implements MagentoPathProviderInterface { public function getMagentoRootDirectory(): string @@ -13,8 +11,7 @@ public function getMagentoRootDirectory(): string $path = __DIR__; $maxCount = count(explode('/', $path)); $i = 0; - while (!file_exists(sprintf('%s/app/etc/env.php', $path))) { - Assert::lessThan($i++, $maxCount, 'Could not find Magento root directory'); + while (!file_exists(sprintf('%s/app/etc/env.php', $path)) && $i++ < $maxCount) { $path = sprintf('%s/..', $path); } diff --git a/features/bootstrap/Context/TestRunnerContext.php b/features/bootstrap/Context/TestRunnerContext.php index da8d4ed..3dc1054 100644 --- a/features/bootstrap/Context/TestRunnerContext.php +++ b/features/bootstrap/Context/TestRunnerContext.php @@ -45,7 +45,7 @@ public function __construct( $this->filesystem = $fileSystem ?: new Filesystem(); $this->processFactory = $processFactory ?: new ProcessFactory(); $this->magentoPathProvider = $magentoPathProvider ?? new MagentoPathProvider(); - $this->cacheCleaner = $cacheCleaner ?? new CacheCleaner($this->magentoPathProvider, $this->filesystem); + $this->cacheCleaner = $cacheCleaner ?? new CacheCleaner($this->magentoPathProvider); parent::__construct($fileSystem, $processFactory, $workingDirectoryService, $workingDirectory); } diff --git a/src/ServiceContainer/Magento2Extension.php b/src/ServiceContainer/Magento2Extension.php index 3701ec9..0ca457f 100644 --- a/src/ServiceContainer/Magento2Extension.php +++ b/src/ServiceContainer/Magento2Extension.php @@ -5,6 +5,7 @@ namespace SEEC\Behat\Magento2Extension\ServiceContainer; use Behat\Testwork\ServiceContainer\ExtensionManager; +use SEEC\Behat\Magento2Extension\Features\Bootstrap\Context\Tasks\MagentoPathProvider; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -55,16 +56,8 @@ public function process(TaggedContainerInterface $container): void private function getMagentoBootstrapPath(): string { - $path = sprintf('%s/app/bootstrap.php', getcwd()); - $prefix = getcwd() ?: '/'; - $limit = count(explode('/', $prefix)); - $i = 0; - while (!file_exists($path)) { - Assert::lessThan(++$i, $limit, 'Could not find Magento root directory'); - $prefix = sprintf('%s/..', $prefix); - $path = sprintf('%s/app/bootstrap.php', $prefix); - } + $magentoPathProvider = new MagentoPathProvider(); - return $path; + return sprintf('%s/app/bootstrap.php', $magentoPathProvider->getMagentoRootDirectory()); } } diff --git a/tests/Context/Tasks/CacheCleanerTest.php b/tests/Context/Tasks/CacheCleanerTest.php index 38bf108..d80c2c6 100644 --- a/tests/Context/Tasks/CacheCleanerTest.php +++ b/tests/Context/Tasks/CacheCleanerTest.php @@ -11,7 +11,6 @@ use SEEC\Behat\Magento2Extension\Features\Bootstrap\Context\Tasks\MagentoPathProviderInterface; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Finder\Finder; -use Symfony\Component\Finder\SplFileInfo; final class CacheCleanerTest extends TestCase { @@ -37,29 +36,14 @@ public function test_it_will_attempt_to_clear_the_cache_correctly(): void ->method('getMagentoRootDirectory') ->willReturn('/var/www/html'); - $file1 = $this->createMock(SplFileInfo::class); - $file1->expects($this->once()) - ->method('getRelativePathname') - ->willReturn('.'); - $file1->expects($this->never()) - ->method('getPathname'); - - $file2 = $this->createMock(SplFileInfo::class); - $file2->expects($this->once()) - ->method('getRelativePathname') - ->willReturn('test'); - $file2->expects($this->once()) - ->method('getPathname') - ->willReturn('/var/www/html/var/cache/test'); - $this->finder->expects($this->once()) ->method('in') - ->with('/var/www/html/var/cache/') - ->willReturn([$file1, $file2]); + ->with('/var/www/html/var/cache') + ->willReturnSelf(); $this->fileSystem->expects($this->once()) ->method('remove') - ->with('/var/www/html/var/cache/test'); + ->with($this->finder); $this->cacheCleaner->clean(false); } From daf3b0f1653cdde61a1599191b054562cfaca8d8 Mon Sep 17 00:00:00 2001 From: Maximilian Graf Schimmelmann Date: Wed, 2 Aug 2023 11:29:27 +0200 Subject: [PATCH 12/24] Adapt Github Action Yaml in attempt to fix opensearch connectivity issue --- .github/workflows/ci.yml | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f5e41d9..950df6c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,6 +33,8 @@ jobs: opensearch: image: opensearchproject/opensearch:2.7.0 + ports: + - 9200:9200 env: discovery.type: single-node cluster.name: opensearch-cluster @@ -40,9 +42,10 @@ jobs: bootstrap.memory_lock: true OPENSEARCH_JAVA_OPTS: -Xms512m -Xmx512m DISABLE_INSTALL_DEMO_CONFIG: true - DISABLE_SECURITY_PLUGIN: true - ports: - - "8892:9200" + plugins.security.disabled: true + plugins.security.ssl.http.enabled: false + options: --health-cmd="curl http://localhost:9200/_cluster/health" --health-interval=10s --health-timeout=5s --health-retries=10 + steps: - name: "Install PHP" @@ -81,7 +84,29 @@ jobs: run: | rm -f app/etc/env.php mkdir -p pub/static pub/media - bin/magento setup:install --admin-email "magento@magento.com" --admin-firstname "admin" --admin-lastname "admin" --admin-password "admin123!#" --admin-user "admin" --backend-frontname admin --base-url "http://magento.test" --db-host 127.0.0.1 --db-name magento --db-user magento --db-password magento --session-save files --use-rewrites 1 --use-secure 0 --opensearch-host="opensearch" --opensearch-port="9200" --timezone="Europe/Amsterdam" -vvv + bin/magento setup:install \ + --admin-email="magento@magento.com" \ + --admin-firstname="admin" \ + --admin-lastname="admin" \ + --admin-password="admin123!#" \ + --admin-user="admin" \ + --backend-frontname="admin" \ + --base-url="http://magento.test" \ + --cleanup-database \ + --db-host="mysql" \ + --db-name="magento" \ + --db-password="magento" \ + --db-user="magento" \ + --opensearch-host="opensearch" \ + --opensearch-port=9200 \ + --search-engine="opensearch" \ + --session-save="files" \ + --skip-db-validation \ + --timezone="Europe/Amsterdam" \ + --use-rewrites=1 \ + --use-secure-admin=0 \ + --use-secure=0 \ + -vvv bin/magento setup:upgrade working-directory: 'magento' From 7c21aa90e40f5c5be3232c4718c093229df981cc Mon Sep 17 00:00:00 2001 From: Maximilian Graf Schimmelmann Date: Wed, 2 Aug 2023 11:34:49 +0200 Subject: [PATCH 13/24] Replace mysql/opensearch with 127.0.0.1 to establish connectivity. --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 950df6c..9b46f3a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,11 +93,11 @@ jobs: --backend-frontname="admin" \ --base-url="http://magento.test" \ --cleanup-database \ - --db-host="mysql" \ + --db-host="127.0.0.1" \ --db-name="magento" \ --db-password="magento" \ --db-user="magento" \ - --opensearch-host="opensearch" \ + --opensearch-host="127.0.0.1" \ --opensearch-port=9200 \ --search-engine="opensearch" \ --session-save="files" \ From c358de3976868efe923c34c29ff4bec413b7ce26 Mon Sep 17 00:00:00 2001 From: Maximilian Graf Schimmelmann Date: Wed, 2 Aug 2023 11:40:56 +0200 Subject: [PATCH 14/24] Add minimum stability to test specific local case of module and prevent errors. --- .github/workflows/ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b46f3a..1f2f761 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,7 +72,7 @@ jobs: - name: "Create Magento 2.4.6 project with testing dependencies" run: | - composer create-project --repository=https://repo.magento.com/ magento/project-community-edition=2.4.6 magento + composer create-project --repository=https://repo.magento.com/ magento/project-community-edition=2.4.6 magento --q if: hashFiles('magento/composer.json') == '' - name: "Checkout" @@ -112,8 +112,9 @@ jobs: - name: "Install Behat Magento 2 Extension in the Magento 2 Test Environment" run: | + composer config minimum-stability dev composer config repositories.behat-m2-extension path vendor/seec/behat-magento2-extension - composer require --dev seec/behat-magento2-extension:@dev + composer require seec/behat-magento2-extension:@dev working-directory: 'magento' - name: "Install Behat Magento 2 Extension's testing dependencies" From c4574b01e0ef46e85fb4375095906660f2253739 Mon Sep 17 00:00:00 2001 From: Maximilian Graf Schimmelmann Date: Wed, 2 Aug 2023 11:42:55 +0200 Subject: [PATCH 15/24] remove attempt for quiet create package. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f2f761..a8ee636 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,7 +72,7 @@ jobs: - name: "Create Magento 2.4.6 project with testing dependencies" run: | - composer create-project --repository=https://repo.magento.com/ magento/project-community-edition=2.4.6 magento --q + composer create-project --repository=https://repo.magento.com/ magento/project-community-edition=2.4.6 magento if: hashFiles('magento/composer.json') == '' - name: "Checkout" From 33f40775ccb47e9235376e26ac5ad4b710e8ef77 Mon Sep 17 00:00:00 2001 From: Maximilian Graf Schimmelmann Date: Wed, 2 Aug 2023 11:49:56 +0200 Subject: [PATCH 16/24] Add Working Directory to Phpstan declaration --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8ee636..c5be58f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -123,9 +123,11 @@ jobs: - name: "PhpStan for Src and Feature Contexts" run: "vendor/bin/phpstan analyse --error-format=checkstyle src/ features/ --level=8 | cs2pr" + working-directory: 'magento/vendor/seec/behat-magento2-extension' - name: "PhpStan for Test" run: "vendor/bin/phpstan analyse --error-format=checkstyle tests/ --level=6 | cs2pr" + working-directory: 'magento/vendor/seec/behat-magento2-extension' - name: "Run full test suite" run: "vendor/bin/behat --tags=@virtual --stop-on-failure --config behat.yml" From af42c6785eb3084239db3b68c4c0e17e0fee848e Mon Sep 17 00:00:00 2001 From: Maximilian Graf Schimmelmann Date: Thu, 3 Aug 2023 00:19:51 +0200 Subject: [PATCH 17/24] Remove behat.yml, remove force directory setting in dist, change git action. --- .github/workflows/ci.yml | 12 ++++++------ behat.yml | 11 ----------- behat.yml.dist | 6 ++---- composer.json | 2 +- docker/php/etc/install-magento.sh | 9 +++++++-- features/bootstrap/Context/TestRunnerContext.php | 4 +++- 6 files changed, 19 insertions(+), 25 deletions(-) delete mode 100644 behat.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c5be58f..6e4cd01 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -110,27 +110,27 @@ jobs: bin/magento setup:upgrade working-directory: 'magento' - - name: "Install Behat Magento 2 Extension in the Magento 2 Test Environment" + - name: "Install BehatMagento2 Extension" run: | composer config minimum-stability dev composer config repositories.behat-m2-extension path vendor/seec/behat-magento2-extension composer require seec/behat-magento2-extension:@dev working-directory: 'magento' - - name: "Install Behat Magento 2 Extension's testing dependencies" + - name: "Install BehatMagento2 Extension dependencies" run: "composer install --no-interaction --no-progress --no-suggest" working-directory: 'magento/vendor/seec/behat-magento2-extension' - - name: "PhpStan for Src and Feature Contexts" + - name: "Run phpstan for src/ and feature/" run: "vendor/bin/phpstan analyse --error-format=checkstyle src/ features/ --level=8 | cs2pr" working-directory: 'magento/vendor/seec/behat-magento2-extension' - - name: "PhpStan for Test" + - name: "Run phpstan for tests/" run: "vendor/bin/phpstan analyse --error-format=checkstyle tests/ --level=6 | cs2pr" working-directory: 'magento/vendor/seec/behat-magento2-extension' - - name: "Run full test suite" - run: "vendor/bin/behat --tags=@virtual --stop-on-failure --config behat.yml" + - name: "Run Behat tests" + run: "vendor/bin/behat --stop-on-failure --config behat.yml.dist" working-directory: 'magento/vendor/seec/behat-magento2-extension' code-style: diff --git a/behat.yml b/behat.yml deleted file mode 100644 index 5f9bf46..0000000 --- a/behat.yml +++ /dev/null @@ -1,11 +0,0 @@ -default: - formatters: - progress: true - - suites: - default: - contexts: - - SEEC\Behat\Magento2Extension\Features\Bootstrap\Context\TestRunnerContext: - workingDirectory: /var/www/html/vendor/seec/behat-magento2-extension/test_dir - - SEEC\BehatTestRunner\Context\TestRunnerContext: - workingDirectory: /var/www/html/vendor/seec/behat-magento2-extension/test_dir diff --git a/behat.yml.dist b/behat.yml.dist index 5f9bf46..1aab7a1 100644 --- a/behat.yml.dist +++ b/behat.yml.dist @@ -5,7 +5,5 @@ default: suites: default: contexts: - - SEEC\Behat\Magento2Extension\Features\Bootstrap\Context\TestRunnerContext: - workingDirectory: /var/www/html/vendor/seec/behat-magento2-extension/test_dir - - SEEC\BehatTestRunner\Context\TestRunnerContext: - workingDirectory: /var/www/html/vendor/seec/behat-magento2-extension/test_dir + - SEEC\Behat\Magento2Extension\Features\Bootstrap\Context\TestRunnerContext + - SEEC\BehatTestRunner\Context\TestRunnerContext diff --git a/composer.json b/composer.json index aae0b97..90c7c57 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "magento/module-backend": "*", "friends-of-behat/page-object-extension": "^0.3.2", "friends-of-behat/suite-settings-extension": "^1.1", - "seec/behat-test-runner": "dev-master", + "seec/behat-test-runner": "^8", "friends-of-behat/symfony-extension": "^2.0", "react/promise": "~2.0" }, diff --git a/docker/php/etc/install-magento.sh b/docker/php/etc/install-magento.sh index d68693d..c326e58 100644 --- a/docker/php/etc/install-magento.sh +++ b/docker/php/etc/install-magento.sh @@ -2,7 +2,8 @@ rm -f app/etc/env.php mkdir -p pub/static pub/media -$(which php) bin/magento setup:install --admin-email "magento@magento.com" \ +$(which php) bin/magento setup:install \ + --admin-email "magento@magento.com" \ --admin-firstname "admin" \ --admin-lastname "admin" \ --admin-password "admin123!#" \ @@ -16,9 +17,13 @@ $(which php) bin/magento setup:install --admin-email "magento@magento.com" \ --session-save files \ --use-rewrites 1 \ --use-secure 0 \ + --search-engine="opensearch" \ --opensearch-host="opensearch" \ --opensearch-port="9200" \ - --timezone="Europe/Amsterdam" -vvv + --timezone="Europe/Amsterdam" \ + --skip-db-validation \ + --cleanup-database \ + -vvv $(which php) bin/magento deploy:mode:set developer composer dump-autoload $(which php) bin/magento setup:upgrade diff --git a/features/bootstrap/Context/TestRunnerContext.php b/features/bootstrap/Context/TestRunnerContext.php index 3dc1054..c273f8b 100644 --- a/features/bootstrap/Context/TestRunnerContext.php +++ b/features/bootstrap/Context/TestRunnerContext.php @@ -16,6 +16,7 @@ use SEEC\BehatTestRunner\Context\Components\ProcessFactory\Factory\ProcessFactoryInterface; use SEEC\BehatTestRunner\Context\Services\WorkingDirectoryServiceInterface; use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Finder\Finder; use Webmozart\Assert\Assert; final class TestRunnerContext extends AbstractTestRunnerContext implements Context @@ -41,12 +42,13 @@ public function __construct( ?MagentoPathProviderInterface $magentoPathProvider = null, ?CacheCleaner $cacheCleaner = null, ?string $workingDirectory = null, + ?Finder $finder = null, ) { $this->filesystem = $fileSystem ?: new Filesystem(); $this->processFactory = $processFactory ?: new ProcessFactory(); $this->magentoPathProvider = $magentoPathProvider ?? new MagentoPathProvider(); $this->cacheCleaner = $cacheCleaner ?? new CacheCleaner($this->magentoPathProvider); - parent::__construct($fileSystem, $processFactory, $workingDirectoryService, $workingDirectory); + parent::__construct($fileSystem, $processFactory, $workingDirectoryService, $workingDirectory, $finder); } public function createWorkingDirectory(): void From 8e88ba55568ff47a79fac6ccf7f249c760d253d0 Mon Sep 17 00:00:00 2001 From: Maximilian Graf Schimmelmann Date: Thu, 3 Aug 2023 00:59:45 +0200 Subject: [PATCH 18/24] Change version to be used for behat test runner. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 90c7c57..aae0b97 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "magento/module-backend": "*", "friends-of-behat/page-object-extension": "^0.3.2", "friends-of-behat/suite-settings-extension": "^1.1", - "seec/behat-test-runner": "^8", + "seec/behat-test-runner": "dev-master", "friends-of-behat/symfony-extension": "^2.0", "react/promise": "~2.0" }, From 0e64a297f58c4baf4af332d89d52c8089e7e4383 Mon Sep 17 00:00:00 2001 From: Maximilian Graf Schimmelmann Date: Thu, 3 Aug 2023 10:19:15 +0200 Subject: [PATCH 19/24] Add broader catch scope with extra output to errors, adapted docker setup. --- .dockerignore | 19 +++++++++---------- docker-compose.yml | 12 +++++++++--- docker/php/Dockerfile | 14 ++++++++++---- docker/php/etc/entrypoint.sh | 1 + docker/php/etc/install-magento.sh | 1 + .../Context/Tasks/DefaultFixtures.php | 4 +++- 6 files changed, 33 insertions(+), 18 deletions(-) diff --git a/.dockerignore b/.dockerignore index 0b07001..2b89d65 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,10 +1,9 @@ -./.github -./docs -./docker -./docker/data -./docker-compose.yml -./vendor -!./docker/php/etc -!./docker/php/etc/* -./magento -./test_dir +/.github +/docs +/docker +/docker/data +/docker-compose.yml +!/docker/php/etc +!/docker/php/etc/* +/magento +/test_dir diff --git a/docker-compose.yml b/docker-compose.yml index f9d51f1..3d7b3e5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,9 +11,15 @@ services: MAGENTO_PUBLIC_KEY: ${MAGENTO_PUBLIC_KEY} MAGENTO_SECRET_KEY: ${MAGENTO_SECRET_KEY} volumes: - - ./:/var/www/html/vendor/seec/behat-magento2-extension - - ./behat.yml:/var/www/html/behat.yml - - ./features:/var/www/html/features + - ./src:/var/www/html/vendor/seec/behat-magento2-extension/src + - ./features:/var/www/html/vendor/seec/behat-magento2-extension/features + - ./tests:/var/www/html/vendor/seec/behat-magento2-extension/tests + - ./behat.yml.dist:/var/www/html/vendor/seec/behat-magento2-extension/behat.yml.dist + - ./ecs.php:/var/www/html/vendor/seec/behat-magento2-extension/ecs.php + - ./phpstan.neon:/var/www/html/vendor/seec/behat-magento2-extension/phpstan.neon + - ./composer.json:/var/www/html/vendor/seec/behat-magento2-extension/composer.json + - ./composer.lock:/var/www/html/vendor/seec/behat-magento2-extension/composer.lock + - ./vendor:/var/www/html/vendor/seec/behat-magento2-extension/vendor - ./magento:/var/www/html/vendor/magento-ext environment: PHP_IDE_CONFIG: serverName=magento2-behat-extension diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile index 577d5a2..32fa7e9 100644 --- a/docker/php/Dockerfile +++ b/docker/php/Dockerfile @@ -80,12 +80,18 @@ RUN create-auth-file RUN rm -rf /var/www/html/* RUN fetch-magento -COPY ./ /var/www/html/vendor/seec/behat-magento2-extension/ +COPY ./src /var/www/html/vendor/seec/behat-magento2-extension/src +COPY ./features /var/www/html/vendor/seec/behat-magento2-extension/features +COPY ./tests /var/www/html/vendor/seec/behat-magento2-extension/tests +COPY ./behat.yml.dist /var/www/html/vendor/seec/behat-magento2-extension/behat.yml.dist +COPY ./ecs.php /var/www/html/vendor/seec/behat-magento2-extension/ecs.php +COPY ./phpstan.neon /var/www/html/vendor/seec/behat-magento2-extension/phpstan.neon +COPY ./composer.json /var/www/html/vendor/seec/behat-magento2-extension/composer.json +COPY ./composer.lock /var/www/html/vendor/seec/behat-magento2-extension/composer.lock +COPY ./vendor /var/www/html/vendor/seec/behat-magento2-extension/vendor RUN install-extension -COPY ./behat.yml /var/www/html/behat.yml - -WORKDIR /var/www/html +WORKDIR /var/www/html/vendor/seec/behat-magento2-extension ENTRYPOINT ["/usr/local/bin/entrypoint"] CMD ["tail", "-F", "/var/www/html/php_error.log"] diff --git a/docker/php/etc/entrypoint.sh b/docker/php/etc/entrypoint.sh index c93f6c7..94e1930 100644 --- a/docker/php/etc/entrypoint.sh +++ b/docker/php/etc/entrypoint.sh @@ -1,5 +1,6 @@ #!/usr/bin/env sh create-auth-file +install-magento exec "$@" diff --git a/docker/php/etc/install-magento.sh b/docker/php/etc/install-magento.sh index c326e58..4657e1b 100644 --- a/docker/php/etc/install-magento.sh +++ b/docker/php/etc/install-magento.sh @@ -1,5 +1,6 @@ #!/usr/bin/env sh +cd /var/www/html rm -f app/etc/env.php mkdir -p pub/static pub/media $(which php) bin/magento setup:install \ diff --git a/features/bootstrap/Context/Tasks/DefaultFixtures.php b/features/bootstrap/Context/Tasks/DefaultFixtures.php index ec77e95..9ada363 100644 --- a/features/bootstrap/Context/Tasks/DefaultFixtures.php +++ b/features/bootstrap/Context/Tasks/DefaultFixtures.php @@ -7,6 +7,7 @@ use DomainException; use Magento\Framework\App\ObjectManager; use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\Exception\CouldNotSaveException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\InventoryApi\Api\Data\StockInterface; use Magento\InventoryApi\Api\StockRepositoryInterface; @@ -173,7 +174,8 @@ public function getOrCreateStock(string $name, AdapterInterface $connection): St $stock = $this->getObjectManager()->create(StockInterface::class); $stock->setName($name); $repo->save($stock); - } catch (DomainException $e) { + } catch (DomainException|CouldNotSaveException $e) { + print sprintf('Could not create stock regularry, retry with direct injection. Error: %s, File: %s:%s', $e->getMessage(), $e->getFile(), $e->getLine()) . PHP_EOL; $connection->insert('inventory_stock', ['stock_id' => 1, 'name' => $name]); $stock = $repo->get(1); } From be7e6d586a5b3ce8600e1080ed1f65435476e290 Mon Sep 17 00:00:00 2001 From: Maximilian Graf Schimmelmann Date: Thu, 3 Aug 2023 10:22:19 +0200 Subject: [PATCH 20/24] Applied ECS to default fixtures. --- features/bootstrap/Context/Tasks/DefaultFixtures.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/features/bootstrap/Context/Tasks/DefaultFixtures.php b/features/bootstrap/Context/Tasks/DefaultFixtures.php index 9ada363..ed8d0f5 100644 --- a/features/bootstrap/Context/Tasks/DefaultFixtures.php +++ b/features/bootstrap/Context/Tasks/DefaultFixtures.php @@ -175,7 +175,7 @@ public function getOrCreateStock(string $name, AdapterInterface $connection): St $stock->setName($name); $repo->save($stock); } catch (DomainException|CouldNotSaveException $e) { - print sprintf('Could not create stock regularry, retry with direct injection. Error: %s, File: %s:%s', $e->getMessage(), $e->getFile(), $e->getLine()) . PHP_EOL; + echo sprintf('Could not create stock regularry, retry with direct injection. Error: %s, File: %s:%s', $e->getMessage(), $e->getFile(), $e->getLine()) . \PHP_EOL; $connection->insert('inventory_stock', ['stock_id' => 1, 'name' => $name]); $stock = $repo->get(1); } @@ -185,10 +185,7 @@ public function getOrCreateStock(string $name, AdapterInterface $connection): St return $stock; } - /** - * @return GroupInterface|StoreInterface|WebsiteInterface|StockInterface|null - */ - private function getExistingEntity(string $interface, string $identifier): ?object + private function getExistingEntity(string $interface, string $identifier): null|GroupInterface|StoreInterface|WebsiteInterface|StockInterface { $checkBy = 'getCode'; switch ($interface) { From a3c5d1a1300b26d41626fafefe2cb083278ac7d5 Mon Sep 17 00:00:00 2001 From: Maximilian Graf Schimmelmann Date: Thu, 3 Aug 2023 11:54:38 +0200 Subject: [PATCH 21/24] Adapt Composer json to load specifically the subset of modules for 2.4.6 and add tag to behat indication on CI. --- .github/workflows/ci.yml | 2 +- composer.json | 4 +++- docker/php/Dockerfile | 3 +-- features/bootstrap/Context/Tasks/DefaultFixtures.php | 6 +++++- features/purging_feature.feature | 2 +- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e4cd01..d19723e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -130,7 +130,7 @@ jobs: working-directory: 'magento/vendor/seec/behat-magento2-extension' - name: "Run Behat tests" - run: "vendor/bin/behat --stop-on-failure --config behat.yml.dist" + run: "vendor/bin/behat --stop-on-failure --config behat.yml.dist --tags=@virtual" working-directory: 'magento/vendor/seec/behat-magento2-extension' code-style: diff --git a/composer.json b/composer.json index aae0b97..cbd1e27 100644 --- a/composer.json +++ b/composer.json @@ -20,10 +20,12 @@ } ], "minimum-stability": "dev", + "prefer-stable": true, "require": { "php": "^8.1", "behat/behat": "^3.7", - "magento/framework": "^103", + "magento/framework": "103.0.6", + "magento/module-store": "101.1.6", "container-interop/container-interop": "^1.2", "symfony/dependency-injection": "^6", "symfony/event-dispatcher": "^6", diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile index 32fa7e9..e5fbcd2 100644 --- a/docker/php/Dockerfile +++ b/docker/php/Dockerfile @@ -87,8 +87,7 @@ COPY ./behat.yml.dist /var/www/html/vendor/seec/behat-magento2-extension/behat.y COPY ./ecs.php /var/www/html/vendor/seec/behat-magento2-extension/ecs.php COPY ./phpstan.neon /var/www/html/vendor/seec/behat-magento2-extension/phpstan.neon COPY ./composer.json /var/www/html/vendor/seec/behat-magento2-extension/composer.json -COPY ./composer.lock /var/www/html/vendor/seec/behat-magento2-extension/composer.lock -COPY ./vendor /var/www/html/vendor/seec/behat-magento2-extension/vendor + RUN install-extension WORKDIR /var/www/html/vendor/seec/behat-magento2-extension diff --git a/features/bootstrap/Context/Tasks/DefaultFixtures.php b/features/bootstrap/Context/Tasks/DefaultFixtures.php index ed8d0f5..ed70cca 100644 --- a/features/bootstrap/Context/Tasks/DefaultFixtures.php +++ b/features/bootstrap/Context/Tasks/DefaultFixtures.php @@ -175,7 +175,11 @@ public function getOrCreateStock(string $name, AdapterInterface $connection): St $stock->setName($name); $repo->save($stock); } catch (DomainException|CouldNotSaveException $e) { - echo sprintf('Could not create stock regularry, retry with direct injection. Error: %s, File: %s:%s', $e->getMessage(), $e->getFile(), $e->getLine()) . \PHP_EOL; + echo sprintf('Could not create stock regularly, retry with direct injection. Error: %s, File: %s:%s', + $e->getPrevious()?->getMessage() ?? $e->getMessage(), + $e->getFile(), + $e->getLine() + ) . \PHP_EOL; $connection->insert('inventory_stock', ['stock_id' => 1, 'name' => $name]); $stock = $repo->get(1); } diff --git a/features/purging_feature.feature b/features/purging_feature.feature index 0612632..de3a4f6 100644 --- a/features/purging_feature.feature +++ b/features/purging_feature.feature @@ -1,4 +1,4 @@ -@purge @fixtureCreation +@purge @fixtureCreation @virtual Feature: Using helper services to access services outside of Magento As a developer In order to write Behat tests easily From f37739cf494b37549f059e9cba7c4757a2c8f7fc Mon Sep 17 00:00:00 2001 From: Maximilian Graf Schimmelmann Date: Thu, 3 Aug 2023 11:56:48 +0200 Subject: [PATCH 22/24] Applied ECS to Default Fixtures. --- features/bootstrap/Context/Tasks/DefaultFixtures.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/features/bootstrap/Context/Tasks/DefaultFixtures.php b/features/bootstrap/Context/Tasks/DefaultFixtures.php index ed70cca..9bef177 100644 --- a/features/bootstrap/Context/Tasks/DefaultFixtures.php +++ b/features/bootstrap/Context/Tasks/DefaultFixtures.php @@ -175,11 +175,12 @@ public function getOrCreateStock(string $name, AdapterInterface $connection): St $stock->setName($name); $repo->save($stock); } catch (DomainException|CouldNotSaveException $e) { - echo sprintf('Could not create stock regularly, retry with direct injection. Error: %s, File: %s:%s', - $e->getPrevious()?->getMessage() ?? $e->getMessage(), - $e->getFile(), - $e->getLine() - ) . \PHP_EOL; + echo sprintf( + 'Could not create stock regularly, retry with direct injection. Error: %s, File: %s:%s', + $e->getPrevious()?->getMessage() ?? $e->getMessage(), + $e->getFile(), + $e->getLine(), + ) . \PHP_EOL; $connection->insert('inventory_stock', ['stock_id' => 1, 'name' => $name]); $stock = $repo->get(1); } From 962da6b79db517c181bdc93db08edd66652aa1ee Mon Sep 17 00:00:00 2001 From: Maximilian Graf Schimmelmann Date: Thu, 3 Aug 2023 13:18:24 +0200 Subject: [PATCH 23/24] Improve CI file --- .github/workflows/ci.yml | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d19723e..7c80eb0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,11 +70,19 @@ jobs: magento key: "magento-2.4.6-with-php-${{ matrix.php-version }}" - - name: "Create Magento 2.4.6 project with testing dependencies" + - name: "Create Magento 2.4.6 project" run: | - composer create-project --repository=https://repo.magento.com/ magento/project-community-edition=2.4.6 magento + composer create-project --no-progress --no-install --repository=https://repo.magento.com/ magento/project-community-edition=2.4.6 magento if: hashFiles('magento/composer.json') == '' + - name: "Add testing dependencies" + run: | + composer config --no-plugins allow-plugins.magento/* true + composer config --no-plugins allow-plugins.php-http/discovery true + composer config --no-plugins allow-plugins.laminas/laminas-dependency-plugin true + composer config --no-plugins allow-plugins.dealerdirect/phpcodesniffer-composer-installer true + composer require --no-progress behat/behat tkotosz/test-area-magento2 + composer install --no-progress - name: "Checkout" uses: "actions/checkout@v2" with: @@ -84,6 +92,7 @@ jobs: run: | rm -f app/etc/env.php mkdir -p pub/static pub/media + composer require --dev behat/behat tkotosz/test-area-magento2 bin/magento setup:install \ --admin-email="magento@magento.com" \ --admin-firstname="admin" \ @@ -105,9 +114,9 @@ jobs: --timezone="Europe/Amsterdam" \ --use-rewrites=1 \ --use-secure-admin=0 \ - --use-secure=0 \ - -vvv - bin/magento setup:upgrade + --use-secure=0 + bin/magento --quiet deploy:mode:set developer + bin/magento --quiet setup:upgrade working-directory: 'magento' - name: "Install BehatMagento2 Extension" From 027401f0d4fc211db6c3584291eb21c96a05e706 Mon Sep 17 00:00:00 2001 From: Maximilian Graf Schimmelmann Date: Thu, 3 Aug 2023 13:46:18 +0200 Subject: [PATCH 24/24] Improve CI file --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7c80eb0..e4a2b9d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,6 +83,8 @@ jobs: composer config --no-plugins allow-plugins.dealerdirect/phpcodesniffer-composer-installer true composer require --no-progress behat/behat tkotosz/test-area-magento2 composer install --no-progress + working-directory: 'magento' + - name: "Checkout" uses: "actions/checkout@v2" with: