├── .codeclimate.yml ├── .github ├── phpunit9-integration.xml └── workflows │ └── tests.yml ├── .gitignore ├── LICENSE ├── README.md ├── auth.json ├── composer.json ├── grumphp.yml ├── phpcs.xml ├── phpstan.neon ├── registration.php ├── src ├── Catalog │ ├── CategoryBuilder.php │ ├── CategoryFixture.php │ ├── CategoryFixturePool.php │ ├── CategoryFixtureRollback.php │ ├── FulltextIndex.php │ ├── IndexFailed.php │ ├── OptionBuilder.php │ ├── OptionFixture.php │ ├── OptionFixturePool.php │ ├── OptionFixtureRollback.php │ ├── ProductBuilder.php │ ├── ProductFixture.php │ ├── ProductFixturePool.php │ └── ProductFixtureRollback.php ├── Checkout │ ├── CartBuilder.php │ └── CustomerCheckout.php ├── Core │ └── ConfigFixture.php ├── Customer │ ├── AddressBuilder.php │ ├── CustomerBuilder.php │ ├── CustomerFixture.php │ ├── CustomerFixturePool.php │ └── CustomerFixtureRollback.php ├── Sales │ ├── CreditmemoBuilder.php │ ├── CreditmemoFixture.php │ ├── CreditmemoFixturePool.php │ ├── InvoiceBuilder.php │ ├── InvoiceFixture.php │ ├── InvoiceFixturePool.php │ ├── OrderBuilder.php │ ├── OrderFixture.php │ ├── OrderFixturePool.php │ ├── OrderFixtureRollback.php │ ├── ShipmentBuilder.php │ ├── ShipmentFixture.php │ └── ShipmentFixturePool.php ├── Theme │ └── ThemeFixture.php └── etc │ └── module.xml └── tests ├── Catalog ├── CategoryBuilderTest.php ├── CategoryFixturePoolTest.php ├── IndexerErrorsTest.php ├── OptionBuilderTest.php ├── OptionFixturePoolTest.php ├── OptionFixtureRollbackTest.php ├── ProductBuilderTest.php ├── ProductFixturePoolTest.php └── ProductFixtureRollbackTest.php ├── Checkout ├── CartBuilderTest.php └── CustomerCheckoutTest.php ├── Core └── ConfigFixtureTest.php ├── Customer ├── CustomerBuilderTest.php ├── CustomerFixturePoolTest.php └── CustomerFixtureRollbackTest.php ├── Sales ├── CreditmemoBuilderTest.php ├── CreditmemoFixturePoolTest.php ├── InvoiceBuilderTest.php ├── InvoiceFixturePoolTest.php ├── OrderBuilderTest.php ├── OrderFixturePoolRollbackTest.php ├── OrderFixturePoolTest.php ├── ShipmentBuilderTest.php └── ShipmentFixturePoolTest.php ├── Theme ├── ThemeFixtureTest.php └── _files │ └── design │ └── frontend │ └── Custom │ └── default │ ├── registration.php │ └── theme.xml ├── install-config-mysql.php └── phpunit.xml.dist /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | duplication: 4 | enabled: true 5 | fixme: 6 | enabled: true 7 | phpmd: 8 | enabled: true 9 | checks: 10 | CleanCode/StaticAccess: 11 | enabled: false 12 | phan: 13 | enabled: true 14 | config: 15 | file_extensions: "php" 16 | ignore-undeclared: true 17 | ratings: 18 | paths: 19 | - "**.php" 20 | exclude_paths: 21 | - tests/ 22 | - vendor/ 23 | -------------------------------------------------------------------------------- /.github/phpunit9-integration.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | ../../../vendor/%COMPOSER_NAME%/tests 13 | 14 | 15 | 16 | 17 | 18 | ../../../lib/internal/*/*/Test 19 | ../../../lib/internal/*/*/*/Test 20 | ../../../setup/src/*/*/Test 21 | ../../../vendor/*/*/Test 22 | 23 | 24 | 25 | 26 | . 27 | testsuite 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 52 | 53 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | # disabled because MAGENTO_MARKETPLACE_USERNAME and MAGENTO_MARKETPLACE_PASSWORD repository secrets do not work: 11 | ########### 12 | # phpstan: 13 | # 14 | # name: PHPStan Static Analysis 15 | # runs-on: ubuntu-latest 16 | # 17 | # steps: 18 | # - uses: actions/checkout@v2 19 | # 20 | # - name: Setup PHP 21 | # uses: shivammathur/setup-php@v2 22 | # with: 23 | # php-version: '8.1' 24 | # 25 | # - name: Validate composer.json and composer.lock 26 | # run: composer validate --strict 27 | # 28 | # - name: Cache Composer packages 29 | # id: composer-cache 30 | # uses: actions/cache@v2 31 | # with: 32 | # path: vendor 33 | # key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} 34 | # restore-keys: | 35 | # ${{ runner.os }}-php- 36 | # 37 | # - name: Install dependencies 38 | # run: composer global config http-basic.repo.magento.com ${{ secrets.MAGENTO_MARKETPLACE_USERNAME }} ${{ secrets.MAGENTO_MARKETPLACE_PASSWORD }} && composer install --prefer-dist --no-progress 39 | # 40 | # - name: Run PHPStan 41 | # run: vendor/bin/phpstan --no-progress 42 | 43 | # cannot parameterize php version because of different ext-dn actions per version 44 | integration-tests-74: 45 | name: Magento 2 Integration Tests (php 7.4) 46 | runs-on: ubuntu-latest 47 | 48 | strategy: 49 | matrix: 50 | magento-version: [2.3, 2.4] 51 | include: 52 | - magento-version: 2.3 53 | magento-patch-version: 2.3.7 54 | - magento-version: 2.4 55 | magento-patch-version: 2.4.4 56 | 57 | services: 58 | mysql: 59 | image: mysql:5.7 60 | env: 61 | MYSQL_ROOT_PASSWORD: root 62 | ports: 63 | - 3306:3306 64 | options: --tmpfs /tmp:rw --tmpfs /var/lib/mysql:rw --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 65 | es: 66 | image: docker.io/wardenenv/elasticsearch:7.8 67 | ports: 68 | - 9200:9200 69 | env: 70 | 'discovery.type': single-node 71 | 'xpack.security.enabled': false 72 | ES_JAVA_OPTS: "-Xms64m -Xmx512m" 73 | options: --health-cmd="curl localhost:9200/_cluster/health?wait_for_status=yellow&timeout=60s" --health-interval=10s --health-timeout=5s --health-retries=3 74 | steps: 75 | - uses: actions/checkout@v2 76 | - name: M2 Integration Tests with Magento 2 77 | uses: extdn/github-actions-m2/magento-integration-tests/7.4@master 78 | with: 79 | module_name: TddWizard_Fixtures 80 | composer_name: tddwizard/magento2-fixtures 81 | composer_version: 2 82 | ce_version: ${{ matrix.magento-patch-version }} 83 | phpunit_file: .github/phpunit9-integration.xml 84 | - name: Upload Integration Test Results 85 | if: always() 86 | uses: actions/upload-artifact@v2 87 | with: 88 | name: Integration Test Results 89 | # filename is defined in phpunit9-integration.xml 90 | # since it's the same every time, only one integration test result is published 91 | path: var/test-results/integration.xml 92 | integration-tests-81: 93 | name: Magento 2 Integration Tests (PHP 8.1) 94 | runs-on: ubuntu-latest 95 | 96 | strategy: 97 | matrix: 98 | magento-version: [2.4] 99 | include: 100 | - magento-version: 2.4 101 | magento-patch-version: 2.4.4 102 | 103 | services: 104 | mysql: 105 | image: mysql:5.7 106 | env: 107 | MYSQL_ROOT_PASSWORD: root 108 | ports: 109 | - 3306:3306 110 | options: --tmpfs /tmp:rw --tmpfs /var/lib/mysql:rw --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 111 | es: 112 | image: docker.io/wardenenv/elasticsearch:7.8 113 | ports: 114 | - 9200:9200 115 | env: 116 | 'discovery.type': single-node 117 | 'xpack.security.enabled': false 118 | ES_JAVA_OPTS: "-Xms64m -Xmx512m" 119 | options: --health-cmd="curl localhost:9200/_cluster/health?wait_for_status=yellow&timeout=60s" --health-interval=10s --health-timeout=5s --health-retries=3 120 | steps: 121 | - uses: actions/checkout@v2 122 | - name: M2 Integration Tests with Magento 2 123 | uses: extdn/github-actions-m2/magento-integration-tests/8.1@master 124 | with: 125 | module_name: TddWizard_Fixtures 126 | composer_name: tddwizard/magento2-fixtures 127 | composer_version: 2 128 | ce_version: ${{ matrix.magento-patch-version }} 129 | phpunit_file: .github/phpunit9-integration.xml 130 | - name: Upload Integration Test Results 131 | if: always() 132 | uses: actions/upload-artifact@v2 133 | with: 134 | name: Integration Test Results 135 | path: var/test-results/integration.xml 136 | 137 | publish-test-results: 138 | name: "Publish Tests Results" 139 | needs: 140 | - integration-tests-74 141 | - integration-tests-81 142 | runs-on: ubuntu-latest 143 | if: always() 144 | 145 | steps: 146 | - name: Download Artifacts 147 | uses: actions/download-artifact@v2 148 | with: 149 | path: artifacts 150 | 151 | - name: Publish Test Results 152 | uses: EnricoMi/publish-unit-test-result-action@v1 153 | with: 154 | files: artifacts/**/*.xml 155 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /vendor 3 | /composer.lock 4 | /tests/phpunit.xml 5 | 6 | #docker 7 | .env 8 | docker-compose.yml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 tddwizard 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TddWizard Fixture library 2 | 3 | [![Latest Version on Packagist][ico-version]][link-packagist] 4 | [![Software License][ico-license]](LICENSE.md) 5 | [![Build Status][ico-travis]][link-travis] 6 | [![Coverage Status][ico-scrutinizer]][link-scrutinizer] 7 | [![Quality Score][ico-code-quality]][link-code-quality] 8 | [![Code Climate](https://img.shields.io/codeclimate/maintainability/tddwizard/magento2-fixtures?style=flat-square)](https://codeclimate.com/github/tddwizard/magento2-fixtures) 9 | 10 | --- 11 | 12 | Magento 2 Fixtures by Fabian Schmengler 13 | 14 | 🧙🏻‍♂ https://tddwizard.com/ 15 | 16 | ## What is it? 17 | 18 | An alternative to the procedural script based fixtures in Magento 2 integration tests. 19 | 20 | It aims to be: 21 | 22 | - extensible 23 | - expressive 24 | - easy to use 25 | 26 | ## Installation 27 | 28 | Install it into your Magento 2 project with composer: 29 | 30 | composer require --dev tddwizard/magento2-fixtures 31 | 32 | ## Requirements 33 | 34 | - Magento 2.3 or Magento 2.4 35 | - PHP 7.3 or 7.4 *(7.1 and 7.2 is allowed via composer for full Magento 2.3 compatibility but not tested anymore)* 36 | 37 | ## Usage examples: 38 | 39 | ### Customer 40 | 41 | If you need a customer without specific data, this is all: 42 | 43 | ```php 44 | protected function setUp(): void 45 | { 46 | $this->customerFixture = new CustomerFixture( 47 | CustomerBuilder::aCustomer()->build() 48 | ); 49 | } 50 | protected function tearDown(): void 51 | { 52 | $this->customerFixture->rollback(); 53 | } 54 | ``` 55 | 56 | It uses default sample data and a random email address. If you need the ID or email address in the tests, the `CustomerFixture` gives you access: 57 | 58 | ```php 59 | $this->customerFixture->getId(); 60 | $this->customerFixture->getEmail(); 61 | ``` 62 | 63 | You can configure the builder with attributes: 64 | 65 | ```php 66 | CustomerBuilder::aCustomer() 67 | ->withEmail('test@example.com') 68 | ->withCustomAttributes( 69 | [ 70 | 'my_custom_attribute' => 42 71 | ] 72 | ) 73 | ->build() 74 | ``` 75 | 76 | You can add addresses to the customer: 77 | 78 | ```php 79 | CustomerBuilder::aCustomer() 80 | ->withAddresses( 81 | AddressBuilder::anAddress()->asDefaultBilling(), 82 | AddressBuilder::anAddress()->asDefaultShipping(), 83 | AddressBuilder::anAddress() 84 | ) 85 | ->build() 86 | ``` 87 | 88 | Or just one: 89 | 90 | ```php 91 | CustomerBuilder::aCustomer() 92 | ->withAddresses( 93 | AddressBuilder::anAddress()->asDefaultBilling()->asDefaultShipping() 94 | ) 95 | ->build() 96 | ``` 97 | 98 | The `CustomerFixture` also has a shortcut to create a customer session: 99 | 100 | ```php 101 | $this->customerFixture->login(); 102 | ``` 103 | 104 | 105 | 106 | ### Addresses 107 | 108 | Similar to the customer builder you can also configure the address builder with custom attributes: 109 | 110 | ```php 111 | AddressBuilder::anAddress() 112 | ->withCountryId('DE') 113 | ->withCity('Aachen') 114 | ->withPostcode('52078') 115 | ->withCustomAttributes( 116 | [ 117 | 'my_custom_attribute' => 42 118 | ] 119 | ) 120 | ->asDefaultShipping() 121 | ``` 122 | 123 | ### Product 124 | 125 | Product fixtures work similar as customer fixtures: 126 | 127 | ```php 128 | protected function setUp(): void 129 | { 130 | $this->productFixture = new ProductFixture( 131 | ProductBuilder::aSimpleProduct() 132 | ->withPrice(10) 133 | ->withCustomAttributes( 134 | [ 135 | 'my_custom_attribute' => 42 136 | ] 137 | ) 138 | ->build() 139 | ); 140 | } 141 | protected function tearDown(): void 142 | { 143 | $this->productFixture->rollback(); 144 | } 145 | ``` 146 | 147 | The SKU is randomly generated and can be accessed through `ProductFixture`, just as the ID: 148 | 149 | ```php 150 | $this->productFixture->getSku(); 151 | $this->productFixture->getId(); 152 | ``` 153 | 154 | ### Cart/Checkout 155 | 156 | To create a quote, use the `CartBuilder` together with product fixtures: 157 | 158 | ```php 159 | $cart = CartBuilder::forCurrentSession() 160 | ->withSimpleProduct( 161 | $productFixture1->getSku() 162 | ) 163 | ->withSimpleProduct( 164 | $productFixture2->getSku(), 10 // optional qty parameter 165 | ) 166 | ->build() 167 | $quote = $cart->getQuote(); 168 | ``` 169 | 170 | Checkout is supported for logged in customers. To create an order, you can simulate the checkout as follows, given a customer fixture with default shipping and billing addresses and a product fixture: 171 | 172 | ```php 173 | $customerFixture = new CustomerFixture(CustomerBuilder::aCustomer()->withAddresses( 174 | AddressBuilder::anAddress()->asDefaultBilling(), 175 | AddressBuilder::anAddress()->asDefaultShipping() 176 | )->build()); 177 | $customerFixture->login(); 178 | 179 | $checkout = CustomerCheckout::fromCart( 180 | CartBuilder::forCurrentSession() 181 | ->withProductRequest(ProductBuilder::aVirtualProduct()->build()->getSku()) 182 | ->build() 183 | ); 184 | 185 | $order = $checkout->placeOrder(); 186 | ``` 187 | 188 | It will try to select the default addresses and the first available shipping and payment methods. 189 | 190 | You can also select them explicitly: 191 | 192 | ```php 193 | $order = $checkout 194 | ->withShippingMethodCode('freeshipping_freeshipping') 195 | ->withPaymentMethodCode('checkmo') 196 | ->withCustomerBillingAddressId($this->customerFixture->getOtherAddressId()) 197 | ->withCustomerShippingAddressId($this->customerFixture->getOtherAddressId()) 198 | ->placeOrder(); 199 | ``` 200 | 201 | ### Order 202 | 203 | The `OrderBuilder` is a shortcut for checkout simulation. 204 | 205 | ```php 206 | $order = OrderBuilder::anOrder()->build(); 207 | ``` 208 | 209 | Logged-in customer, products, and cart item quantities will be 210 | generated internally unless more control is desired: 211 | 212 | ```php 213 | $order = OrderBuilder::anOrder() 214 | ->withProducts( 215 | // prepare catalog product fixtures 216 | ProductBuilder::aSimpleProduct()->withSku('foo'), 217 | ProductBuilder::aSimpleProduct()->withSku('bar') 218 | )->withCart( 219 | // define cart item quantities 220 | CartBuilder::forCurrentSession()->withSimpleProduct('foo', 2)->withSimpleProduct('bar', 3) 221 | )->build(); 222 | ``` 223 | 224 | ### Shipment 225 | 226 | Orders can be fully or partially shipped, optionally with tracks. 227 | 228 | ```php 229 | $order = OrderBuilder::anOrder()->build(); 230 | 231 | // ship everything 232 | $shipment = ShipmentBuilder::forOrder($order)->build(); 233 | // ship only given order items, add tracks 234 | $shipment = ShipmentBuilder::forOrder($order) 235 | ->withItem($fooItemId, $fooQtyToShip) 236 | ->withItem($barItemId, $barQtyToShip) 237 | ->withTrackingNumbers('123-FOO', '456-BAR') 238 | ->build(); 239 | ``` 240 | 241 | ### Invoice 242 | 243 | Orders can be fully or partially invoiced. 244 | 245 | ```php 246 | $order = OrderBuilder::anOrder()->build(); 247 | 248 | // invoice everything 249 | $invoice = InvoiceBuilder::forOrder($order)->build(); 250 | // invoice only given order items 251 | $invoice = InvoiceBuilder::forOrder($order) 252 | ->withItem($fooItemId, $fooQtyToInvoice) 253 | ->withItem($barItemId, $barQtyToInvoice) 254 | ->build(); 255 | ``` 256 | 257 | ### Credit Memo 258 | 259 | Credit memos can be created for either all or some of the items ordered. 260 | An invoice to refund will be created internally. 261 | 262 | ```php 263 | $order = OrderBuilder::anOrder()->build(); 264 | 265 | // refund everything 266 | $creditmemo = CreditmemoBuilder::forOrder($order)->build(); 267 | // refund only given order items 268 | $creditmemo = CreditmemoBuilder::forOrder($order) 269 | ->withItem($fooItemId, $fooQtyToRefund) 270 | ->withItem($barItemId, $barQtyToRefund) 271 | ->build(); 272 | ``` 273 | 274 | ### Fixture pools 275 | 276 | To manage multiple fixtures, **fixture pools** have been introduced for all entities: 277 | 278 | Usage demonstrated with the `ProductFixturePool`: 279 | ``` 280 | protected function setUp() 281 | { 282 | $this->productFixtures = new ProductFixturePool; 283 | } 284 | 285 | protected function tearDown() 286 | { 287 | $this->productFixtures->rollback(); 288 | } 289 | 290 | public function testSomethingWithMultipleProducts() 291 | { 292 | $this->productFixtures->add(ProductBuilder::aSimpleProduct()->build()); 293 | $this->productFixtures->add(ProductBuilder::aSimpleProduct()->build(), 'foo'); 294 | $this->productFixtures->add(ProductBuilder::aSimpleProduct()->build()); 295 | 296 | $this->productFixtures->get(); // returns ProductFixture object for last added product 297 | $this->productFixtures->get('foo'); // returns ProductFixture object for product added with specific key 'foo' 298 | $this->productFixtures->get(0); // returns ProductFixture object for first product added without specific key (numeric array index) 299 | } 300 | 301 | ``` 302 | 303 | ### Config Fixtures 304 | 305 | With the config fixture you can set a configuration value globally, i.e. it will ensure that it is not only set in the default scope but also in all store scopes: 306 | ``` 307 | ConfigFixture::setGlobal('general/store_information/name', 'Ye Olde Wizard Shop'); 308 | ``` 309 | 310 | It uses `MutableScopeConfigInterface`, so the configuration is not persisted in the database. Use `@magentoAppIsolation enabled` in your test to make sure that changes are reverted in subsequent tests. 311 | 312 | You can also set configuration values explicitly for stores with `ConfigFixture::setForStore()` 313 | 314 | ## Credits 315 | 316 | - [Fabian Schmengler][link-author] 317 | - [All Contributors][link-contributors] 318 | 319 | ## License 320 | 321 | The MIT License (MIT). Please see [License File](LICENSE.txt) for more information. 322 | 323 | [ico-version]: https://img.shields.io/packagist/v/tddwizard/magento2-fixtures.svg?style=flat-square 324 | [ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square 325 | [ico-travis]: https://img.shields.io/travis/tddwizard/magento2-fixtures/master.svg?style=flat-square 326 | [ico-scrutinizer]: https://img.shields.io/scrutinizer/coverage/g/tddwizard/magento2-fixtures?style=flat-square 327 | [ico-code-quality]: https://img.shields.io/scrutinizer/g/tddwizard/magento2-fixtures.svg?style=flat-square 328 | 329 | [link-packagist]: https://packagist.org/packages/tddwizard/magento2-fixtures 330 | [link-travis]: https://travis-ci.org/tddwizard/magento2-fixtures 331 | [link-scrutinizer]: https://scrutinizer-ci.com/g/tddwizard/magento2-fixtures/code-structure 332 | [link-code-quality]: https://scrutinizer-ci.com/g/tddwizard/magento2-fixtures 333 | [link-author]: https://github.com/schmengler 334 | [link-contributors]: ../../contributors 335 | 336 | -------------------------------------------------------------------------------- /auth.json: -------------------------------------------------------------------------------- 1 | { 2 | "http-basic": { 3 | "repo.magento.com": { 4 | "username": "438956769d577f67f247510ffa624225", 5 | "password": "b5d1664bb6ee2d27ec75dd36d6a2ac4e" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tddwizard/magento2-fixtures", 3 | "description": "Fixture library for Magento 2 integration tests", 4 | "type": "magento2-module", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Fabian Schmengler", 9 | "email": "fs@integer-net.de" 10 | } 11 | ], 12 | "minimum-stability": "stable", 13 | "autoload": { 14 | "files": [ 15 | "registration.php" 16 | ], 17 | "psr-4": { 18 | "TddWizard\\Fixtures\\": "src" 19 | } 20 | }, 21 | "autoload-dev": { 22 | "psr-4": { 23 | "TddWizard\\Fixtures\\": "tests" 24 | } 25 | }, 26 | "repositories": [ 27 | { 28 | "type": "composer", 29 | "url": "https://repo.magento.com/" 30 | } 31 | ], 32 | "require": { 33 | "php": "^7.1|^8.0", 34 | "magento/framework": "^102.0|^103.0", 35 | "magento/zendframework1": "^1.14", 36 | "magento/module-catalog": "^103.0|^104.0", 37 | "magento/module-catalog-inventory": "^100.3", 38 | "magento/module-checkout": "^100.3", 39 | "magento/module-customer": "^102.0|^103.0", 40 | "magento/module-directory": "^100.3", 41 | "magento/module-indexer": "^100.3", 42 | "magento/module-payment": "^100.3", 43 | "magento/module-quote": "^101.1", 44 | "magento/module-sales": "^102.0|^103.0", 45 | "fakerphp/faker": "^1.9.1" 46 | }, 47 | "require-dev": { 48 | "ext-json": "*", 49 | "magento/module-store": "^101.0", 50 | "phpunit/phpunit": "^6.0|^9.0", 51 | "pds/skeleton": "^1.0", 52 | "phpro/grumphp": "^0.19.0", 53 | "phpstan/phpstan": "^0.12.0", 54 | "squizlabs/php_codesniffer": "^3.3.1" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /grumphp.yml: -------------------------------------------------------------------------------- 1 | grumphp: 2 | tasks: 3 | composer: [] 4 | phpcs: 5 | standard: 'PSR2' 6 | encoding: utf-8 7 | phpstan: 8 | configuration: phpstan.neon 9 | phpparser: 10 | visitors: 11 | no_exit_statements: ~ 12 | forbidden_function_calls: 13 | blacklist: 14 | - 'var_dump' 15 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 6 3 | paths: 4 | - src 5 | - tests 6 | ignoreErrors: 7 | - '#(class|type) Magento\\TestFramework#i' 8 | - '#(class|type) Magento\\\S*Factory#i' 9 | - '#(method) Magento\\Framework\\Api\\ExtensionAttributesInterface#i' 10 | -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 | categoryRepository = $categoryRepository; 63 | $this->categoryResource = $categoryResource; 64 | $this->categoryLinkRepository = $categoryLinkRepository; 65 | $this->productLinkFactory = $productLinkFactory; 66 | $this->category = $category; 67 | $this->skus = $skus; 68 | } 69 | 70 | public static function topLevelCategory(): CategoryBuilder 71 | { 72 | $objectManager = Bootstrap::getObjectManager(); 73 | 74 | // use interface to reflect DI configuration but assume instance of the real model because we need its methods 75 | /** @var Category $category */ 76 | $category = $objectManager->create(CategoryInterface::class); 77 | 78 | $category->setName('Top Level Category'); 79 | $category->setIsActive(true); 80 | $category->setPath('1/2'); 81 | 82 | return new self( 83 | $objectManager->create(CategoryRepositoryInterface::class), 84 | $objectManager->create(CategoryResource::class), 85 | $objectManager->create(CategoryLinkRepositoryInterface::class), 86 | $objectManager->create(CategoryProductLinkInterfaceFactory::class), 87 | $category, 88 | [] 89 | ); 90 | } 91 | 92 | public static function childCategoryOf( 93 | CategoryFixture $parent 94 | ): CategoryBuilder { 95 | $objectManager = Bootstrap::getObjectManager(); 96 | // use interface to reflect DI configuration but assume instance of the real model because we need its methods 97 | /** @var Category $category */ 98 | $category = $objectManager->create(CategoryInterface::class); 99 | 100 | $category->setName('Child Category'); 101 | $category->setIsActive(true); 102 | $category->setPath((string)$parent->getCategory()->getPath()); 103 | 104 | return new self( 105 | $objectManager->create(CategoryRepositoryInterface::class), 106 | $objectManager->create(CategoryResource::class), 107 | $objectManager->create(CategoryLinkRepositoryInterface::class), 108 | $objectManager->create(CategoryProductLinkInterfaceFactory::class), 109 | $category, 110 | [] 111 | ); 112 | } 113 | 114 | /** 115 | * Assigns products by sku. The keys of the array will be used for the sort position 116 | * 117 | * @param string[] $skus 118 | * @return CategoryBuilder 119 | */ 120 | public function withProducts(array $skus): CategoryBuilder 121 | { 122 | $builder = clone $this; 123 | $builder->skus = $skus; 124 | return $builder; 125 | } 126 | 127 | public function withDescription(string $description): CategoryBuilder 128 | { 129 | $builder = clone $this; 130 | $builder->category->setCustomAttribute('description', $description); 131 | return $builder; 132 | } 133 | 134 | public function withName(string $name): CategoryBuilder 135 | { 136 | $builder = clone $this; 137 | $builder->category->setName($name); 138 | return $builder; 139 | } 140 | 141 | public function withUrlKey(string $urlKey): CategoryBuilder 142 | { 143 | $builder = clone $this; 144 | $builder->category->setData('url_key', $urlKey); 145 | return $builder; 146 | } 147 | 148 | public function withIsActive(bool $isActive): CategoryBuilder 149 | { 150 | $builder = clone $this; 151 | $builder->category->setIsActive($isActive); 152 | return $builder; 153 | } 154 | 155 | public function __clone() 156 | { 157 | $this->category = clone $this->category; 158 | } 159 | 160 | /** 161 | * @return Category 162 | * @throws \Exception 163 | */ 164 | public function build(): Category 165 | { 166 | $builder = clone $this; 167 | 168 | if (!$builder->category->getData('url_key')) { 169 | $builder->category->setData('url_key', sha1(uniqid('', true))); 170 | } 171 | 172 | // Save with global scope if not specified otherwise 173 | if (!$builder->category->hasData('store_id')) { 174 | $builder->category->setStoreId(0); 175 | } 176 | $builder->categoryResource->save($builder->category); 177 | 178 | foreach ($builder->skus as $position => $sku) { 179 | $productLink = $builder->productLinkFactory->create(); 180 | $productLink->setSku($sku); 181 | $productLink->setPosition($position); 182 | $productLink->setCategoryId($builder->category->getId()); 183 | $builder->categoryLinkRepository->save($productLink); 184 | } 185 | return $builder->category; 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/Catalog/CategoryFixture.php: -------------------------------------------------------------------------------- 1 | category; 22 | } 23 | 24 | public function __construct(CategoryInterface $category) 25 | { 26 | $this->category = $category; 27 | } 28 | 29 | public function getId(): int 30 | { 31 | return (int) $this->category->getId(); 32 | } 33 | 34 | public function getUrlKey(): string 35 | { 36 | /** @var Category $category */ 37 | $category = $this->category; 38 | return (string)$category->getUrlKey(); 39 | } 40 | 41 | public function rollback(): void 42 | { 43 | CategoryFixtureRollback::create()->execute($this); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Catalog/CategoryFixturePool.php: -------------------------------------------------------------------------------- 1 | categoryFixtures[] = new CategoryFixture($category); 21 | } else { 22 | $this->categoryFixtures[$key] = new CategoryFixture($category); 23 | } 24 | } 25 | 26 | /** 27 | * Returns category fixture by key, or last added if key not specified 28 | * 29 | * @param int|string|null $key 30 | * @return CategoryFixture 31 | */ 32 | public function get($key = null): CategoryFixture 33 | { 34 | if ($key === null) { 35 | $key = \array_key_last($this->categoryFixtures); 36 | } 37 | if ($key === null || !array_key_exists($key, $this->categoryFixtures)) { 38 | throw new \OutOfBoundsException('No matching category found in fixture pool'); 39 | } 40 | return $this->categoryFixtures[$key]; 41 | } 42 | 43 | public function rollback(): void 44 | { 45 | CategoryFixtureRollback::create()->execute(...values($this->categoryFixtures)); 46 | $this->categoryFixtures = []; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Catalog/CategoryFixtureRollback.php: -------------------------------------------------------------------------------- 1 | registry = $registry; 29 | $this->categoryRepository = $categoryRepository; 30 | } 31 | 32 | public static function create(): CategoryFixtureRollback 33 | { 34 | $objectManager = Bootstrap::getObjectManager(); 35 | return new self( 36 | $objectManager->get(Registry::class), 37 | $objectManager->get(CategoryRepositoryInterface::class) 38 | ); 39 | } 40 | 41 | /** 42 | * @param CategoryFixture ...$categoryFixtures 43 | * @throws LocalizedException 44 | */ 45 | public function execute(CategoryFixture ...$categoryFixtures): void 46 | { 47 | $this->registry->unregister('isSecureArea'); 48 | $this->registry->register('isSecureArea', true); 49 | 50 | foreach ($categoryFixtures as $categoryFixture) { 51 | $this->categoryRepository->deleteByIdentifier($categoryFixture->getId()); 52 | } 53 | 54 | $this->registry->unregister('isSecureArea'); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Catalog/FulltextIndex.php: -------------------------------------------------------------------------------- 1 | indexerFactory = $indexerFactory; 27 | } 28 | 29 | /** 30 | * @throws \Throwable 31 | */ 32 | public static function ensureTablesAreCreated(): void 33 | { 34 | if (!self::$created) { 35 | (new self(Bootstrap::getObjectManager()->create(IndexerFactory::class)))->reindex(); 36 | } 37 | } 38 | 39 | /** 40 | * @throws \Throwable 41 | */ 42 | public function reindex(): void 43 | { 44 | $indexer = $this->indexerFactory->create(); 45 | $indexer->load('catalogsearch_fulltext'); 46 | $indexer->reindexAll(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Catalog/IndexFailed.php: -------------------------------------------------------------------------------- 1 | optionManagement = $optionManagement; 56 | $this->option = $option; 57 | $this->optionLabel = $optionLabel; 58 | $this->attributeCode = $attributeCode; 59 | } 60 | 61 | /** 62 | * Clone the builder. 63 | */ 64 | public function __clone() 65 | { 66 | $this->option = clone $this->option; 67 | } 68 | 69 | /** 70 | * Create an option. 71 | * 72 | * @param string $attributeCode 73 | * @return OptionBuilder 74 | * @throws \Magento\Framework\Exception\InputException 75 | * @throws \Magento\Framework\Exception\StateException 76 | */ 77 | public static function anOptionFor(string $attributeCode): OptionBuilder 78 | { 79 | $objectManager = Bootstrap::getObjectManager(); 80 | /** @var AttributeOptionManagementInterface $optionManagement */ 81 | $optionManagement = $objectManager->create(AttributeOptionManagementInterface::class); 82 | $items = $optionManagement->getItems(Product::ENTITY, $attributeCode); 83 | 84 | /** @var AttributeOptionLabelInterface $optionLabel */ 85 | $optionLabel = $objectManager->create(AttributeOptionLabelInterface::class); 86 | $label = uniqid('Name ', true); 87 | $optionLabel->setStoreId(0); 88 | $optionLabel->setLabel($label); 89 | 90 | /** @var AttributeOption $option */ 91 | $option = $objectManager->create(AttributeOption::class); 92 | $option->setLabel($label); 93 | $option->setStoreLabels([$optionLabel]); 94 | $option->setSortOrder(count($items) + 1); 95 | $option->setIsDefault(false); 96 | 97 | return new static( 98 | $optionManagement, 99 | $option, 100 | $optionLabel, 101 | $attributeCode 102 | ); 103 | } 104 | 105 | /** 106 | * Set label. 107 | * 108 | * @param string $label 109 | * @return OptionBuilder 110 | */ 111 | public function withLabel(string $label): OptionBuilder 112 | { 113 | $builder = clone $this; 114 | $builder->optionLabel->setLabel($label); 115 | $builder->option->setStoreLabels([$builder->optionLabel]); 116 | $builder->option->setLabel($label); 117 | 118 | return $builder; 119 | } 120 | 121 | /** 122 | * Set sort order. 123 | * 124 | * @param int $sortOrder 125 | * @return OptionBuilder 126 | */ 127 | public function withSortOrder(int $sortOrder): OptionBuilder 128 | { 129 | $builder = clone $this; 130 | $builder->option->setSortOrder($sortOrder); 131 | 132 | return $builder; 133 | } 134 | 135 | /** 136 | * Set default. 137 | * 138 | * @param bool $isDefault 139 | * @return OptionBuilder 140 | */ 141 | public function withIsDefault(bool $isDefault): OptionBuilder 142 | { 143 | $builder = clone $this; 144 | $builder->option->setIsDefault($isDefault); 145 | 146 | return $builder; 147 | } 148 | 149 | /** 150 | * Set store ID. 151 | * 152 | * @param int $storeId 153 | * @return OptionBuilder 154 | */ 155 | public function withStoreId(int $storeId): OptionBuilder 156 | { 157 | $builder = clone $this; 158 | $builder->optionLabel->setStoreId($storeId); 159 | 160 | return $builder; 161 | } 162 | 163 | /** 164 | * Build the option and apply it to the attribute. 165 | * 166 | * @return AttributeOption 167 | * @throws \Magento\Framework\Exception\InputException 168 | * @throws \Magento\Framework\Exception\StateException 169 | */ 170 | public function build(): AttributeOption 171 | { 172 | $builder = clone $this; 173 | 174 | // add the option 175 | $this->optionManagement->add( 176 | \Magento\Catalog\Model\Product::ENTITY, 177 | $builder->attributeCode, 178 | $builder->option 179 | ); 180 | 181 | $optionId = $this->getOptionId(); 182 | $builder->option->setId($optionId); 183 | 184 | return $builder->option; 185 | } 186 | 187 | /** 188 | * Get the option ID. 189 | * 190 | * @return int 191 | */ 192 | private function getOptionId(): int 193 | { 194 | $objectManager = Bootstrap::getObjectManager(); 195 | // the add option above does not return the option, so we need to retrieve it 196 | $attributeRepository = $objectManager->get(ProductAttributeRepositoryInterface::class); 197 | $attribute = $attributeRepository->get($this->attributeCode); 198 | $attributeValues[$attribute->getAttributeId()] = []; 199 | 200 | // We have to generate a new sourceModel instance each time through to prevent it from 201 | // referencing its _options cache. No other way to get it to pick up newly-added values. 202 | $tableFactory = $objectManager->get(\Magento\Eav\Model\Entity\Attribute\Source\TableFactory::class); 203 | $sourceModel = $tableFactory->create(); 204 | $sourceModel->setAttribute($attribute); 205 | foreach ($sourceModel->getAllOptions() as $option) { 206 | $attributeValues[$attribute->getAttributeId()][$option['label']] = $option['value']; 207 | } 208 | if (isset($attributeValues[$attribute->getAttributeId()][$this->optionLabel->getLabel()])) { 209 | return (int)$attributeValues[$attribute->getAttributeId()][$this->optionLabel->getLabel()]; 210 | } 211 | 212 | throw new \RuntimeException('Error building option'); 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/Catalog/OptionFixture.php: -------------------------------------------------------------------------------- 1 | attributeCode = $attributeCode; 34 | $this->option = $option; 35 | } 36 | 37 | /** 38 | * Get the attribute code. 39 | * 40 | * @return string 41 | */ 42 | public function getAttributeCode(): string 43 | { 44 | return $this->attributeCode; 45 | } 46 | 47 | /** 48 | * Get the option. 49 | * 50 | * @return AttributeOption 51 | */ 52 | public function getOption(): AttributeOption 53 | { 54 | return $this->option; 55 | } 56 | 57 | /** 58 | * Rollback the option(s). 59 | * 60 | * @throws LocalizedException 61 | */ 62 | public function rollback(): void 63 | { 64 | OptionFixtureRollback::create()->execute($this); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Catalog/OptionFixturePool.php: -------------------------------------------------------------------------------- 1 | optionFixtures[] = new OptionFixture($option, $attributecode); 24 | } else { 25 | $this->optionFixtures[$key] = new OptionFixture($option, $attributecode); 26 | } 27 | } 28 | 29 | /** 30 | * Returns option fixture by key, or last added if key not specified 31 | * 32 | * @param string|null $key 33 | * @return OptionFixture 34 | */ 35 | public function get(string $key = null): OptionFixture 36 | { 37 | if ($key === null) { 38 | $key = \array_key_last($this->optionFixtures); 39 | } 40 | if ($key === null || !\array_key_exists($key, $this->optionFixtures)) { 41 | throw new \OutOfBoundsException('No matching option found in fixture pool'); 42 | } 43 | return $this->optionFixtures[$key]; 44 | } 45 | 46 | public function rollback(): void 47 | { 48 | OptionFixtureRollback::create()->execute(...values($this->optionFixtures)); 49 | $this->optionFixtures = []; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Catalog/OptionFixtureRollback.php: -------------------------------------------------------------------------------- 1 | registry = $registry; 41 | $this->optionManagement = $optionManagement; 42 | } 43 | 44 | /** 45 | * Create the object. 46 | * 47 | * @return OptionFixtureRollback 48 | */ 49 | public static function create(): OptionFixtureRollback 50 | { 51 | $objectManager = Bootstrap::getObjectManager(); 52 | return new self( 53 | $objectManager->get(Registry::class), 54 | $objectManager->get(AttributeOptionManagementInterface::class) 55 | ); 56 | } 57 | 58 | /** 59 | * Remove the given option(s). 60 | * 61 | * @param OptionFixture ...$optionFixtures 62 | * @throws InputException 63 | * @throws NoSuchEntityException 64 | * @throws StateException 65 | */ 66 | public function execute(OptionFixture ...$optionFixtures): void 67 | { 68 | $this->registry->unregister('isSecureArea'); 69 | $this->registry->register('isSecureArea', true); 70 | 71 | foreach ($optionFixtures as $optionFixture) { 72 | $this->optionManagement->delete( 73 | Product::ENTITY, 74 | $optionFixture->getAttributeCode(), 75 | $optionFixture->getOption()->getId() 76 | ); 77 | } 78 | 79 | $this->registry->unregister('isSecureArea'); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Catalog/ProductFixture.php: -------------------------------------------------------------------------------- 1 | product = $product; 18 | } 19 | 20 | public function getProduct(): ProductInterface 21 | { 22 | return $this->product; 23 | } 24 | 25 | public function getId(): int 26 | { 27 | return (int) $this->product->getId(); 28 | } 29 | 30 | public function getSku(): string 31 | { 32 | return $this->product->getSku(); 33 | } 34 | 35 | public function rollback(): void 36 | { 37 | ProductFixtureRollback::create()->execute($this); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Catalog/ProductFixturePool.php: -------------------------------------------------------------------------------- 1 | productFixtures[] = new ProductFixture($product); 21 | } else { 22 | $this->productFixtures[$key] = new ProductFixture($product); 23 | } 24 | } 25 | 26 | /** 27 | * Returns product fixture by key, or last added if key not specified 28 | * 29 | * @param int|string|null $key 30 | * @return ProductFixture 31 | */ 32 | public function get($key = null): ProductFixture 33 | { 34 | if ($key === null) { 35 | $key = \array_key_last($this->productFixtures); 36 | } 37 | if ($key === null || !array_key_exists($key, $this->productFixtures)) { 38 | throw new \OutOfBoundsException('No matching product found in fixture pool'); 39 | } 40 | return $this->productFixtures[$key]; 41 | } 42 | 43 | public function rollback(): void 44 | { 45 | ProductFixtureRollback::create()->execute(...values($this->productFixtures)); 46 | $this->productFixtures = []; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Catalog/ProductFixtureRollback.php: -------------------------------------------------------------------------------- 1 | registry = $registry; 29 | $this->productRepository = $productRepository; 30 | } 31 | 32 | public static function create(): ProductFixtureRollback 33 | { 34 | $objectManager = Bootstrap::getObjectManager(); 35 | return new self( 36 | $objectManager->get(Registry::class), 37 | $objectManager->get(ProductRepositoryInterface::class) 38 | ); 39 | } 40 | 41 | /** 42 | * @param ProductFixture ...$productFixtures 43 | * @throws LocalizedException 44 | */ 45 | public function execute(ProductFixture ...$productFixtures): void 46 | { 47 | $this->registry->unregister('isSecureArea'); 48 | $this->registry->register('isSecureArea', true); 49 | 50 | foreach ($productFixtures as $productFixture) { 51 | $this->productRepository->deleteById($productFixture->getSku()); 52 | } 53 | 54 | $this->registry->unregister('isSecureArea'); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Checkout/CartBuilder.php: -------------------------------------------------------------------------------- 1 | [buyRequest]] (multiple requests per sku are possible) 27 | */ 28 | private $addToCartRequests; 29 | 30 | final public function __construct(ProductRepositoryInterface $productRepository, Cart $cart) 31 | { 32 | $this->productRepository = $productRepository; 33 | $this->cart = $cart; 34 | $this->addToCartRequests = []; 35 | } 36 | 37 | public static function forCurrentSession(): CartBuilder 38 | { 39 | $objectManager = Bootstrap::getObjectManager(); 40 | return new static( 41 | $objectManager->create(ProductRepositoryInterface::class), 42 | $objectManager->create(Cart::class) 43 | ); 44 | } 45 | 46 | public function withSimpleProduct(string $sku, float $qty = 1): CartBuilder 47 | { 48 | $result = clone $this; 49 | $result->addToCartRequests[$sku][] = new DataObject(['qty' => $qty]); 50 | return $result; 51 | } 52 | 53 | public function withReservedOrderId(string $orderId): CartBuilder 54 | { 55 | $result = clone $this; 56 | $result->cart->getQuote()->setReservedOrderId($orderId); 57 | return $result; 58 | } 59 | 60 | /** 61 | * Lower-level API to support arbitrary products 62 | * 63 | * @param string $sku 64 | * @param int $qty 65 | * @param mixed[] $request 66 | * @return CartBuilder 67 | */ 68 | public function withProductRequest($sku, $qty = 1, $request = []): CartBuilder 69 | { 70 | $result = clone $this; 71 | $requestInfo = array_merge(['qty' => $qty], $request); 72 | $result->addToCartRequests[$sku][] = new DataObject($requestInfo); 73 | return $result; 74 | } 75 | 76 | /** 77 | * @return Cart 78 | * @throws LocalizedException 79 | */ 80 | public function build(): Cart 81 | { 82 | foreach ($this->addToCartRequests as $sku => $requests) { 83 | /** @var Product $product */ 84 | $product = $this->productRepository->get($sku); 85 | foreach ($requests as $requestInfo) { 86 | $this->cart->addProduct($product, $requestInfo); 87 | } 88 | } 89 | $this->cart->save(); 90 | return $this->cart; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Checkout/CustomerCheckout.php: -------------------------------------------------------------------------------- 1 | addressRepository = $addressRepository; 75 | $this->quoteRepository = $quoteRepository; 76 | $this->quoteManagement = $quoteManagement; 77 | $this->paymentConfig = $paymentConfig; 78 | $this->cart = $cart; 79 | $this->shippingAddressId = $shippingAddressId; 80 | $this->billingAddressId = $billingAddressId; 81 | $this->shippingMethodCode = $shippingMethodCode; 82 | $this->paymentMethodCode = $paymentMethodCode; 83 | } 84 | 85 | public static function fromCart(Cart $cart): CustomerCheckout 86 | { 87 | $objectManager = Bootstrap::getObjectManager(); 88 | return new static( 89 | $objectManager->create(AddressRepositoryInterface::class), 90 | $objectManager->create(CartRepositoryInterface::class), 91 | $objectManager->create(QuoteManagement::class), 92 | $objectManager->create(PaymentConfig::class), 93 | $cart 94 | ); 95 | } 96 | 97 | public function withCustomerBillingAddressId(int $addressId): CustomerCheckout 98 | { 99 | $checkout = clone $this; 100 | $checkout->billingAddressId = $addressId; 101 | return $checkout; 102 | } 103 | 104 | public function withCustomerShippingAddressId(int $addressId): CustomerCheckout 105 | { 106 | $checkout = clone $this; 107 | $checkout->shippingAddressId = $addressId; 108 | return $checkout; 109 | } 110 | 111 | public function withShippingMethodCode(string $code): CustomerCheckout 112 | { 113 | $checkout = clone $this; 114 | $checkout->shippingMethodCode = $code; 115 | return $checkout; 116 | } 117 | 118 | public function withPaymentMethodCode(string $code): CustomerCheckout 119 | { 120 | $checkout = clone $this; 121 | $checkout->paymentMethodCode = $code; 122 | return $checkout; 123 | } 124 | 125 | /** 126 | * @return int Customer shipping address as configured or try default shipping address 127 | */ 128 | private function getCustomerShippingAddressId(): int 129 | { 130 | return $this->shippingAddressId 131 | ?? (int) $this->cart->getCustomerSession()->getCustomer()->getDefaultShippingAddress()->getId(); 132 | } 133 | 134 | /** 135 | * @return int Customer billing address as configured or try default billing address 136 | */ 137 | private function getCustomerBillingAddressId(): int 138 | { 139 | return $this->billingAddressId 140 | ?? (int) $this->cart->getCustomerSession()->getCustomer()->getDefaultBillingAddress()->getId(); 141 | } 142 | 143 | /** 144 | * @return string Shipping method code as configured, or try first available rate 145 | */ 146 | private function getShippingMethodCode(): string 147 | { 148 | return $this->shippingMethodCode 149 | ?? $this->cart->getQuote()->getShippingAddress()->getAllShippingRates()[0]->getCode(); 150 | } 151 | 152 | /** 153 | * @return string Payment method code as configured, or try first available method 154 | */ 155 | private function getPaymentMethodCode(): string 156 | { 157 | return $this->paymentMethodCode ?? array_values($this->paymentConfig->getActiveMethods())[0]->getCode(); 158 | } 159 | 160 | /** 161 | * @return Order 162 | * @throws \Exception 163 | */ 164 | public function placeOrder(): Order 165 | { 166 | $this->saveBilling(); 167 | $this->saveShipping(); 168 | $this->savePayment(); 169 | /** @var Quote $reloadedQuote */ 170 | $reloadedQuote = $this->quoteRepository->get($this->cart->getQuote()->getId()); 171 | // Collect missing totals, like shipping 172 | $reloadedQuote->collectTotals(); 173 | $order = $this->quoteManagement->submit($reloadedQuote); 174 | if (! $order instanceof Order) { 175 | $returnType = is_object($order) ? get_class($order) : gettype($order); 176 | throw new \RuntimeException('QuoteManagement::submit() returned ' . $returnType . ' instead of Order'); 177 | } 178 | $this->cart->getCheckoutSession()->clearQuote(); 179 | return $order; 180 | } 181 | 182 | /** 183 | * @throws \Exception 184 | */ 185 | private function saveBilling(): void 186 | { 187 | $billingAddress = $this->cart->getQuote()->getBillingAddress(); 188 | $billingAddress->importCustomerAddressData( 189 | $this->addressRepository->getById($this->getCustomerBillingAddressId()) 190 | ); 191 | $billingAddress->save(); 192 | } 193 | 194 | /** 195 | * @throws \Exception 196 | */ 197 | private function saveShipping(): void 198 | { 199 | $shippingAddress = $this->cart->getQuote()->getShippingAddress(); 200 | $shippingAddress->importCustomerAddressData( 201 | $this->addressRepository->getById($this->getCustomerShippingAddressId()) 202 | ); 203 | $shippingAddress->setCollectShippingRates(true); 204 | $shippingAddress->collectShippingRates(); 205 | $shippingAddress->setShippingMethod($this->getShippingMethodCode()); 206 | $shippingAddress->save(); 207 | } 208 | 209 | /** 210 | * @throws \Exception 211 | */ 212 | private function savePayment(): void 213 | { 214 | $payment = $this->cart->getQuote()->getPayment(); 215 | $payment->setMethod($this->getPaymentMethodCode()); 216 | $payment->save(); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/Core/ConfigFixture.php: -------------------------------------------------------------------------------- 1 | setValue($path, $value, ScopeConfigInterface::SCOPE_TYPE_DEFAULT); 24 | foreach (self::storeRepository()->getList() as $store) { 25 | self::scopeConfig()->setValue($path, $value, ScopeInterface::SCOPE_STORE, $store->getCode()); 26 | } 27 | } 28 | 29 | /** 30 | * Sets configuration in store scope 31 | * 32 | * @param string $path 33 | * @param mixed $value 34 | * @param null $storeCode store code or NULL for current store 35 | */ 36 | public static function setForStore(string $path, $value, $storeCode = null) : void 37 | { 38 | self::scopeConfig()->setValue($path, $value, ScopeInterface::SCOPE_STORE, $storeCode); 39 | } 40 | 41 | private static function scopeConfig(): MutableScopeConfigInterface 42 | { 43 | return Bootstrap::getObjectManager()->get(MutableScopeConfigInterface::class); 44 | } 45 | 46 | private static function storeRepository(): StoreRepositoryInterface 47 | { 48 | return Bootstrap::getObjectManager()->get(StoreRepositoryInterface::class); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Customer/AddressBuilder.php: -------------------------------------------------------------------------------- 1 | address = $address; 33 | $this->addressRepository = $addressRepository; 34 | } 35 | 36 | public function __clone() 37 | { 38 | $this->address = clone $this->address; 39 | } 40 | 41 | public static function anAddress( 42 | string $locale = 'de_DE' 43 | ): AddressBuilder { 44 | $objectManager = Bootstrap::getObjectManager(); 45 | 46 | $address = self::prepareFakeAddress($objectManager, $locale); 47 | return new self($objectManager->create(AddressRepositoryInterface::class), $address); 48 | } 49 | 50 | public static function aCompanyAddress( 51 | string $locale = 'de_DE', 52 | string $vatId = '1234567890' 53 | ): AddressBuilder { 54 | $objectManager = Bootstrap::getObjectManager(); 55 | 56 | $address = self::prepareFakeAddress($objectManager, $locale); 57 | $address->setVatId($vatId); 58 | return new self($objectManager->create(AddressRepositoryInterface::class), $address); 59 | } 60 | 61 | public function asDefaultShipping(): AddressBuilder 62 | { 63 | $builder = clone $this; 64 | $builder->address->setIsDefaultShipping(true); 65 | return $builder; 66 | } 67 | 68 | public function asDefaultBilling(): AddressBuilder 69 | { 70 | $builder = clone $this; 71 | $builder->address->setIsDefaultBilling(true); 72 | return $builder; 73 | } 74 | 75 | public function withPrefix(string $prefix): AddressBuilder 76 | { 77 | $builder = clone $this; 78 | $builder->address->setPrefix($prefix); 79 | return $builder; 80 | } 81 | 82 | public function withFirstname(string $firstname): AddressBuilder 83 | { 84 | $builder = clone $this; 85 | $builder->address->setFirstname($firstname); 86 | return $builder; 87 | } 88 | 89 | public function withMiddlename(string $middlename): AddressBuilder 90 | { 91 | $builder = clone $this; 92 | $builder->address->setMiddlename($middlename); 93 | return $builder; 94 | } 95 | 96 | public function withLastname(string $lastname): AddressBuilder 97 | { 98 | $builder = clone $this; 99 | $builder->address->setLastname($lastname); 100 | return $builder; 101 | } 102 | 103 | public function withSuffix(string $suffix): AddressBuilder 104 | { 105 | $builder = clone $this; 106 | $builder->address->setSuffix($suffix); 107 | return $builder; 108 | } 109 | 110 | public function withStreet(string $street): AddressBuilder 111 | { 112 | $builder = clone $this; 113 | $builder->address->setStreet((array)$street); 114 | return $builder; 115 | } 116 | 117 | public function withCompany(string $company): AddressBuilder 118 | { 119 | $builder = clone $this; 120 | $builder->address->setCompany($company); 121 | return $builder; 122 | } 123 | 124 | public function withTelephone(string $telephone): AddressBuilder 125 | { 126 | $builder = clone $this; 127 | $builder->address->setTelephone($telephone); 128 | return $builder; 129 | } 130 | 131 | public function withPostcode(string $postcode): AddressBuilder 132 | { 133 | $builder = clone $this; 134 | $builder->address->setPostcode($postcode); 135 | return $builder; 136 | } 137 | 138 | public function withCity(string $city): AddressBuilder 139 | { 140 | $builder = clone $this; 141 | $builder->address->setCity($city); 142 | return $builder; 143 | } 144 | 145 | public function withCountryId(string $countryId): AddressBuilder 146 | { 147 | $builder = clone $this; 148 | $builder->address->setCountryId($countryId); 149 | return $builder; 150 | } 151 | 152 | public function withRegionId(int $regionId): AddressBuilder 153 | { 154 | $builder = clone $this; 155 | $builder->address->setRegionId($regionId); 156 | return $builder; 157 | } 158 | 159 | /** 160 | * @param mixed[] $values 161 | * @return AddressBuilder 162 | */ 163 | public function withCustomAttributes(array $values): AddressBuilder 164 | { 165 | $builder = clone $this; 166 | foreach ($values as $code => $value) { 167 | $builder->address->setCustomAttribute($code, $value); 168 | } 169 | return $builder; 170 | } 171 | 172 | /** 173 | * @return AddressInterface 174 | * @throws LocalizedException 175 | */ 176 | public function build(): AddressInterface 177 | { 178 | return $this->addressRepository->save($this->address); 179 | } 180 | 181 | public function buildWithoutSave(): AddressInterface 182 | { 183 | return clone $this->address; 184 | } 185 | 186 | private static function prepareFakeAddress( 187 | ObjectManagerInterface $objectManager, 188 | string $locale = 'de_DE' 189 | ): AddressInterface { 190 | $faker = FakerFactory::create($locale); 191 | $countryCode = substr($locale, -2); 192 | 193 | try { 194 | $region = $faker->province; 195 | } catch (InvalidArgumentException $exception) { 196 | $region = $faker->state; 197 | } 198 | 199 | $regionId = $objectManager->create(Region::class)->loadByName($region, $countryCode)->getId(); 200 | 201 | /** @var AddressInterface $address */ 202 | $address = $objectManager->create(AddressInterface::class); 203 | $address 204 | ->setTelephone($faker->phoneNumber) 205 | ->setPostcode($faker->postcode) 206 | ->setCountryId($countryCode) 207 | ->setCity($faker->city) 208 | ->setCompany($faker->company) 209 | ->setStreet([$faker->streetAddress]) 210 | ->setLastname($faker->lastName) 211 | ->setFirstname($faker->firstName) 212 | ->setRegionId($regionId); 213 | 214 | return $address; 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/Customer/CustomerBuilder.php: -------------------------------------------------------------------------------- 1 | customerRepository = $customerRepository; 50 | $this->customer = $customer; 51 | $this->encryptor = $encryptor; 52 | $this->password = $password; 53 | $this->addressBuilders = $addressBuilders; 54 | } 55 | 56 | public function __clone() 57 | { 58 | $this->customer = clone $this->customer; 59 | } 60 | 61 | public static function aCustomer(): CustomerBuilder 62 | { 63 | $objectManager = Bootstrap::getObjectManager(); 64 | /** @var CustomerInterface $customer */ 65 | $customer = $objectManager->create(CustomerInterface::class); 66 | $customer->setWebsiteId(1) 67 | ->setGroupId(1) 68 | ->setStoreId(1) 69 | ->setPrefix('Mr.') 70 | ->setFirstname('John') 71 | ->setMiddlename('A') 72 | ->setLastname('Smith') 73 | ->setSuffix('Esq.') 74 | ->setTaxvat('12') 75 | ->setGender(0); 76 | $password = 'Test#123'; 77 | return new self( 78 | $objectManager->create(CustomerRepositoryInterface::class), 79 | $customer, 80 | $objectManager->create(Encryptor::class), 81 | $password 82 | ); 83 | } 84 | 85 | public function withAddresses(AddressBuilder ...$addressBuilders): CustomerBuilder 86 | { 87 | $builder = clone $this; 88 | $builder->addressBuilders = $addressBuilders; 89 | return $builder; 90 | } 91 | 92 | public function withEmail(string $email): CustomerBuilder 93 | { 94 | $builder = clone $this; 95 | $builder->customer->setEmail($email); 96 | return $builder; 97 | } 98 | 99 | public function withGroupId(int $groupId): CustomerBuilder 100 | { 101 | $builder = clone $this; 102 | $builder->customer->setGroupId($groupId); 103 | return $builder; 104 | } 105 | 106 | public function withStoreId(int $storeId): CustomerBuilder 107 | { 108 | $builder = clone $this; 109 | $builder->customer->setStoreId($storeId); 110 | return $builder; 111 | } 112 | 113 | public function withWebsiteId(int $websiteId): CustomerBuilder 114 | { 115 | $builder = clone $this; 116 | $builder->customer->setWebsiteId($websiteId); 117 | return $builder; 118 | } 119 | 120 | public function withPrefix(string $prefix): CustomerBuilder 121 | { 122 | $builder = clone $this; 123 | $builder->customer->setPrefix($prefix); 124 | return $builder; 125 | } 126 | 127 | public function withFirstname(string $firstname): CustomerBuilder 128 | { 129 | $builder = clone $this; 130 | $builder->customer->setFirstname($firstname); 131 | return $builder; 132 | } 133 | 134 | public function withMiddlename(string $middlename): CustomerBuilder 135 | { 136 | $builder = clone $this; 137 | $builder->customer->setMiddlename($middlename); 138 | return $builder; 139 | } 140 | 141 | public function withLastname(string $lastname): CustomerBuilder 142 | { 143 | $builder = clone $this; 144 | $builder->customer->setLastname($lastname); 145 | return $builder; 146 | } 147 | 148 | public function withSuffix(string $suffix): CustomerBuilder 149 | { 150 | $builder = clone $this; 151 | $builder->customer->setSuffix($suffix); 152 | return $builder; 153 | } 154 | 155 | public function withTaxvat(string $taxvat): CustomerBuilder 156 | { 157 | $builder = clone $this; 158 | $builder->customer->setTaxvat($taxvat); 159 | return $builder; 160 | } 161 | 162 | public function withDob(string $dob): CustomerBuilder 163 | { 164 | $builder = clone $this; 165 | $builder->customer->setDob($dob); 166 | return $builder; 167 | } 168 | 169 | /** 170 | * @param mixed[] $values 171 | * @return CustomerBuilder 172 | */ 173 | public function withCustomAttributes(array $values): CustomerBuilder 174 | { 175 | $builder = clone $this; 176 | foreach ($values as $code => $value) { 177 | $builder->customer->setCustomAttribute($code, $value); 178 | } 179 | return $builder; 180 | } 181 | 182 | public function withConfirmation(string $confirmation): CustomerBuilder 183 | { 184 | $builder = clone $this; 185 | $builder->customer->setConfirmation($confirmation); 186 | return $builder; 187 | } 188 | 189 | /** 190 | * @return CustomerInterface 191 | * @throws LocalizedException 192 | */ 193 | public function build(): CustomerInterface 194 | { 195 | $builder = clone $this; 196 | if (!$builder->customer->getEmail()) { 197 | $builder->customer->setEmail(sha1(uniqid('', true)) . '@example.com'); 198 | } 199 | $addresses = array_map( 200 | function (AddressBuilder $addressBuilder) { 201 | return $addressBuilder->buildWithoutSave(); 202 | }, 203 | $builder->addressBuilders 204 | ); 205 | $builder->customer->setAddresses($addresses); 206 | $customer = $builder->saveNewCustomer(); 207 | /* 208 | * Magento automatically sets random confirmation key for new account with password. 209 | * We need to save again with our own confirmation (null for confirmed customer) 210 | */ 211 | $customer->setConfirmation((string)$builder->customer->getConfirmation()); 212 | return $builder->customerRepository->save($customer); 213 | } 214 | 215 | /** 216 | * @SuppressWarnings(PHPMD.UnusedPrivateMethod) False positive: the method is used in build() on the cloned builder 217 | * 218 | * @return CustomerInterface 219 | * @throws LocalizedException 220 | */ 221 | private function saveNewCustomer(): CustomerInterface 222 | { 223 | return $this->customerRepository->save($this->customer, $this->encryptor->getHash($this->password, true)); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/Customer/CustomerFixture.php: -------------------------------------------------------------------------------- 1 | customer = $customer; 24 | } 25 | 26 | public function getCustomer(): CustomerInterface 27 | { 28 | return $this->customer; 29 | } 30 | 31 | public function getDefaultShippingAddressId(): int 32 | { 33 | return (int) $this->customer->getDefaultShipping(); 34 | } 35 | 36 | public function getDefaultBillingAddressId(): int 37 | { 38 | return (int) $this->customer->getDefaultBilling(); 39 | } 40 | 41 | public function getOtherAddressId(): int 42 | { 43 | return $this->getNonDefaultAddressIds()[0]; 44 | } 45 | 46 | /** 47 | * @return int[] 48 | */ 49 | public function getNonDefaultAddressIds(): array 50 | { 51 | return array_values( 52 | array_diff( 53 | $this->getAllAddressIds(), 54 | [$this->getDefaultBillingAddressId(), $this->getDefaultShippingAddressId()] 55 | ) 56 | ); 57 | } 58 | 59 | /** 60 | * @return int[] 61 | */ 62 | public function getAllAddressIds(): array 63 | { 64 | return array_map( 65 | function (AddressInterface $address): int { 66 | return (int)$address->getId(); 67 | }, 68 | (array)$this->customer->getAddresses() 69 | ); 70 | } 71 | 72 | public function getId(): int 73 | { 74 | return (int) $this->customer->getId(); 75 | } 76 | 77 | public function getConfirmation(): string 78 | { 79 | return (string)$this->customer->getConfirmation(); 80 | } 81 | 82 | public function getEmail(): string 83 | { 84 | return $this->customer->getEmail(); 85 | } 86 | 87 | public function login(Session $session = null): void 88 | { 89 | if ($session === null) { 90 | $objectManager = Bootstrap::getObjectManager(); 91 | $objectManager->removeSharedInstance(Session::class); 92 | $session = $objectManager->get(Session::class); 93 | } 94 | $session->setCustomerId($this->getId()); 95 | } 96 | 97 | public function logout(Session $session = null): void 98 | { 99 | if ($session === null) { 100 | $objectManager = Bootstrap::getObjectManager(); 101 | $session = $objectManager->get(Session::class); 102 | } 103 | 104 | $session->logout(); 105 | } 106 | 107 | public function rollback(): void 108 | { 109 | CustomerFixtureRollback::create()->execute($this); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Customer/CustomerFixturePool.php: -------------------------------------------------------------------------------- 1 | customerFixtures[] = new CustomerFixture($customer); 21 | } else { 22 | $this->customerFixtures[$key] = new CustomerFixture($customer); 23 | } 24 | } 25 | 26 | /** 27 | * Returns customer fixture by key, or last added if key not specified 28 | * 29 | * @param string|null $key 30 | * @return CustomerFixture 31 | */ 32 | public function get(string $key = null): CustomerFixture 33 | { 34 | if ($key === null) { 35 | $key = \array_key_last($this->customerFixtures); 36 | } 37 | if ($key === null || !array_key_exists($key, $this->customerFixtures)) { 38 | throw new \OutOfBoundsException('No matching customer found in fixture pool'); 39 | } 40 | return $this->customerFixtures[$key]; 41 | } 42 | 43 | public function rollback(): void 44 | { 45 | CustomerFixtureRollback::create()->execute(...values($this->customerFixtures)); 46 | $this->customerFixtures = []; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Customer/CustomerFixtureRollback.php: -------------------------------------------------------------------------------- 1 | registry = $registry; 28 | $this->customerRepository = $customerRepository; 29 | } 30 | 31 | public static function create(): CustomerFixtureRollback 32 | { 33 | $objectManager = Bootstrap::getObjectManager(); 34 | return new self( 35 | $objectManager->get(Registry::class), 36 | $objectManager->get(CustomerRepositoryInterface::class) 37 | ); 38 | } 39 | 40 | /** 41 | * @param CustomerFixture ...$customerFixtures 42 | * @throws LocalizedException 43 | */ 44 | public function execute(CustomerFixture ...$customerFixtures): void 45 | { 46 | $this->registry->unregister('isSecureArea'); 47 | $this->registry->register('isSecureArea', true); 48 | 49 | foreach ($customerFixtures as $customerFixture) { 50 | $this->customerRepository->deleteById($customerFixture->getId()); 51 | } 52 | 53 | $this->registry->unregister('isSecureArea'); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Sales/CreditmemoBuilder.php: -------------------------------------------------------------------------------- 1 | itemFactory = $itemFactory; 50 | $this->refundOrder = $refundOrder; 51 | $this->creditmemoRepository = $creditmemoRepository; 52 | $this->order = $order; 53 | 54 | $this->orderItems = []; 55 | } 56 | 57 | public static function forOrder( 58 | Order $order 59 | ): CreditmemoBuilder { 60 | $objectManager = Bootstrap::getObjectManager(); 61 | 62 | return new static( 63 | $objectManager->create(CreditmemoItemCreationInterfaceFactory::class), 64 | $objectManager->create(RefundOrderInterface::class), 65 | $objectManager->create(CreditmemoRepositoryInterface::class), 66 | $order 67 | ); 68 | } 69 | 70 | public function withItem(int $orderItemId, int $qty): CreditmemoBuilder 71 | { 72 | $builder = clone $this; 73 | 74 | $builder->orderItems[$orderItemId] = $qty; 75 | 76 | return $builder; 77 | } 78 | 79 | public function build(): CreditmemoInterface 80 | { 81 | // order must be invoiced before a refund can be created. 82 | if ($this->order->canInvoice()) { 83 | InvoiceBuilder::forOrder($this->order)->build(); 84 | } 85 | 86 | // refund items must be explicitly set 87 | if (empty($this->orderItems)) { 88 | foreach ($this->order->getItems() as $item) { 89 | $this->orderItems[$item->getItemId()] = (float)$item->getQtyOrdered(); 90 | } 91 | } 92 | 93 | $creditmemoItems = $this->buildCreditmemoItems(); 94 | 95 | $creditmemoId = $this->refundOrder->execute($this->order->getEntityId(), $creditmemoItems); 96 | 97 | return $this->creditmemoRepository->get($creditmemoId); 98 | } 99 | 100 | /** 101 | * @return \Magento\Sales\Api\Data\CreditmemoItemCreationInterface[] 102 | */ 103 | private function buildCreditmemoItems(): array 104 | { 105 | $creditmemoItems = []; 106 | foreach ($this->orderItems as $orderItemId => $qty) { 107 | $creditmemoItem = $this->itemFactory->create(); 108 | $creditmemoItem->setOrderItemId($orderItemId); 109 | $creditmemoItem->setQty($qty); 110 | $creditmemoItems[] = $creditmemoItem; 111 | } 112 | return $creditmemoItems; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Sales/CreditmemoFixture.php: -------------------------------------------------------------------------------- 1 | creditmemo = $creditmemo; 18 | } 19 | 20 | public function getCreditmemo(): CreditmemoInterface 21 | { 22 | return $this->creditmemo; 23 | } 24 | 25 | public function getId(): int 26 | { 27 | return (int) $this->creditmemo->getEntityId(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Sales/CreditmemoFixturePool.php: -------------------------------------------------------------------------------- 1 | creditmemoFixtures[] = new CreditmemoFixture($creditmemo); 20 | } else { 21 | $this->creditmemoFixtures[$key] = new CreditmemoFixture($creditmemo); 22 | } 23 | } 24 | 25 | /** 26 | * Returns creditmemo fixture by key, or last added if key not specified 27 | * 28 | * @param string|null $key 29 | * @return CreditmemoFixture 30 | */ 31 | public function get(string $key = null): CreditmemoFixture 32 | { 33 | if ($key === null) { 34 | $key = \array_key_last($this->creditmemoFixtures); 35 | } 36 | if ($key === null || !array_key_exists($key, $this->creditmemoFixtures)) { 37 | throw new \OutOfBoundsException('No matching creditmemo found in fixture pool'); 38 | } 39 | return $this->creditmemoFixtures[$key]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Sales/InvoiceBuilder.php: -------------------------------------------------------------------------------- 1 | itemFactory = $itemFactory; 50 | $this->invoiceOrder = $invoiceOrder; 51 | $this->invoiceRepository = $invoiceRepository; 52 | $this->order = $order; 53 | 54 | $this->orderItems = []; 55 | } 56 | 57 | public static function forOrder( 58 | Order $order 59 | ): InvoiceBuilder { 60 | $objectManager = Bootstrap::getObjectManager(); 61 | 62 | return new static( 63 | $objectManager->create(InvoiceItemCreationInterfaceFactory::class), 64 | $objectManager->create(InvoiceOrderInterface::class), 65 | $objectManager->create(InvoiceRepositoryInterface::class), 66 | $order 67 | ); 68 | } 69 | 70 | public function withItem(int $orderItemId, int $qty): InvoiceBuilder 71 | { 72 | $builder = clone $this; 73 | 74 | $builder->orderItems[$orderItemId] = $qty; 75 | 76 | return $builder; 77 | } 78 | 79 | public function build(): InvoiceInterface 80 | { 81 | $invoiceItems = $this->buildInvoiceItems(); 82 | 83 | $invoiceId = $this->invoiceOrder->execute($this->order->getEntityId(), false, $invoiceItems); 84 | 85 | return $this->invoiceRepository->get($invoiceId); 86 | } 87 | 88 | /** 89 | * @return \Magento\Sales\Api\Data\InvoiceItemCreationInterface[] 90 | */ 91 | private function buildInvoiceItems(): array 92 | { 93 | $invoiceItems = []; 94 | 95 | foreach ($this->orderItems as $orderItemId => $qty) { 96 | $invoiceItem = $this->itemFactory->create(); 97 | $invoiceItem->setOrderItemId($orderItemId); 98 | $invoiceItem->setQty($qty); 99 | $invoiceItems[] = $invoiceItem; 100 | } 101 | return $invoiceItems; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Sales/InvoiceFixture.php: -------------------------------------------------------------------------------- 1 | invoice = $shipment; 18 | } 19 | 20 | public function getInvoice(): InvoiceInterface 21 | { 22 | return $this->invoice; 23 | } 24 | 25 | public function getId(): int 26 | { 27 | return (int) $this->invoice->getEntityId(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Sales/InvoiceFixturePool.php: -------------------------------------------------------------------------------- 1 | invoiceFixtures[] = new InvoiceFixture($invoice); 20 | } else { 21 | $this->invoiceFixtures[$key] = new InvoiceFixture($invoice); 22 | } 23 | } 24 | 25 | /** 26 | * Returns invoice fixture by key, or last added if key not specified 27 | * 28 | * @param string|null $key 29 | * @return InvoiceFixture 30 | */ 31 | public function get(string $key = null): InvoiceFixture 32 | { 33 | if ($key === null) { 34 | $key = \array_key_last($this->invoiceFixtures); 35 | } 36 | if ($key === null || !array_key_exists($key, $this->invoiceFixtures)) { 37 | throw new \OutOfBoundsException('No matching invoice found in fixture pool'); 38 | } 39 | return $this->invoiceFixtures[$key]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Sales/OrderBuilder.php: -------------------------------------------------------------------------------- 1 | productBuilders = $productBuilders; 57 | 58 | return $builder; 59 | } 60 | 61 | public function withCustomer(CustomerBuilder $customerBuilder): OrderBuilder 62 | { 63 | $builder = clone $this; 64 | $builder->customerBuilder = $customerBuilder; 65 | 66 | return $builder; 67 | } 68 | 69 | public function withCart(CartBuilder $cartBuilder): OrderBuilder 70 | { 71 | $builder = clone $this; 72 | $builder->cartBuilder = $cartBuilder; 73 | 74 | return $builder; 75 | } 76 | 77 | public function withShippingMethod(string $shippingMethod): OrderBuilder 78 | { 79 | $builder = clone $this; 80 | $builder->shippingMethod = $shippingMethod; 81 | 82 | return $builder; 83 | } 84 | 85 | public function withPaymentMethod(string $paymentMethod): OrderBuilder 86 | { 87 | $builder = clone $this; 88 | $builder->paymentMethod = $paymentMethod; 89 | 90 | return $builder; 91 | } 92 | 93 | /** 94 | * @return Order 95 | * @throws \Exception 96 | */ 97 | public function build(): Order 98 | { 99 | $builder = clone $this; 100 | 101 | if (empty($builder->productBuilders)) { 102 | // init simple products 103 | for ($i = 0; $i < 3; $i++) { 104 | $builder->productBuilders[] = ProductBuilder::aSimpleProduct(); 105 | } 106 | } 107 | 108 | // create products 109 | $products = array_map( 110 | static function (ProductBuilder $productBuilder) { 111 | return $productBuilder->build(); 112 | }, 113 | $builder->productBuilders 114 | ); 115 | 116 | if (empty($builder->customerBuilder)) { 117 | // init customer 118 | $builder->customerBuilder = CustomerBuilder::aCustomer() 119 | ->withAddresses(AddressBuilder::anAddress()->asDefaultBilling()->asDefaultShipping()); 120 | } 121 | 122 | // log customer in 123 | $customer = $builder->customerBuilder->build(); 124 | $customerFixture = new CustomerFixture($customer); 125 | $customerFixture->login(); 126 | 127 | if (empty($builder->cartBuilder)) { 128 | // init cart, add products 129 | $builder->cartBuilder = CartBuilder::forCurrentSession(); 130 | foreach ($products as $product) { 131 | $qty = 1; 132 | $builder->cartBuilder = $builder->cartBuilder->withSimpleProduct($product->getSku(), $qty); 133 | } 134 | } 135 | 136 | // check out, place order 137 | $checkout = CustomerCheckout::fromCart($builder->cartBuilder->build()); 138 | if ($builder->shippingMethod) { 139 | $checkout = $checkout->withShippingMethodCode($builder->shippingMethod); 140 | } 141 | 142 | if ($builder->paymentMethod) { 143 | $checkout = $checkout->withPaymentMethodCode($builder->paymentMethod); 144 | } 145 | 146 | $order = $checkout->placeOrder(); 147 | 148 | $customerFixture->logout(); 149 | 150 | return $order; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/Sales/OrderFixture.php: -------------------------------------------------------------------------------- 1 | order = $order; 18 | } 19 | 20 | public function getOrder(): Order 21 | { 22 | return $this->order; 23 | } 24 | 25 | public function getId(): int 26 | { 27 | return (int) $this->order->getEntityId(); 28 | } 29 | 30 | public function getCustomerId(): int 31 | { 32 | return (int) $this->order->getCustomerId(); 33 | } 34 | 35 | public function getCustomerEmail(): string 36 | { 37 | return (string) $this->order->getCustomerEmail(); 38 | } 39 | 40 | /** 41 | * Obtain `qty_ordered` per order item, indexed with `item_id`. 42 | * 43 | * @return float[] 44 | */ 45 | public function getOrderItemQtys(): array 46 | { 47 | $qtys = []; 48 | foreach ($this->order->getItems() as $item) { 49 | $qtys[$item->getItemId()] = (float)$item->getQtyOrdered(); 50 | } 51 | 52 | return $qtys; 53 | } 54 | 55 | public function getPaymentMethod(): string 56 | { 57 | $payment = $this->order->getPayment(); 58 | if ($payment === null) { 59 | throw new \RuntimeException('Order does not have any payment information'); 60 | } 61 | return (string)$payment->getMethod(); 62 | } 63 | 64 | public function getShippingMethod(): string 65 | { 66 | return (string)$this->order->getShippingMethod(); 67 | } 68 | 69 | public function rollback(): void 70 | { 71 | OrderFixtureRollback::create()->execute($this); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Sales/OrderFixturePool.php: -------------------------------------------------------------------------------- 1 | orderFixtures[] = new OrderFixture($order); 21 | } else { 22 | $this->orderFixtures[$key] = new OrderFixture($order); 23 | } 24 | } 25 | 26 | /** 27 | * Returns order fixture by key, or last added if key not specified 28 | * 29 | * @param string|null $key 30 | * @return OrderFixture 31 | */ 32 | public function get(string $key = null): OrderFixture 33 | { 34 | if ($key === null) { 35 | $key = \array_key_last($this->orderFixtures); 36 | } 37 | if ($key === null || !array_key_exists($key, $this->orderFixtures)) { 38 | throw new \OutOfBoundsException('No matching order found in fixture pool'); 39 | } 40 | return $this->orderFixtures[$key]; 41 | } 42 | 43 | public function rollback(): void 44 | { 45 | OrderFixtureRollback::create()->execute(...values($this->orderFixtures)); 46 | $this->orderFixtures = []; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Sales/OrderFixtureRollback.php: -------------------------------------------------------------------------------- 1 | registry = $registry; 48 | $this->orderRepository = $orderRepository; 49 | $this->customerRepository = $customerRepository; 50 | $this->productRepository = $productRepository; 51 | } 52 | 53 | public static function create(): OrderFixtureRollback 54 | { 55 | $objectManager = Bootstrap::getObjectManager(); 56 | 57 | return new self( 58 | $objectManager->get(Registry::class), 59 | $objectManager->get(OrderRepositoryInterface::class), 60 | $objectManager->get(CustomerRepositoryInterface::class), 61 | $objectManager->get(ProductRepositoryInterface::class) 62 | ); 63 | } 64 | 65 | /** 66 | * Roll back orders with associated customers and products. 67 | * 68 | * @param OrderFixture ...$orderFixtures 69 | * @throws LocalizedException 70 | * @throws NoSuchEntityException 71 | */ 72 | public function execute(OrderFixture ...$orderFixtures): void 73 | { 74 | $this->registry->unregister('isSecureArea'); 75 | $this->registry->register('isSecureArea', true); 76 | 77 | foreach ($orderFixtures as $orderFixture) { 78 | $orderItems = $this->orderRepository->get($orderFixture->getId())->getItems(); 79 | 80 | $this->orderRepository->deleteById($orderFixture->getId()); 81 | $this->customerRepository->deleteById($orderFixture->getCustomerId()); 82 | array_walk( 83 | $orderItems, 84 | function (OrderItemInterface $orderItem) { 85 | try { 86 | $this->productRepository->deleteById($orderItem->getSku()); 87 | } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { 88 | // ignore if already deleted 89 | } 90 | } 91 | ); 92 | } 93 | 94 | $this->registry->unregister('isSecureArea'); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Sales/ShipmentBuilder.php: -------------------------------------------------------------------------------- 1 | itemFactory = $itemFactory; 63 | $this->trackFactory = $trackFactory; 64 | $this->shipOrder = $shipOrder; 65 | $this->shipmentRepository = $shipmentRepository; 66 | $this->order = $order; 67 | 68 | $this->orderItems = []; 69 | $this->trackingNumbers = []; 70 | } 71 | 72 | public static function forOrder( 73 | Order $order 74 | ): ShipmentBuilder { 75 | $objectManager = Bootstrap::getObjectManager(); 76 | 77 | return new static( 78 | $objectManager->create(ShipmentItemCreationInterfaceFactory::class), 79 | $objectManager->create(ShipmentTrackCreationInterfaceFactory::class), 80 | $objectManager->create(ShipOrderInterface::class), 81 | $objectManager->create(ShipmentRepositoryInterface::class), 82 | $order 83 | ); 84 | } 85 | 86 | public function withItem(int $orderItemId, int $qty): ShipmentBuilder 87 | { 88 | $builder = clone $this; 89 | 90 | $builder->orderItems[$orderItemId] = $qty; 91 | 92 | return $builder; 93 | } 94 | 95 | public function withTrackingNumbers(string ...$trackingNumbers): ShipmentBuilder 96 | { 97 | $builder = clone $this; 98 | 99 | $builder->trackingNumbers = $trackingNumbers; 100 | 101 | return $builder; 102 | } 103 | 104 | public function build(): ShipmentInterface 105 | { 106 | $shipmentItems = $this->buildShipmentItems(); 107 | $tracks = $this->buildTracks(); 108 | 109 | $shipmentId = $this->shipOrder->execute( 110 | $this->order->getEntityId(), 111 | $shipmentItems, 112 | false, 113 | false, 114 | null, 115 | $tracks 116 | ); 117 | 118 | $shipment = $this->shipmentRepository->get($shipmentId); 119 | if (!empty($this->trackingNumbers)) { 120 | $shipment->setShippingLabel('%PDF-1.4'); 121 | $this->shipmentRepository->save($shipment); 122 | } 123 | 124 | return $shipment; 125 | } 126 | 127 | /** 128 | * @return \Magento\Sales\Api\Data\ShipmentTrackCreationInterface[] 129 | */ 130 | private function buildTracks(): array 131 | { 132 | return array_map( 133 | function (string $trackingNumber): ShipmentTrackCreationInterface { 134 | $carrierCode = strtok((string)$this->order->getShippingMethod(), '_'); 135 | $track = $this->trackFactory->create(); 136 | $track->setCarrierCode($carrierCode); 137 | $track->setTitle($carrierCode); 138 | $track->setTrackNumber($trackingNumber); 139 | 140 | return $track; 141 | }, 142 | $this->trackingNumbers 143 | ); 144 | } 145 | 146 | /** 147 | * @return \Magento\Sales\Api\Data\ShipmentItemCreationInterface[] 148 | */ 149 | private function buildShipmentItems(): array 150 | { 151 | $shipmentItems = []; 152 | 153 | foreach ($this->orderItems as $orderItemId => $qty) { 154 | $shipmentItem = $this->itemFactory->create(); 155 | $shipmentItem->setOrderItemId($orderItemId); 156 | $shipmentItem->setQty($qty); 157 | $shipmentItems[] = $shipmentItem; 158 | } 159 | return $shipmentItems; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/Sales/ShipmentFixture.php: -------------------------------------------------------------------------------- 1 | shipment = $shipment; 18 | } 19 | 20 | public function getShipment(): ShipmentInterface 21 | { 22 | return $this->shipment; 23 | } 24 | 25 | public function getId(): int 26 | { 27 | return (int) $this->shipment->getEntityId(); 28 | } 29 | 30 | /** 31 | * @return \Magento\Sales\Api\Data\ShipmentTrackInterface[] 32 | */ 33 | public function getTracks(): array 34 | { 35 | return $this->shipment->getTracks(); 36 | } 37 | 38 | public function getShippingLabel(): string 39 | { 40 | return (string) $this->shipment->getShippingLabel(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Sales/ShipmentFixturePool.php: -------------------------------------------------------------------------------- 1 | shipmentFixtures[] = new ShipmentFixture($shipment); 20 | } else { 21 | $this->shipmentFixtures[$key] = new ShipmentFixture($shipment); 22 | } 23 | } 24 | 25 | /** 26 | * Returns shipment fixture by key, or last added if key not specified 27 | * 28 | * @param string|null $key 29 | * @return ShipmentFixture 30 | */ 31 | public function get(string $key = null): ShipmentFixture 32 | { 33 | if ($key === null) { 34 | $key = \array_key_last($this->shipmentFixtures); 35 | } 36 | if ($key === null || !array_key_exists($key, $this->shipmentFixtures)) { 37 | throw new \OutOfBoundsException('No matching shipment found in fixture pool'); 38 | } 39 | return $this->shipmentFixtures[$key]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Theme/ThemeFixture.php: -------------------------------------------------------------------------------- 1 | get(Registration::class); 23 | $registration->register(); 24 | } 25 | 26 | /** 27 | * Set the current theme 28 | * 29 | * @param string $themePath a theme identifier without the area, e.g. Magento/luma 30 | */ 31 | public static function setCurrentTheme(string $themePath): void 32 | { 33 | /** @var DesignInterface $design */ 34 | $design = Bootstrap::getObjectManager()->get(DesignInterface::class); 35 | $design->setDesignTheme($themePath); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/Catalog/CategoryBuilderTest.php: -------------------------------------------------------------------------------- 1 | categoryRepository = Bootstrap::getObjectManager()->create(CategoryRepositoryInterface::class); 35 | $this->categories = []; 36 | $this->products = []; 37 | } 38 | 39 | protected function tearDown(): void 40 | { 41 | if (!empty($this->categories)) { 42 | foreach ($this->categories as $product) { 43 | CategoryFixtureRollback::create()->execute($product); 44 | } 45 | } 46 | if (! empty($this->products)) { 47 | foreach ($this->products as $product) { 48 | ProductFixtureRollback::create()->execute($product); 49 | } 50 | } 51 | } 52 | 53 | public function testDefaultTopLevelCategory() 54 | { 55 | $categoryFixture = new CategoryFixture( 56 | CategoryBuilder::topLevelCategory()->build() 57 | ); 58 | $this->categories[] = $categoryFixture; 59 | 60 | /** @var Category $category */ 61 | $category = $this->categoryRepository->get($categoryFixture->getId()); 62 | 63 | // store ids are mixed type, normalize first for strict type checking 64 | $storeIds = array_map('strval', $category->getStoreIds()); 65 | 66 | $this->assertEquals('Top Level Category', $category->getName(), 'Category name does not match expected value.'); 67 | $this->assertContains('0', $storeIds, 'Admin store ID is not assigned.'); 68 | $this->assertContains('1', $storeIds, 'Default store ID is not assigned.'); 69 | $this->assertEquals( 70 | '1/2/' . $categoryFixture->getId(), 71 | $category->getPath(), 72 | 'Category path does not match expected value.' 73 | ); 74 | } 75 | 76 | public function testDefaultChildCategory() 77 | { 78 | $parentCategoryFixture = new CategoryFixture( 79 | CategoryBuilder::topLevelCategory()->build() 80 | ); 81 | $this->categories[] = $parentCategoryFixture; 82 | $childCategoryFixture = new CategoryFixture( 83 | CategoryBuilder::childCategoryOf($parentCategoryFixture)->build() 84 | ); 85 | 86 | /** @var Category $category */ 87 | $category = $this->categoryRepository->get($childCategoryFixture->getId()); 88 | 89 | // store ids are mixed type, normalize first for strict type checking 90 | $storeIds = array_map('strval', $category->getStoreIds()); 91 | 92 | $this->assertEquals('Child Category', $category->getName(), 'Category name does not match expected value.'); 93 | $this->assertContains('0', $storeIds, 'Admin store ID is not assigned.'); 94 | $this->assertContains('1', $storeIds, 'Default store ID is not assigned.'); 95 | $this->assertEquals( 96 | '1/2/' . $parentCategoryFixture->getId() . '/' . $childCategoryFixture->getId(), 97 | $category->getPath(), 98 | 'Category path does not match expected value.' 99 | ); 100 | } 101 | 102 | public function testCategoryWithSpecificAttributes() 103 | { 104 | $categoryFixture = new CategoryFixture( 105 | CategoryBuilder::topLevelCategory() 106 | ->withName('Custom Name') 107 | ->withDescription('Custom Description') 108 | ->withIsActive(false) 109 | ->withUrlKey('my-url-key') 110 | ->build() 111 | ); 112 | $this->categories[] = $categoryFixture; 113 | 114 | /** @var Category $category */ 115 | $category = $this->categoryRepository->get($categoryFixture->getId()); 116 | $this->assertEquals('0', $category->getIsActive(), 'Category should be inactive'); 117 | $this->assertEquals('Custom Name', $category->getName(), 'Category name'); 118 | $this->assertEquals('my-url-key', $category->getUrlKey(), 'Category URL key'); 119 | $this->assertEquals( 120 | 'Custom Description', 121 | $category->getCustomAttribute('description')->getValue(), 122 | 'Category description' 123 | ); 124 | } 125 | 126 | public function testCategoryWithProducts() 127 | { 128 | $product1 = new ProductFixture(ProductBuilder::aSimpleProduct()->build()); 129 | $product2 = new ProductFixture(ProductBuilder::aSimpleProduct()->build()); 130 | $categoryFixture = new CategoryFixture( 131 | CategoryBuilder::topLevelCategory()->withProducts([$product1->getSku(), $product2->getSku()])->build() 132 | ); 133 | $this->products[] = $product1; 134 | $this->products[] = $product2; 135 | $this->categories[] = $categoryFixture; 136 | 137 | /** @var Category $category */ 138 | $category = $this->categoryRepository->get($categoryFixture->getId()); 139 | 140 | $this->assertEquals( 141 | [$product1->getId() => 0, $product2->getId() => 1], 142 | $category->getProductsPosition(), 143 | 'Product positions' 144 | ); 145 | } 146 | 147 | public function testMultipleCategories() 148 | { 149 | $this->categories[0] = new CategoryFixture( 150 | CategoryBuilder::topLevelCategory()->build() 151 | ); 152 | $this->categories[1] = new CategoryFixture( 153 | CategoryBuilder::topLevelCategory()->build() 154 | ); 155 | 156 | /** @var Category $category1 */ 157 | $category1 = $this->categoryRepository->get($this->categories[0]->getId()); 158 | /** @var Category $category2 */ 159 | $category2 = $this->categoryRepository->get($this->categories[1]->getId()); 160 | $this->assertNotEquals( 161 | $category1->getUrlKey(), 162 | $category2->getUrlKey(), 163 | 'Categories should be created with different URL keys' 164 | ); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /tests/Catalog/CategoryFixturePoolTest.php: -------------------------------------------------------------------------------- 1 | categoryFixtures = new CategoryFixturePool(); 31 | $this->categoryRepository = Bootstrap::getObjectManager()->create(CategoryRepositoryInterface::class); 32 | } 33 | 34 | public function testLastCategoryFixtureReturnedByDefault() 35 | { 36 | $firstCategory = $this->createCategory(); 37 | $lastCategory = $this->createCategory(); 38 | $this->categoryFixtures->add($firstCategory); 39 | $this->categoryFixtures->add($lastCategory); 40 | $categoryFixture = $this->categoryFixtures->get(); 41 | $this->assertEquals($lastCategory->getId(), $categoryFixture->getId()); 42 | } 43 | 44 | public function testExceptionThrownWhenAccessingEmptyCategoryPool() 45 | { 46 | $this->expectException(\OutOfBoundsException::class); 47 | $this->categoryFixtures->get(); 48 | } 49 | 50 | public function testCategoryFixtureReturnedByKey() 51 | { 52 | $firstCategory = $this->createCategory(); 53 | $lastCategory = $this->createCategory(); 54 | $this->categoryFixtures->add($firstCategory, 'first'); 55 | $this->categoryFixtures->add($lastCategory, 'last'); 56 | $categoryFixture = $this->categoryFixtures->get('first'); 57 | $this->assertEquals($firstCategory->getId(), $categoryFixture->getId()); 58 | } 59 | 60 | public function testCategoryFixtureReturnedByNumericKey() 61 | { 62 | $firstCategory = $this->createCategory(); 63 | $lastCategory = $this->createCategory(); 64 | $this->categoryFixtures->add($firstCategory); 65 | $this->categoryFixtures->add($lastCategory); 66 | $categoryFixture = $this->categoryFixtures->get(0); 67 | $this->assertEquals($firstCategory->getId(), $categoryFixture->getId()); 68 | } 69 | 70 | public function testExceptionThrownWhenAccessingNonexistingKey() 71 | { 72 | $category = $this->createCategory(); 73 | $this->categoryFixtures->add($category, 'foo'); 74 | $this->expectException(\OutOfBoundsException::class); 75 | $this->categoryFixtures->get('bar'); 76 | } 77 | 78 | public function testRollbackRemovesCategorysFromPool() 79 | { 80 | $category = $this->createCategoryInDb(); 81 | $this->categoryFixtures->add($category); 82 | $this->categoryFixtures->rollback(); 83 | $this->expectException(\OutOfBoundsException::class); 84 | $this->categoryFixtures->get(); 85 | } 86 | 87 | public function testRollbackWorksWithKeys() 88 | { 89 | $category = $this->createCategoryInDb(); 90 | $this->categoryFixtures->add($category, 'key'); 91 | $this->categoryFixtures->rollback(); 92 | $this->expectException(\OutOfBoundsException::class); 93 | $this->categoryFixtures->get(); 94 | } 95 | 96 | public function testRollbackDeletesCategorysFromDb() 97 | { 98 | $category = $this->createCategoryInDb(); 99 | $this->categoryFixtures->add($category); 100 | $this->categoryFixtures->rollback(); 101 | $this->expectException(NoSuchEntityException::class); 102 | $this->categoryRepository->get($category->getId()); 103 | } 104 | 105 | /** 106 | * Creates dummy category object 107 | * 108 | * @return \Magento\Catalog\Model\Category 109 | * @throws \Exception 110 | */ 111 | private function createCategory(): \Magento\Catalog\Model\Category 112 | { 113 | static $nextId = 1; 114 | /** @var Category $category */ 115 | $category = Bootstrap::getObjectManager()->create(Category::class); 116 | $category->setId($nextId++); 117 | return $category; 118 | } 119 | 120 | /** 121 | * Creates category using builder 122 | * 123 | * @return \Magento\Catalog\Model\Category 124 | * @throws \Exception 125 | */ 126 | private function createCategoryInDb(): \Magento\Catalog\Model\Category 127 | { 128 | return CategoryBuilder::topLevelCategory()->build(); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /tests/Catalog/IndexerErrorsTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('Since Magento 2.3.6 / 2.4.1 these transaction exceptions do not occur anymore'); 29 | $this->expectException(\Exception::class); 30 | 31 | try { 32 | /** @var StoreManagerInterface $storeManager */ 33 | $storeManager = Bootstrap::getObjectManager()->get(StoreManagerInterface::class); 34 | $secondWebsiteId = $storeManager->getWebsite('test')->getId(); 35 | ProductBuilder::aSimpleProduct()->withWebsiteIds([$secondWebsiteId])->build(); 36 | } catch (\Exception $exception) { 37 | // manual check, there is no common assertion in PHPUnit 6 / PHPUnit 9 38 | $this->assertNotFalse( 39 | preg_match( 40 | '{@magentoDataFixtureBeforeTransaction Magento/Catalog/_files/enable_reindex_schedule.php}', 41 | $exception->getMessage() 42 | ) 43 | ); 44 | 45 | throw $exception; 46 | } 47 | } 48 | 49 | public static function disableReindexSchedule() 50 | { 51 | /* @var IndexerInterface $model */ 52 | $model = Bootstrap::getObjectManager()->get(IndexerRegistry::class)->get('catalogsearch_fulltext'); 53 | self::$indexIsScheduledOrig = $model->isScheduled(); 54 | $model->setScheduled(false); 55 | } 56 | 57 | public static function disableReindexScheduleRollback() 58 | { 59 | /* @var IndexerInterface $model */ 60 | $model = Bootstrap::getObjectManager()->get(IndexerRegistry::class)->get('catalogsearch_fulltext'); 61 | $model->setScheduled(self::$indexIsScheduledOrig); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/Catalog/OptionBuilderTest.php: -------------------------------------------------------------------------------- 1 | optionManagement = $objectManager->get(AttributeOptionManagementInterface::class); 37 | $this->optionFactory = $objectManager->get(OptionFactory::class); 38 | $this->optionResourceModel = $objectManager->get(OptionResource::class); 39 | } 40 | 41 | protected function tearDown(): void 42 | { 43 | if (!empty($this->options)) { 44 | foreach ($this->options as $optionFixture) { 45 | $optionFixture->rollBack(); 46 | } 47 | } 48 | } 49 | 50 | /** 51 | * @magentoDataFixture Magento/Catalog/_files/dropdown_attribute.php 52 | */ 53 | public function testAddOption(): void 54 | { 55 | $userDefinedAttributeCode = 'dropdown_attribute'; 56 | $optionFixture = new OptionFixture( 57 | OptionBuilder::anOptionFor($userDefinedAttributeCode)->build(), 58 | $userDefinedAttributeCode 59 | ); 60 | $this->options[] = $optionFixture; 61 | 62 | /** @var \Magento\Eav\Model\Entity\Attribute\Option $option */ 63 | $option = $this->optionFactory->create(); 64 | $this->optionResourceModel->load($option, $optionFixture->getOption()->getId()); 65 | 66 | self::assertEquals($optionFixture->getOption()->getId(), $option->getId()); 67 | } 68 | 69 | /** 70 | * @magentoDataFixture Magento/Catalog/_files/dropdown_attribute.php 71 | */ 72 | public function testAddOptionWithLabel(): void 73 | { 74 | $userDefinedAttributeCode = 'dropdown_attribute'; 75 | $label = uniqid('Label ', true); 76 | $optionFixture = new OptionFixture( 77 | OptionBuilder::anOptionFor($userDefinedAttributeCode)->withLabel($label)->build(), 78 | $userDefinedAttributeCode 79 | ); 80 | $this->options[] = $optionFixture; 81 | 82 | /** @var \Magento\Eav\Model\Entity\Attribute\Option $option */ 83 | $option = $this->optionFactory->create(); 84 | $this->optionResourceModel->load($option, $optionFixture->getOption()->getId()); 85 | 86 | $items = $this->optionManagement->getItems(Product::ENTITY, $userDefinedAttributeCode); 87 | 88 | self::assertEquals($optionFixture->getOption()->getId(), $option->getId()); 89 | $foundLabel = false; 90 | foreach ($items as $item) { 91 | if ((int)$item->getValue() === $optionFixture->getOption()->getId()) { 92 | self::assertEquals($label, $item->getLabel()); 93 | $foundLabel = true; 94 | } 95 | } 96 | if (!$foundLabel) { 97 | self::fail('No label found'); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /tests/Catalog/OptionFixturePoolTest.php: -------------------------------------------------------------------------------- 1 | optionFixtures = new OptionFixturePool(); 44 | $this->optionFactory = Bootstrap::getObjectManager()->get(AttributeOptionFactory::class); 45 | $this->optionResourceModel = Bootstrap::getObjectManager()->get(OptionResource::class); 46 | } 47 | 48 | public function testLastOptionFixtureReturnedByDefault(): void 49 | { 50 | $firstOption = $this->createOption(); 51 | $lastOption = $this->createOption(); 52 | $this->optionFixtures->add($firstOption, 'option_1'); 53 | $this->optionFixtures->add($lastOption, 'option_2'); 54 | $optionFixture = $this->optionFixtures->get(); 55 | self::assertEquals($lastOption->getId(), $optionFixture->getOption()->getId()); 56 | } 57 | 58 | public function testExceptionThrownWhenAccessingEmptyOptionPool(): void 59 | { 60 | $this->expectException(\OutOfBoundsException::class); 61 | $this->optionFixtures->get(); 62 | } 63 | 64 | public function testOptionFixtureReturnedByKey(): void 65 | { 66 | $firstOption = $this->createOption(); 67 | $lastOption = $this->createOption(); 68 | $this->optionFixtures->add($firstOption, 'option_1', 'first'); 69 | $this->optionFixtures->add($lastOption, 'option_2', 'last'); 70 | $optionFixture = $this->optionFixtures->get('first'); 71 | self::assertEquals($firstOption->getId(), $optionFixture->getOption()->getId()); 72 | } 73 | 74 | public function testExceptionThrownWhenAccessingNonexistingKey(): void 75 | { 76 | $option = $this->createOption(); 77 | $this->optionFixtures->add($option, 'option_1', 'foo'); 78 | $this->expectException(\OutOfBoundsException::class); 79 | $this->optionFixtures->get('bar'); 80 | } 81 | 82 | /** 83 | * @magentoDataFixture Magento/Catalog/_files/dropdown_attribute.php 84 | */ 85 | public function testRollbackRemovesOptionsFromPool(): void 86 | { 87 | $option = $this->createOptionInDb($this->dbAttributeCode); 88 | $this->optionFixtures->add($option, $this->dbAttributeCode); 89 | $this->optionFixtures->rollback(); 90 | $this->expectException(\OutOfBoundsException::class); 91 | $this->optionFixtures->get(); 92 | } 93 | 94 | /** 95 | * @magentoDataFixture Magento/Catalog/_files/dropdown_attribute.php 96 | */ 97 | public function testRollbackDeletesOptionsFromDb(): void 98 | { 99 | $option = $this->createOptionInDb($this->dbAttributeCode); 100 | $this->optionFixtures->add($option, $this->dbAttributeCode); 101 | $this->optionFixtures->rollback(); 102 | $option = $this->optionFactory->create(); 103 | $this->optionResourceModel->load($option, $option->getId()); 104 | self::assertEmpty($option->getId()); 105 | } 106 | 107 | /** 108 | * Creates a dummy option object 109 | * 110 | * @return AttributeOption 111 | */ 112 | private function createOption(): AttributeOption 113 | { 114 | static $nextId = 1; 115 | /** @var AttributeOption $option */ 116 | $option = Bootstrap::getObjectManager()->create(AttributeOption::class); 117 | $option->setId($nextId++); 118 | 119 | return $option; 120 | } 121 | 122 | /** 123 | * Uses builder to create a customer 124 | * 125 | * @param string $attributeCode 126 | * @return AttributeOption 127 | * @throws \Magento\Framework\Exception\InputException 128 | * @throws \Magento\Framework\Exception\StateException 129 | */ 130 | private function createOptionInDb(string $attributeCode): AttributeOption 131 | { 132 | return OptionBuilder::anOptionFor($attributeCode)->withLabel('Testing')->build(); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /tests/Catalog/OptionFixtureRollbackTest.php: -------------------------------------------------------------------------------- 1 | optionFactory = $objectManager->get(OptionFactory::class); 31 | $this->optionResourceModel = $objectManager->get(OptionResource::class); 32 | } 33 | 34 | /** 35 | * @magentoDataFixture Magento/Catalog/_files/dropdown_attribute.php 36 | */ 37 | public function testRollbackSingleOptionFixture(): void 38 | { 39 | $userDefinedAttributeCode = 'dropdown_attribute'; 40 | $optionFixture = new OptionFixture( 41 | OptionBuilder::anOptionFor($userDefinedAttributeCode)->build(), 42 | $userDefinedAttributeCode 43 | ); 44 | OptionFixtureRollback::create()->execute($optionFixture); 45 | 46 | /** @var \Magento\Eav\Model\Entity\Attribute\Option $option */ 47 | $option = $this->optionFactory->create(); 48 | $this->optionResourceModel->load($option, $optionFixture->getOption()->getId()); 49 | self::assertNull($option->getId()); 50 | } 51 | 52 | /** 53 | * @magentoDataFixture Magento/Catalog/_files/dropdown_attribute.php 54 | */ 55 | public function testRollbackMultipleOptionFixtures(): void 56 | { 57 | $userDefinedAttributeCode = 'dropdown_attribute'; 58 | $optionFixture = new OptionFixture( 59 | OptionBuilder::anOptionFor($userDefinedAttributeCode)->build(), 60 | $userDefinedAttributeCode 61 | ); 62 | $otherOptionFixture = new OptionFixture( 63 | OptionBuilder::anOptionFor($userDefinedAttributeCode)->build(), 64 | $userDefinedAttributeCode 65 | ); 66 | OptionFixtureRollback::create()->execute($optionFixture, $otherOptionFixture); 67 | 68 | /** @var \Magento\Eav\Model\Entity\Attribute\Option $option */ 69 | $option = $this->optionFactory->create(); 70 | $this->optionResourceModel->load($option, $optionFixture->getOption()->getId()); 71 | self::assertNull($option->getId()); 72 | 73 | $this->optionResourceModel->load($option, $otherOptionFixture->getOption()->getId()); 74 | self::assertNull($option->getId()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/Catalog/ProductFixturePoolTest.php: -------------------------------------------------------------------------------- 1 | productFixtures = new ProductFixturePool(); 31 | $this->productRepository = Bootstrap::getObjectManager()->create(ProductRepositoryInterface::class); 32 | } 33 | 34 | public function testLastProductFixtureReturnedByDefault() 35 | { 36 | $firstProduct = $this->createProduct(); 37 | $lastProduct = $this->createProduct(); 38 | $this->productFixtures->add($firstProduct); 39 | $this->productFixtures->add($lastProduct); 40 | $productFixture = $this->productFixtures->get(); 41 | $this->assertEquals($lastProduct->getSku(), $productFixture->getSku()); 42 | } 43 | 44 | public function testExceptionThrownWhenAccessingEmptyProductPool() 45 | { 46 | $this->expectException(\OutOfBoundsException::class); 47 | $this->productFixtures->get(); 48 | } 49 | 50 | public function testProductFixtureReturnedByKey() 51 | { 52 | $firstProduct = $this->createProduct(); 53 | $lastProduct = $this->createProduct(); 54 | $this->productFixtures->add($firstProduct, 'first'); 55 | $this->productFixtures->add($lastProduct, 'last'); 56 | $productFixture = $this->productFixtures->get('first'); 57 | $this->assertEquals($firstProduct->getSku(), $productFixture->getSku()); 58 | } 59 | 60 | public function testProductFixtureReturnedByNumericKey() 61 | { 62 | $firstProduct = $this->createProduct(); 63 | $lastProduct = $this->createProduct(); 64 | $this->productFixtures->add($firstProduct); 65 | $this->productFixtures->add($lastProduct); 66 | $productFixture = $this->productFixtures->get(0); 67 | $this->assertEquals($firstProduct->getSku(), $productFixture->getSku()); 68 | } 69 | 70 | public function testExceptionThrownWhenAccessingNonexistingKey() 71 | { 72 | $product = $this->createProduct(); 73 | $this->productFixtures->add($product, 'foo'); 74 | $this->expectException(\OutOfBoundsException::class); 75 | $this->productFixtures->get('bar'); 76 | } 77 | 78 | public function testRollbackRemovesProductsFromPool() 79 | { 80 | $product = $this->createProductInDb(); 81 | $this->productFixtures->add($product); 82 | $this->productFixtures->rollback(); 83 | $this->expectException(\OutOfBoundsException::class); 84 | $this->productFixtures->get(); 85 | } 86 | 87 | public function testRollbackWorksWithKeys() 88 | { 89 | $product = $this->createProductInDb(); 90 | $this->productFixtures->add($product, 'key'); 91 | $this->productFixtures->rollback(); 92 | $this->expectException(\OutOfBoundsException::class); 93 | $this->productFixtures->get(); 94 | } 95 | 96 | public function testRollbackDeletesProductsFromDb() 97 | { 98 | $product = $this->createProductInDb(); 99 | $this->productFixtures->add($product); 100 | $this->productFixtures->rollback(); 101 | $this->expectException(NoSuchEntityException::class); 102 | $this->productRepository->get($product->getSku()); 103 | } 104 | 105 | /** 106 | * Creates a dummy product object 107 | * 108 | * @return ProductInterface 109 | * @throws \Exception 110 | */ 111 | private function createProduct(): ProductInterface 112 | { 113 | static $nextId = 1; 114 | /** @var ProductInterface $product */ 115 | $product = Bootstrap::getObjectManager()->create(ProductInterface::class); 116 | $product->setSku('product-' . $nextId); 117 | $product->setId($nextId++); 118 | return $product; 119 | } 120 | 121 | /** 122 | * Uses builder to create a product 123 | * 124 | * @return ProductInterface 125 | * @throws \Exception 126 | */ 127 | private function createProductInDb(): ProductInterface 128 | { 129 | return ProductBuilder::aSimpleProduct()->build(); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /tests/Catalog/ProductFixtureRollbackTest.php: -------------------------------------------------------------------------------- 1 | productRepository = Bootstrap::getObjectManager()->create(ProductRepositoryInterface::class); 25 | } 26 | 27 | public function testRollbackSingleProductFixture() 28 | { 29 | $productFixture = new ProductFixture( 30 | ProductBuilder::aSimpleProduct()->build() 31 | ); 32 | ProductFixtureRollback::create()->execute($productFixture); 33 | $this->expectException(NoSuchEntityException::class); 34 | $this->productRepository->getById($productFixture->getId()); 35 | } 36 | 37 | public function testRollbackMultipleProductFixtures() 38 | { 39 | $productFixture = new ProductFixture( 40 | ProductBuilder::aSimpleProduct()->build() 41 | ); 42 | $otherProductFixture = new ProductFixture( 43 | ProductBuilder::aSimpleProduct()->build() 44 | ); 45 | ProductFixtureRollback::create()->execute($productFixture, $otherProductFixture); 46 | $productDeleted = false; 47 | try { 48 | $this->productRepository->getById($productFixture->getId()); 49 | } catch (NoSuchEntityException $e) { 50 | $productDeleted = true; 51 | } 52 | $this->assertTrue($productDeleted, 'First product should be deleted'); 53 | $this->expectException(NoSuchEntityException::class); 54 | $this->productRepository->getById($otherProductFixture->getId()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Checkout/CartBuilderTest.php: -------------------------------------------------------------------------------- 1 | productFixture = new ProductFixture( 20 | ProductBuilder::aSimpleProduct()->build() 21 | ); 22 | } 23 | 24 | /** 25 | * @magentoAppIsolation enabled 26 | * @magentoAppArea frontend 27 | */ 28 | public function testProductCanBeAddedWithCustomBuyRequest() 29 | { 30 | $qty = 2; 31 | $customOptionId = 42; 32 | $customOptionValue = 'foobar'; 33 | $cart = CartBuilder::forCurrentSession()->withProductRequest( 34 | $this->productFixture->getSku(), 35 | $qty, 36 | ['options' => [$customOptionId => $customOptionValue]] 37 | )->build(); 38 | $quoteItems = $cart->getQuote()->getAllItems(); 39 | $this->assertCount(1, $quoteItems, "1 quote item should be added"); 40 | /** @var Item $quoteItem */ 41 | $quoteItem = reset($quoteItems); 42 | $serializedBuyRequest = $quoteItem->getOptionByCode('info_buyRequest')->getValue(); 43 | if (!json_decode($serializedBuyRequest)) { 44 | // Magento 2.1 PHP serialization 45 | $serializedBuyRequest = json_encode(unserialize($serializedBuyRequest, [])); 46 | } 47 | $this->assertJsonStringEqualsJsonString( 48 | json_encode(['qty' => $qty, 'options' => ['42' => 'foobar']]), 49 | $serializedBuyRequest, 50 | "Value of info_buyRequest option should be as configured" 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/Checkout/CustomerCheckoutTest.php: -------------------------------------------------------------------------------- 1 | productFixtures = new ProductFixturePool(); 29 | $this->customerFixtures = new CustomerFixturePool(); 30 | $this->customerFixtures->add( 31 | CustomerBuilder::aCustomer()->withAddresses( 32 | AddressBuilder::anAddress()->asDefaultBilling()->asDefaultShipping() 33 | )->build() 34 | ); 35 | $this->productFixtures->add( 36 | ProductBuilder::aSimpleProduct()->withPrice(10)->build(), 37 | 'simple' 38 | ); 39 | $this->productFixtures->add( 40 | ProductBuilder::aVirtualProduct()->withPrice(10)->build(), 41 | 'virtual' 42 | ); 43 | } 44 | 45 | protected function tearDown(): void 46 | { 47 | $this->customerFixtures->rollback(); 48 | $this->productFixtures->rollback(); 49 | } 50 | 51 | /** 52 | * @magentoAppIsolation enabled 53 | * @magentoAppArea frontend 54 | */ 55 | public function testCreateOrderFromCart() 56 | { 57 | $this->customerFixtures->get()->login(); 58 | $checkout = CustomerCheckout::fromCart( 59 | CartBuilder::forCurrentSession()->withSimpleProduct( 60 | $this->productFixtures->get('simple')->getSku() 61 | )->build() 62 | ); 63 | $order = $checkout->placeOrder(); 64 | $this->assertNotEmpty($order->getEntityId(), 'Order should be saved successfully'); 65 | $this->assertNotEmpty($order->getShippingDescription(), 'Order should have a shipping description'); 66 | } 67 | 68 | /** 69 | * @magentoAppIsolation enabled 70 | * @magentoAppArea frontend 71 | */ 72 | public function testCreateOrderFromCartWithVirtualProduct() 73 | { 74 | $this->customerFixtures->get()->login(); 75 | $checkout = CustomerCheckout::fromCart( 76 | CartBuilder::forCurrentSession()->withSimpleProduct( 77 | $this->productFixtures->get('virtual')->getSku() 78 | )->build() 79 | ); 80 | $order = $checkout->placeOrder(); 81 | $this->assertNotEmpty($order->getEntityId(), 'Order should be saved successfully'); 82 | $this->assertEmpty( 83 | $order->getExtensionAttributes()->getShippingAssignments(), 84 | 'Order with virtual product should not have any shipping assignments' 85 | ); 86 | $this->assertEmpty($order->getShippingDescription(), 'Order should not have a shipping description'); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /tests/Core/ConfigFixtureTest.php: -------------------------------------------------------------------------------- 1 | scopeConfig = $objectManager->get(MutableScopeConfigInterface::class); 35 | $this->storeManager = $objectManager->get(StoreManagerInterface::class); 36 | } 37 | 38 | public function testSetGlobalChangesDefaultScope() 39 | { 40 | ConfigFixture::setGlobal(self::STORE_NAME_PATH, 'Ye Olde Wizard Shop'); 41 | $this->assertConfigValue( 42 | 'Ye Olde Wizard Shop', 43 | self::STORE_NAME_PATH, 44 | ScopeConfigInterface::SCOPE_TYPE_DEFAULT 45 | ); 46 | } 47 | 48 | /** 49 | * @magentoAppArea frontend 50 | */ 51 | public function testSetGlobalOverridesAllScopes() 52 | { 53 | $this->givenStoreValue(self::STORE_NAME_PATH, 'Store Override'); 54 | $this->givenWebsiteValue(self::STORE_NAME_PATH, 'Website Override'); 55 | ConfigFixture::setGlobal(self::STORE_NAME_PATH, 'Global Value'); 56 | $this->assertConfigValue('Global Value', self::STORE_NAME_PATH, ScopeInterface::SCOPE_STORE); 57 | } 58 | 59 | /** 60 | * @magentoAppArea frontend 61 | * @magentoDataFixture Magento/Store/_files/second_store.php 62 | */ 63 | public function testSetForStoreWithCurrentStore() 64 | { 65 | $this->storeManager->setCurrentStore(self::SECOND_STORE_ID_FROM_FIXTURE); 66 | ConfigFixture::setForStore(self::STORE_NAME_PATH, 'Store store'); 67 | $this->assertConfigValue( 68 | 'Store store', 69 | self::STORE_NAME_PATH, 70 | ScopeInterface::SCOPE_STORE, 71 | self::SECOND_STORE_ID_FROM_FIXTURE 72 | ); 73 | } 74 | 75 | /** 76 | * @magentoAppArea frontend 77 | * @magentoDataFixture Magento/Store/_files/second_store.php 78 | */ 79 | public function testSetForStoreWithExplicitStore() 80 | { 81 | ConfigFixture::setForStore(self::STORE_NAME_PATH, 'Store 1', self::FIRST_STORE_ID); 82 | ConfigFixture::setForStore(self::STORE_NAME_PATH, 'Store 2', self::SECOND_STORE_ID_FROM_FIXTURE); 83 | $this->assertConfigValue( 84 | 'Store 1', 85 | self::STORE_NAME_PATH, 86 | ScopeInterface::SCOPE_STORE, 87 | self::FIRST_STORE_ID 88 | ); 89 | $this->assertConfigValue( 90 | 'Store 2', 91 | self::STORE_NAME_PATH, 92 | ScopeInterface::SCOPE_STORE, 93 | self::SECOND_STORE_ID_FROM_FIXTURE 94 | ); 95 | } 96 | 97 | private function givenStoreValue(string $path, string $storeValue): void 98 | { 99 | $this->scopeConfig->setValue($path, $storeValue, ScopeInterface::SCOPE_STORE); 100 | } 101 | 102 | private function givenWebsiteValue(string $path, string $websiteValue): void 103 | { 104 | $this->scopeConfig->setValue($path, $websiteValue, ScopeInterface::SCOPE_WEBSITE); 105 | } 106 | 107 | private function assertConfigValue($expectedValue, string $path, string $scope, string $scopeCode = null): void 108 | { 109 | $this->assertEquals( 110 | $expectedValue, 111 | $this->scopeConfig->getValue($path, $scope, $scopeCode) 112 | ); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /tests/Customer/CustomerBuilderTest.php: -------------------------------------------------------------------------------- 1 | objectManager = Bootstrap::getObjectManager(); 37 | $this->customerRepository = $this->objectManager->create(CustomerRepositoryInterface::class); 38 | $this->customers = []; 39 | } 40 | 41 | protected function tearDown(): void 42 | { 43 | if (! empty($this->customers)) { 44 | foreach ($this->customers as $customer) { 45 | CustomerFixtureRollback::create()->execute($customer); 46 | } 47 | } 48 | } 49 | 50 | public function testDefaultCustomer(): void 51 | { 52 | $customerFixture = new CustomerFixture( 53 | CustomerBuilder::aCustomer()->build() 54 | ); 55 | $this->customers[] = $customerFixture; 56 | $customer = $this->customerRepository->getById($customerFixture->getId()); 57 | $this->assertNull($customer->getConfirmation(), 'Customer should be active'); 58 | $this->assertEquals(1, $customer->getWebsiteId(), 'Default website'); 59 | $this->assertEquals(1, $customer->getStoreId(), 'Default store'); 60 | $this->assertEquals(1, $customer->getGroupId(), 'Default customer group'); 61 | } 62 | 63 | public function testDefaultCustomerWithDefaultAddresses(): void 64 | { 65 | $customerFixture = new CustomerFixture( 66 | CustomerBuilder::aCustomer() 67 | ->withAddresses( 68 | AddressBuilder::anAddress()->asDefaultShipping(), 69 | AddressBuilder::anAddress()->asDefaultBilling() 70 | )->build() 71 | ); 72 | $this->customers[] = $customerFixture; 73 | $customer = $this->customerRepository->getById($customerFixture->getId()); 74 | 75 | $this->assertCount( 76 | 2, 77 | $customer->getAddresses(), 78 | 'Customer should have two addresses' 79 | ); 80 | $this->assertNotEquals( 81 | $customer->getDefaultBilling(), 82 | $customer->getDefaultShipping(), 83 | 'Default shipping address should be different from default billing address' 84 | ); 85 | } 86 | 87 | /** 88 | * @magentoDataFixture Magento/Store/_files/second_store.php 89 | */ 90 | public function testCustomerWithSpecificAttributes(): void 91 | { 92 | /** @var StoreManagerInterface $storeManager */ 93 | $storeManager = $this->objectManager->get(StoreManagerInterface::class); 94 | $secondStoreId = $storeManager->getStore('fixture_second_store')->getId(); 95 | $customerFixture = new CustomerFixture( 96 | CustomerBuilder::aCustomer() 97 | ->withEmail('example@example.com') 98 | ->withGroupId(2) 99 | ->withStoreId($secondStoreId) 100 | ->withPrefix('Agent') 101 | ->withFirstname('James') 102 | ->withMiddlename('H') 103 | ->withLastname('Bond') 104 | ->withSuffix('007') 105 | ->withTaxvat('7') 106 | ->build() 107 | ); 108 | $this->customers[] = $customerFixture; 109 | $customer = $this->customerRepository->getById($customerFixture->getId()); 110 | $this->assertEquals('example@example.com', $customer->getEmail()); 111 | $this->assertEquals(2, $customer->getGroupId()); 112 | $this->assertEquals($secondStoreId, $customer->getStoreId()); 113 | $this->assertEquals('Agent', $customer->getPrefix()); 114 | $this->assertEquals('James', $customer->getFirstname()); 115 | $this->assertEquals('H', $customer->getMiddlename()); 116 | $this->assertEquals('Bond', $customer->getLastname()); 117 | $this->assertEquals('007', $customer->getSuffix()); 118 | $this->assertEquals('7', $customer->getTaxvat()); 119 | } 120 | 121 | public function testAddressWithSpecificAttributes(): void 122 | { 123 | $customerFixture = new CustomerFixture( 124 | CustomerBuilder::aCustomer() 125 | ->withAddresses( 126 | AddressBuilder::anAddress() 127 | ->withPrefix('Sir') 128 | ->withFirstname('Wasch') 129 | ->withMiddleName('H') 130 | ->withLastname('Bär') 131 | ->withSuffix('Esquire') 132 | ->withStreet('Trierer Str. 791') 133 | ->withTelephone('555-666-777') 134 | ->withCompany('integer_net') 135 | ->withCountryId('DE') 136 | ->withRegionId(88) 137 | ->withPostcode('52078') 138 | ->withCity('Aachen') 139 | ->asDefaultShipping() 140 | ->asDefaultBilling() 141 | )->build() 142 | ); 143 | $this->customers[] = $customerFixture; 144 | $customer = $this->customerRepository->getById($customerFixture->getId()); 145 | $address = $customer->getAddresses()[0]; 146 | $this->assertEquals('Sir', $address->getPrefix()); 147 | $this->assertEquals('Wasch', $address->getFirstname()); 148 | $this->assertEquals('H', $address->getMiddlename()); 149 | $this->assertEquals('Bär', $address->getLastname()); 150 | $this->assertEquals('Esquire', $address->getSuffix()); 151 | $this->assertEquals(['Trierer Str. 791'], $address->getStreet()); 152 | $this->assertEquals('555-666-777', $address->getTelephone()); 153 | $this->assertEquals('integer_net', $address->getCompany()); 154 | $this->assertEquals('DE', $address->getCountryId()); 155 | $this->assertEquals('52078', $address->getPostcode()); 156 | $this->assertEquals('Aachen', $address->getCity()); 157 | $this->assertEquals(88, $address->getRegionId()); 158 | } 159 | 160 | /** 161 | * @throws LocalizedException 162 | */ 163 | public function testLocalizedAddresses(): void 164 | { 165 | $customerFixture = new CustomerFixture( 166 | CustomerBuilder::aCustomer()->withAddresses( 167 | AddressBuilder::anAddress('de_DE')->asDefaultBilling(), 168 | AddressBuilder::anAddress('en_US')->asDefaultShipping() 169 | )->build() 170 | ); 171 | 172 | foreach ($this->customerRepository->getById($customerFixture->getId())->getAddresses() as $address) { 173 | self::assertSame($address->isDefaultBilling() ? 'DE' : 'US', $address->getCountryId()); 174 | } 175 | } 176 | 177 | /** 178 | * @throws LocalizedException 179 | */ 180 | public function testCompanyAddress(): void 181 | { 182 | $vatId = '1112223334'; 183 | $customerFixture = new CustomerFixture( 184 | CustomerBuilder::aCustomer()->withAddresses( 185 | AddressBuilder::aCompanyAddress('de_DE', $vatId)->asDefaultBilling() 186 | )->build() 187 | ); 188 | 189 | $addresses = $this->customerRepository->getById($customerFixture->getId())->getAddresses(); 190 | /** @var AddressInterface $firstAddress */ 191 | $onlyAddress = reset($addresses); 192 | self::assertSame($onlyAddress->getVatId(), $vatId); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /tests/Customer/CustomerFixturePoolTest.php: -------------------------------------------------------------------------------- 1 | customerFixtures = new CustomerFixturePool(); 30 | $this->customerRepository = Bootstrap::getObjectManager()->create(CustomerRepositoryInterface::class); 31 | } 32 | 33 | public function testLastCustomerFixtureReturnedByDefault() 34 | { 35 | $firstCustomer = $this->createCustomer(); 36 | $lastCustomer = $this->createCustomer(); 37 | $this->customerFixtures->add($firstCustomer); 38 | $this->customerFixtures->add($lastCustomer); 39 | $customerFixture = $this->customerFixtures->get(); 40 | $this->assertEquals($lastCustomer->getId(), $customerFixture->getId()); 41 | } 42 | 43 | public function testExceptionThrownWhenAccessingEmptyCustomerPool() 44 | { 45 | $this->expectException(\OutOfBoundsException::class); 46 | $this->customerFixtures->get(); 47 | } 48 | 49 | public function testCustomerFixtureReturnedByKey() 50 | { 51 | $firstCustomer = $this->createCustomer(); 52 | $lastCustomer = $this->createCustomer(); 53 | $this->customerFixtures->add($firstCustomer, 'first'); 54 | $this->customerFixtures->add($lastCustomer, 'last'); 55 | $customerFixture = $this->customerFixtures->get('first'); 56 | $this->assertEquals($firstCustomer->getId(), $customerFixture->getId()); 57 | } 58 | 59 | public function testExceptionThrownWhenAccessingNonexistingKey() 60 | { 61 | $customer = $this->createCustomer(); 62 | $this->customerFixtures->add($customer, 'foo'); 63 | $this->expectException(\OutOfBoundsException::class); 64 | $this->customerFixtures->get('bar'); 65 | } 66 | 67 | public function testRollbackRemovesCustomersFromPool() 68 | { 69 | $customer = $this->createCustomerInDb(); 70 | $this->customerFixtures->add($customer); 71 | $this->customerFixtures->rollback(); 72 | $this->expectException(\OutOfBoundsException::class); 73 | $this->customerFixtures->get(); 74 | } 75 | public function testRollbackDeletesCustomersFromDb() 76 | { 77 | $customer = $this->createCustomerInDb(); 78 | $this->customerFixtures->add($customer); 79 | $this->customerFixtures->rollback(); 80 | $this->expectException(NoSuchEntityException::class); 81 | $this->customerRepository->get($customer->getId()); 82 | } 83 | 84 | /** 85 | * Creates a dummy customer object 86 | * 87 | * @return CustomerInterface 88 | * @throws \Magento\Framework\Exception\LocalizedException 89 | */ 90 | private function createCustomer(): CustomerInterface 91 | { 92 | static $nextId = 1; 93 | /** @var CustomerInterface $customer */ 94 | $customer = Bootstrap::getObjectManager()->create(CustomerInterface::class); 95 | $customer->setId($nextId++); 96 | return $customer; 97 | } 98 | 99 | /** 100 | * Uses builder to create a customer 101 | * 102 | * @return CustomerInterface 103 | * @throws \Magento\Framework\Exception\LocalizedException 104 | */ 105 | private function createCustomerInDb(): CustomerInterface 106 | { 107 | return CustomerBuilder::aCustomer()->build(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /tests/Customer/CustomerFixtureRollbackTest.php: -------------------------------------------------------------------------------- 1 | customerRepository = Bootstrap::getObjectManager()->create(CustomerRepositoryInterface::class); 24 | } 25 | 26 | public function testRollbackSingleCustomerFixture() 27 | { 28 | $customerFixture = new CustomerFixture( 29 | CustomerBuilder::aCustomer()->build() 30 | ); 31 | CustomerFixtureRollback::create()->execute($customerFixture); 32 | $this->expectException(NoSuchEntityException::class); 33 | $this->customerRepository->getById($customerFixture->getId()); 34 | } 35 | 36 | public function testRollbackMultipleCustomerFixtures() 37 | { 38 | $customerFixture = new CustomerFixture( 39 | CustomerBuilder::aCustomer()->build() 40 | ); 41 | $otherCustomerFixture = new CustomerFixture( 42 | CustomerBuilder::aCustomer()->build() 43 | ); 44 | CustomerFixtureRollback::create()->execute($customerFixture, $otherCustomerFixture); 45 | $customerDeleted = false; 46 | try { 47 | $this->customerRepository->getById($customerFixture->getId()); 48 | } catch (NoSuchEntityException $e) { 49 | $customerDeleted = true; 50 | } 51 | $this->assertTrue($customerDeleted, 'First customer should be deleted'); 52 | $this->expectException(NoSuchEntityException::class); 53 | $this->customerRepository->getById($otherCustomerFixture->getId()); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/Sales/CreditmemoBuilderTest.php: -------------------------------------------------------------------------------- 1 | creditmemoRepository = Bootstrap::getObjectManager()->create(CreditmemoRepositoryInterface::class); 35 | } 36 | 37 | /** 38 | * @throws LocalizedException 39 | */ 40 | protected function tearDown(): void 41 | { 42 | OrderFixtureRollback::create()->execute($this->orderFixture); 43 | 44 | parent::tearDown(); 45 | } 46 | 47 | /** 48 | * Create a credit memo for all the order's items. 49 | * 50 | * @test 51 | * 52 | * @throws \Exception 53 | */ 54 | public function createCreditmemo() 55 | { 56 | $order = OrderBuilder::anOrder()->withPaymentMethod('checkmo')->build(); 57 | $this->orderFixture = new OrderFixture($order); 58 | 59 | $refundFixture = new CreditmemoFixture(CreditmemoBuilder::forOrder($order)->build()); 60 | 61 | self::assertInstanceOf(CreditmemoInterface::class, $this->creditmemoRepository->get($refundFixture->getId())); 62 | self::assertFalse($order->canCreditmemo()); 63 | } 64 | 65 | /** 66 | * Create a credit memo for some of the order's items. 67 | * 68 | * @test 69 | * @throws \Exception 70 | */ 71 | public function createPartialCreditmemos() 72 | { 73 | $order = OrderBuilder::anOrder()->withPaymentMethod('checkmo')->withProducts( 74 | ProductBuilder::aSimpleProduct()->withSku('foo'), 75 | ProductBuilder::aSimpleProduct()->withSku('bar') 76 | )->withCart( 77 | CartBuilder::forCurrentSession() 78 | ->withSimpleProduct('foo', 2) 79 | ->withSimpleProduct('bar', 3) 80 | )->build(); 81 | $this->orderFixture = new OrderFixture($order); 82 | 83 | $orderItemIds = []; 84 | /** @var OrderItemInterface $orderItem */ 85 | foreach ($order->getAllVisibleItems() as $orderItem) { 86 | $orderItemIds[$orderItem->getSku()] = $orderItem->getItemId(); 87 | } 88 | 89 | $refundFixture = new CreditmemoFixture( 90 | CreditmemoBuilder::forOrder($order) 91 | ->withItem($orderItemIds['foo'], 2) 92 | ->withItem($orderItemIds['bar'], 2) 93 | ->build() 94 | ); 95 | 96 | self::assertInstanceOf(CreditmemoInterface::class, $this->creditmemoRepository->get($refundFixture->getId())); 97 | self::assertTrue($order->canCreditmemo()); 98 | 99 | $refundFixture = new CreditmemoFixture( 100 | CreditmemoBuilder::forOrder($order) 101 | ->withItem($orderItemIds['bar'], 1) 102 | ->build() 103 | ); 104 | 105 | self::assertInstanceOf(CreditmemoInterface::class, $this->creditmemoRepository->get($refundFixture->getId())); 106 | self::assertFalse($order->canCreditmemo()); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tests/Sales/CreditmemoFixturePoolTest.php: -------------------------------------------------------------------------------- 1 | creditmemoFixtures = new CreditmemoFixturePool(); 29 | $this->creditmemoRepository = Bootstrap::getObjectManager()->create(CreditmemoRepositoryInterface::class); 30 | } 31 | 32 | public function testLastCreditmemoFixtureReturnedByDefault() 33 | { 34 | $firstCreditmemo = $this->createCreditmemo(); 35 | $lastCreditmemo = $this->createCreditmemo(); 36 | $this->creditmemoFixtures->add($firstCreditmemo); 37 | $this->creditmemoFixtures->add($lastCreditmemo); 38 | $creditmemoFixture = $this->creditmemoFixtures->get(); 39 | $this->assertEquals($lastCreditmemo->getEntityId(), $creditmemoFixture->getId()); 40 | } 41 | 42 | public function testExceptionThrownWhenAccessingEmptyCreditmemoPool() 43 | { 44 | $this->expectException(\OutOfBoundsException::class); 45 | $this->creditmemoFixtures->get(); 46 | } 47 | 48 | public function testCreditmemoFixtureReturnedByKey() 49 | { 50 | $firstCreditmemo = $this->createCreditmemo(); 51 | $lastCreditmemo = $this->createCreditmemo(); 52 | $this->creditmemoFixtures->add($firstCreditmemo, 'first'); 53 | $this->creditmemoFixtures->add($lastCreditmemo, 'last'); 54 | $creditmemoFixture = $this->creditmemoFixtures->get('first'); 55 | $this->assertEquals($firstCreditmemo->getEntityId(), $creditmemoFixture->getId()); 56 | } 57 | 58 | public function testExceptionThrownWhenAccessingNonexistingKey() 59 | { 60 | $creditmemo = $this->createCreditmemo(); 61 | $this->creditmemoFixtures->add($creditmemo, 'foo'); 62 | $this->expectException(\OutOfBoundsException::class); 63 | $this->creditmemoFixtures->get('bar'); 64 | } 65 | 66 | /** 67 | * @return CreditmemoInterface 68 | * @throws \Exception 69 | */ 70 | private function createCreditmemo(): CreditmemoInterface 71 | { 72 | static $nextId = 1; 73 | /** @var CreditmemoInterface $creditmemo */ 74 | $creditmemo = Bootstrap::getObjectManager()->create(CreditmemoInterface::class); 75 | $creditmemo->setEntityId($nextId++); 76 | return $creditmemo; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tests/Sales/InvoiceBuilderTest.php: -------------------------------------------------------------------------------- 1 | invoiceRepository = Bootstrap::getObjectManager()->create(InvoiceRepositoryInterface::class); 35 | } 36 | 37 | /** 38 | * @throws LocalizedException 39 | */ 40 | protected function tearDown(): void 41 | { 42 | OrderFixtureRollback::create()->execute($this->orderFixture); 43 | 44 | parent::tearDown(); 45 | } 46 | 47 | /** 48 | * Create a invoice for all the order's items. 49 | * 50 | * @test 51 | * 52 | * @throws \Exception 53 | */ 54 | public function createInvoice() 55 | { 56 | $order = OrderBuilder::anOrder()->build(); 57 | $this->orderFixture = new OrderFixture($order); 58 | 59 | $invoiceFixture = new InvoiceFixture(InvoiceBuilder::forOrder($order)->build()); 60 | 61 | self::assertInstanceOf(InvoiceInterface::class, $this->invoiceRepository->get($invoiceFixture->getId())); 62 | self::assertFalse($order->canInvoice()); 63 | } 64 | 65 | /** 66 | * Create an invoice for some of the order's items. 67 | * 68 | * @test 69 | * @throws \Exception 70 | */ 71 | public function createPartialInvoices() 72 | { 73 | $order = OrderBuilder::anOrder()->withProducts( 74 | ProductBuilder::aSimpleProduct()->withSku('foo'), 75 | ProductBuilder::aSimpleProduct()->withSku('bar') 76 | )->withCart( 77 | CartBuilder::forCurrentSession() 78 | ->withSimpleProduct('foo', 2) 79 | ->withSimpleProduct('bar', 3) 80 | )->build(); 81 | $this->orderFixture = new OrderFixture($order); 82 | 83 | $orderItemIds = []; 84 | /** @var OrderItemInterface $orderItem */ 85 | foreach ($order->getAllVisibleItems() as $orderItem) { 86 | $orderItemIds[$orderItem->getSku()] = $orderItem->getItemId(); 87 | } 88 | 89 | $invoiceFixture = new InvoiceFixture( 90 | InvoiceBuilder::forOrder($order) 91 | ->withItem($orderItemIds['foo'], 2) 92 | ->withItem($orderItemIds['bar'], 2) 93 | ->build() 94 | ); 95 | 96 | self::assertInstanceOf(InvoiceInterface::class, $this->invoiceRepository->get($invoiceFixture->getId())); 97 | self::assertTrue($order->canInvoice()); 98 | 99 | $invoiceFixture = new InvoiceFixture( 100 | InvoiceBuilder::forOrder($order) 101 | ->withItem($orderItemIds['bar'], 1) 102 | ->build() 103 | ); 104 | 105 | self::assertInstanceOf(InvoiceInterface::class, $this->invoiceRepository->get($invoiceFixture->getId())); 106 | self::assertFalse($order->canInvoice()); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tests/Sales/InvoiceFixturePoolTest.php: -------------------------------------------------------------------------------- 1 | invoiceFixtures = new InvoiceFixturePool(); 29 | $this->invoiceRepository = Bootstrap::getObjectManager()->create(InvoiceRepositoryInterface::class); 30 | } 31 | 32 | public function testLastInvoiceFixtureReturnedByDefault() 33 | { 34 | $firstInvoice = $this->createInvoice(); 35 | $lastInvoice = $this->createInvoice(); 36 | $this->invoiceFixtures->add($firstInvoice); 37 | $this->invoiceFixtures->add($lastInvoice); 38 | $invoiceFixture = $this->invoiceFixtures->get(); 39 | $this->assertEquals($lastInvoice->getEntityId(), $invoiceFixture->getId()); 40 | } 41 | 42 | public function testExceptionThrownWhenAccessingEmptyInvoicePool() 43 | { 44 | $this->expectException(\OutOfBoundsException::class); 45 | $this->invoiceFixtures->get(); 46 | } 47 | 48 | public function testInvoiceFixtureReturnedByKey() 49 | { 50 | $firstInvoice = $this->createInvoice(); 51 | $lastInvoice = $this->createInvoice(); 52 | $this->invoiceFixtures->add($firstInvoice, 'first'); 53 | $this->invoiceFixtures->add($lastInvoice, 'last'); 54 | $invoiceFixture = $this->invoiceFixtures->get('first'); 55 | $this->assertEquals($firstInvoice->getEntityId(), $invoiceFixture->getId()); 56 | } 57 | 58 | public function testExceptionThrownWhenAccessingNonexistingKey() 59 | { 60 | $invoice = $this->createInvoice(); 61 | $this->invoiceFixtures->add($invoice, 'foo'); 62 | $this->expectException(\OutOfBoundsException::class); 63 | $this->invoiceFixtures->get('bar'); 64 | } 65 | 66 | /** 67 | * @return InvoiceInterface 68 | * @throws \Exception 69 | */ 70 | private function createInvoice(): InvoiceInterface 71 | { 72 | static $nextId = 1; 73 | /** @var InvoiceInterface $invoice */ 74 | $invoice = Bootstrap::getObjectManager()->create(InvoiceInterface::class); 75 | $invoice->setEntityId($nextId++); 76 | return $invoice; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tests/Sales/OrderBuilderTest.php: -------------------------------------------------------------------------------- 1 | orderRepository = Bootstrap::getObjectManager()->create(OrderRepositoryInterface::class); 36 | } 37 | 38 | /** 39 | * @throws LocalizedException 40 | */ 41 | protected function tearDown(): void 42 | { 43 | OrderFixtureRollback::create()->execute(...$this->orderFixtures); 44 | 45 | parent::tearDown(); 46 | } 47 | 48 | /** 49 | * Create an order for an internally generated customer and internally generated product(s). 50 | * 51 | * Easy to set up, least flexible. 52 | * 53 | * @test 54 | * @throws \Exception 55 | */ 56 | public function createOrder() 57 | { 58 | $orderFixture = new OrderFixture( 59 | OrderBuilder::anOrder()->build() 60 | ); 61 | $this->orderFixtures[] = $orderFixture; 62 | 63 | self::assertInstanceOf(OrderInterface::class, $this->orderRepository->get($orderFixture->getId())); 64 | self::assertNotEmpty($orderFixture->getOrderItemQtys()); 65 | } 66 | 67 | /** 68 | * Create an order for an internally generated customer. 69 | * 70 | * Control the product included with the order, use random item quantities. 71 | * 72 | * @test 73 | * @throws \Exception 74 | */ 75 | public function createOrderWithProduct() 76 | { 77 | $orderFixture = new OrderFixture( 78 | OrderBuilder::anOrder()->withProducts(ProductBuilder::aSimpleProduct())->build() 79 | ); 80 | $this->orderFixtures[] = $orderFixture; 81 | 82 | self::assertInstanceOf(OrderInterface::class, $this->orderRepository->get($orderFixture->getId())); 83 | self::assertCount(1, $orderFixture->getOrderItemQtys()); 84 | } 85 | 86 | /** 87 | * Create an order for an internally generated customer with multiple products. 88 | * 89 | * Control the products included with the order, use random item quantities. 90 | * 91 | * @test 92 | * @throws \Exception 93 | */ 94 | public function createOrderWithProducts() 95 | { 96 | $orderFixture = new OrderFixture( 97 | OrderBuilder::anOrder()->withProducts( 98 | ProductBuilder::aSimpleProduct()->withSku('foo'), 99 | ProductBuilder::aSimpleProduct()->withSku('bar') 100 | )->build() 101 | ); 102 | $this->orderFixtures[] = $orderFixture; 103 | 104 | self::assertInstanceOf(OrderInterface::class, $this->orderRepository->get($orderFixture->getId())); 105 | self::assertCount(2, $orderFixture->getOrderItemQtys()); 106 | } 107 | 108 | /** 109 | * Create an order for a given customer with internally generated product(s). 110 | * 111 | * Control the customer placing the order. 112 | * 113 | * @test 114 | * @throws \Exception 115 | */ 116 | public function createOrderWithCustomer() 117 | { 118 | $customerEmail = 'test@example.com'; 119 | $customerBuilder = CustomerBuilder::aCustomer() 120 | ->withEmail($customerEmail) 121 | ->withAddresses(AddressBuilder::anAddress()->asDefaultBilling()->asDefaultShipping()); 122 | 123 | $orderFixture = new OrderFixture( 124 | OrderBuilder::anOrder()->withCustomer($customerBuilder)->build() 125 | ); 126 | $this->orderFixtures[] = $orderFixture; 127 | 128 | self::assertInstanceOf(OrderInterface::class, $this->orderRepository->get($orderFixture->getId())); 129 | self::assertSame($customerEmail, $orderFixture->getCustomerEmail()); 130 | self::assertNotEmpty($orderFixture->getOrderItemQtys()); 131 | } 132 | 133 | /** 134 | * Create an order for a given cart. 135 | * 136 | * Complex to set up, most flexible: 137 | * - define products 138 | * - define customer 139 | * - set item quantities 140 | * - set payment and shipping method 141 | * 142 | * @test 143 | * @throws \Exception 144 | */ 145 | public function createOrderWithCart() 146 | { 147 | $cartItems = ['foo' => 2, 'bar' => 3]; 148 | $customerEmail = 'test@example.com'; 149 | $paymentMethod = 'checkmo'; 150 | $shippingMethod = 'flatrate_flatrate'; 151 | 152 | $productBuilders = []; 153 | foreach ($cartItems as $sku => $qty) { 154 | $productBuilders[] = ProductBuilder::aSimpleProduct()->withSku($sku); 155 | } 156 | 157 | $customerBuilder = CustomerBuilder::aCustomer(); 158 | $customerBuilder = $customerBuilder 159 | ->withEmail($customerEmail) 160 | ->withAddresses(AddressBuilder::anAddress()->asDefaultBilling()->asDefaultShipping()); 161 | 162 | $cartBuilder = CartBuilder::forCurrentSession(); 163 | foreach ($cartItems as $sku => $qty) { 164 | $cartBuilder = $cartBuilder->withSimpleProduct($sku, $qty); 165 | } 166 | 167 | $orderFixture = new OrderFixture( 168 | OrderBuilder::anOrder() 169 | ->withProducts(...$productBuilders) 170 | ->withCustomer($customerBuilder) 171 | ->withCart($cartBuilder) 172 | ->withPaymentMethod($paymentMethod)->withShippingMethod($shippingMethod) 173 | ->build() 174 | ); 175 | $this->orderFixtures[] = $orderFixture; 176 | 177 | self::assertInstanceOf(OrderInterface::class, $this->orderRepository->get($orderFixture->getId())); 178 | self::assertSame($customerEmail, $orderFixture->getCustomerEmail()); 179 | self::assertEmpty(array_diff($cartItems, $orderFixture->getOrderItemQtys())); 180 | self::assertSame($paymentMethod, $orderFixture->getPaymentMethod()); 181 | self::assertSame($shippingMethod, $orderFixture->getShippingMethod()); 182 | } 183 | 184 | /** 185 | * Create multiple orders. Assert all of them were successfully built. 186 | * 187 | * @test 188 | * @throws \Exception 189 | */ 190 | public function createMultipleOrders() 191 | { 192 | $shippingMethod = 'flatrate_flatrate'; 193 | 194 | // first order, simple 195 | $orderFixture = new OrderFixture( 196 | OrderBuilder::anOrder() 197 | ->withShippingMethod($shippingMethod) 198 | ->build() 199 | ); 200 | $this->orderFixtures[] = $orderFixture; 201 | 202 | // second order, with specified cart 203 | $cartBuilder = CartBuilder::forCurrentSession(); 204 | $orderWithCartFixture = new OrderFixture( 205 | OrderBuilder::anOrder() 206 | ->withShippingMethod($shippingMethod) 207 | ->withProducts(ProductBuilder::aSimpleProduct()->withSku('bar')) 208 | ->withCart($cartBuilder->withSimpleProduct('bar', 3)) 209 | ->build() 210 | ); 211 | $this->orderFixtures[] = $orderWithCartFixture; 212 | 213 | // third order, with specified customer 214 | $orderWithCustomerFixture = new OrderFixture( 215 | OrderBuilder::anOrder() 216 | ->withShippingMethod($shippingMethod) 217 | ->withCustomer( 218 | CustomerBuilder::aCustomer() 219 | ->withAddresses( 220 | AddressBuilder::anAddress('de_AT') 221 | ->asDefaultBilling() 222 | ->asDefaultShipping() 223 | ) 224 | ) 225 | ->build() 226 | ); 227 | $this->orderFixtures[] = $orderWithCustomerFixture; 228 | 229 | // assert all fixtures were created with separate customers. 230 | self::assertCount(3, $this->orderFixtures); 231 | self::assertContainsOnlyInstancesOf(OrderFixture::class, $this->orderFixtures); 232 | 233 | $customerIds[$orderFixture->getCustomerId()] = 1; 234 | $customerIds[$orderWithCartFixture->getCustomerId()] = 1; 235 | $customerIds[$orderWithCustomerFixture->getCustomerId()] = 1; 236 | self::assertCount(3, $customerIds); 237 | } 238 | 239 | /** 240 | * Create orders for faker addresses with either state or province. Assert both types have a `region_id` assigned. 241 | * 242 | * @test 243 | * @throws \Exception 244 | */ 245 | public function createIntlOrders() 246 | { 247 | $atLocale = 'de_AT'; 248 | $atOrder = OrderBuilder::anOrder() 249 | ->withCustomer( 250 | CustomerBuilder::aCustomer()->withAddresses( 251 | AddressBuilder::anAddress($atLocale)->asDefaultBilling()->asDefaultShipping() 252 | ) 253 | ) 254 | ->build(); 255 | $this->orderFixtures[] = new OrderFixture($atOrder); 256 | 257 | $usLocale = 'en_US'; 258 | $usOrder = OrderBuilder::anOrder() 259 | ->withCustomer( 260 | CustomerBuilder::aCustomer()->withAddresses( 261 | AddressBuilder::anAddress($usLocale)->asDefaultBilling()->asDefaultShipping() 262 | ) 263 | ) 264 | ->build(); 265 | $this->orderFixtures[] = new OrderFixture($usOrder); 266 | 267 | $caLocale = 'en_CA'; 268 | $caOrder = OrderBuilder::anOrder() 269 | ->withCustomer( 270 | CustomerBuilder::aCustomer()->withAddresses( 271 | AddressBuilder::anAddress($caLocale)->asDefaultBilling()->asDefaultShipping() 272 | ) 273 | ) 274 | ->build(); 275 | $this->orderFixtures[] = new OrderFixture($caOrder); 276 | 277 | self::assertSame(substr($atLocale, 3, 4), $atOrder->getBillingAddress()->getCountryId()); 278 | self::assertNotEmpty($atOrder->getBillingAddress()->getRegionId()); 279 | self::assertSame(substr($usLocale, 3, 4), $usOrder->getBillingAddress()->getCountryId()); 280 | self::assertNotEmpty($usOrder->getBillingAddress()->getRegionId()); 281 | self::assertSame(substr($caLocale, 3, 4), $caOrder->getBillingAddress()->getCountryId()); 282 | self::assertNotEmpty($caOrder->getBillingAddress()->getRegionId()); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /tests/Sales/OrderFixturePoolRollbackTest.php: -------------------------------------------------------------------------------- 1 | build(); 33 | } 34 | 35 | protected function setUp(): void 36 | { 37 | $this->orderFixtures = new OrderFixturePool(); 38 | $this->orderRepository = Bootstrap::getObjectManager()->create(OrderRepositoryInterface::class); 39 | } 40 | 41 | public function testRollbackRemovesOrdersFromPool() 42 | { 43 | $this->orderFixtures->add(self::$order); 44 | $this->orderFixtures->rollback(); 45 | $this->expectException(\OutOfBoundsException::class); 46 | $this->orderFixtures->get(); 47 | } 48 | 49 | public function testRollbackWorksWithKeys() 50 | { 51 | $this->orderFixtures->add(self::$order, 'key'); 52 | $this->orderFixtures->rollback(); 53 | $this->expectException(\OutOfBoundsException::class); 54 | $this->orderFixtures->get(); 55 | } 56 | 57 | public function testRollbackDeletesOrdersFromDb() 58 | { 59 | $this->orderFixtures->add(self::$order); 60 | $this->orderFixtures->rollback(); 61 | $this->expectException(NoSuchEntityException::class); 62 | $this->orderRepository->get(self::$order->getId()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/Sales/OrderFixturePoolTest.php: -------------------------------------------------------------------------------- 1 | orderFixtures = new OrderFixturePool(); 24 | } 25 | 26 | public function testLastOrderFixtureReturnedByDefault() 27 | { 28 | $firstOrder = $this->createOrder(); 29 | $lastOrder = $this->createOrder(); 30 | $this->orderFixtures->add($firstOrder); 31 | $this->orderFixtures->add($lastOrder); 32 | $orderFixture = $this->orderFixtures->get(); 33 | $this->assertEquals($lastOrder->getId(), $orderFixture->getId()); 34 | } 35 | 36 | public function testExceptionThrownWhenAccessingEmptyOrderPool() 37 | { 38 | $this->expectException(\OutOfBoundsException::class); 39 | $this->orderFixtures->get(); 40 | } 41 | 42 | public function testOrderFixtureReturnedByKey() 43 | { 44 | $firstOrder = $this->createOrder(); 45 | $lastOrder = $this->createOrder(); 46 | $this->orderFixtures->add($firstOrder, 'first'); 47 | $this->orderFixtures->add($lastOrder, 'last'); 48 | $orderFixture = $this->orderFixtures->get('first'); 49 | $this->assertEquals($firstOrder->getId(), $orderFixture->getId()); 50 | } 51 | 52 | public function testExceptionThrownWhenAccessingNonexistingKey() 53 | { 54 | $order = $this->createOrder(); 55 | $this->orderFixtures->add($order, 'foo'); 56 | $this->expectException(\OutOfBoundsException::class); 57 | $this->orderFixtures->get('bar'); 58 | } 59 | 60 | /** 61 | * @return Order 62 | * @throws \Exception 63 | */ 64 | private function createOrder(): Order 65 | { 66 | static $nextId = 1; 67 | /** @var Order $order */ 68 | $order = Bootstrap::getObjectManager()->create(Order::class); 69 | $order->setId($nextId++); 70 | return $order; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tests/Sales/ShipmentBuilderTest.php: -------------------------------------------------------------------------------- 1 | shipmentRepository = Bootstrap::getObjectManager()->create(ShipmentRepositoryInterface::class); 36 | } 37 | 38 | /** 39 | * @throws LocalizedException 40 | */ 41 | protected function tearDown(): void 42 | { 43 | OrderFixtureRollback::create()->execute($this->orderFixture); 44 | 45 | parent::tearDown(); 46 | } 47 | 48 | /** 49 | * Create a shipment for all the order's items. 50 | * 51 | * @test 52 | * 53 | * @throws \Exception 54 | */ 55 | public function createShipment() 56 | { 57 | $order = OrderBuilder::anOrder()->build(); 58 | $this->orderFixture = new OrderFixture($order); 59 | 60 | $shipmentFixture = new ShipmentFixture(ShipmentBuilder::forOrder($order)->build()); 61 | 62 | self::assertInstanceOf(ShipmentInterface::class, $this->shipmentRepository->get($shipmentFixture->getId())); 63 | self::assertEmpty($shipmentFixture->getShippingLabel()); 64 | self::assertEmpty($shipmentFixture->getTracks()); 65 | self::assertFalse($order->canShip()); 66 | } 67 | 68 | /** 69 | * Create a shipment for all the order's items with tracks and shipping label. 70 | * 71 | * @test 72 | * 73 | * @throws \Exception 74 | */ 75 | public function createShipmentWithTracks() 76 | { 77 | $order = OrderBuilder::anOrder()->build(); 78 | $this->orderFixture = new OrderFixture($order); 79 | 80 | $shipmentFixture = new ShipmentFixture( 81 | ShipmentBuilder::forOrder($order)->withTrackingNumbers('123456', '987654', 'abcdef')->build() 82 | ); 83 | 84 | self::assertInstanceOf(ShipmentInterface::class, $this->shipmentRepository->get($shipmentFixture->getId())); 85 | 86 | self::assertNotEmpty($shipmentFixture->getShippingLabel()); 87 | self::assertNotEmpty($shipmentFixture->getTracks()); 88 | self::assertContainsOnlyInstancesOf(ShipmentTrackInterface::class, $shipmentFixture->getTracks()); 89 | self::assertCount(3, $shipmentFixture->getTracks()); 90 | self::assertFalse($order->canShip()); 91 | } 92 | 93 | /** 94 | * Create a shipment for some of the order's items. 95 | * 96 | * @test 97 | * @throws \Exception 98 | */ 99 | public function createPartialShipments() 100 | { 101 | $order = OrderBuilder::anOrder()->withProducts( 102 | ProductBuilder::aSimpleProduct()->withSku('foo'), 103 | ProductBuilder::aSimpleProduct()->withSku('bar') 104 | )->withCart( 105 | CartBuilder::forCurrentSession() 106 | ->withSimpleProduct('foo', 2) 107 | ->withSimpleProduct('bar', 3) 108 | )->build(); 109 | $this->orderFixture = new OrderFixture($order); 110 | 111 | $orderItemIds = []; 112 | /** @var OrderItemInterface $orderItem */ 113 | foreach ($order->getAllVisibleItems() as $orderItem) { 114 | $orderItemIds[$orderItem->getSku()] = $orderItem->getItemId(); 115 | } 116 | 117 | $shipmentFixture = new ShipmentFixture( 118 | ShipmentBuilder::forOrder($order) 119 | ->withItem($orderItemIds['foo'], 2) 120 | ->withItem($orderItemIds['bar'], 2) 121 | ->build() 122 | ); 123 | 124 | self::assertInstanceOf(ShipmentInterface::class, $this->shipmentRepository->get($shipmentFixture->getId())); 125 | self::assertTrue($order->canShip()); 126 | 127 | $shipmentFixture = new ShipmentFixture( 128 | ShipmentBuilder::forOrder($order) 129 | ->withItem($orderItemIds['bar'], 1) 130 | ->build() 131 | ); 132 | 133 | self::assertInstanceOf(ShipmentInterface::class, $this->shipmentRepository->get($shipmentFixture->getId())); 134 | self::assertFalse($order->canShip()); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /tests/Sales/ShipmentFixturePoolTest.php: -------------------------------------------------------------------------------- 1 | shipmentFixtures = new ShipmentFixturePool(); 29 | $this->shipmentRepository = Bootstrap::getObjectManager()->create(ShipmentRepositoryInterface::class); 30 | } 31 | 32 | public function testLastShipmentFixtureReturnedByDefault() 33 | { 34 | $firstShipment = $this->createShipment(); 35 | $lastShipment = $this->createShipment(); 36 | $this->shipmentFixtures->add($firstShipment); 37 | $this->shipmentFixtures->add($lastShipment); 38 | $shipmentFixture = $this->shipmentFixtures->get(); 39 | $this->assertEquals($lastShipment->getEntityId(), $shipmentFixture->getId()); 40 | } 41 | 42 | public function testExceptionThrownWhenAccessingEmptyShipmentPool() 43 | { 44 | $this->expectException(\OutOfBoundsException::class); 45 | $this->shipmentFixtures->get(); 46 | } 47 | 48 | public function testShipmentFixtureReturnedByKey() 49 | { 50 | $firstShipment = $this->createShipment(); 51 | $lastShipment = $this->createShipment(); 52 | $this->shipmentFixtures->add($firstShipment, 'first'); 53 | $this->shipmentFixtures->add($lastShipment, 'last'); 54 | $shipmentFixture = $this->shipmentFixtures->get('first'); 55 | $this->assertEquals($firstShipment->getEntityId(), $shipmentFixture->getId()); 56 | } 57 | 58 | public function testExceptionThrownWhenAccessingNonexistingKey() 59 | { 60 | $shipment = $this->createShipment(); 61 | $this->shipmentFixtures->add($shipment, 'foo'); 62 | $this->expectException(\OutOfBoundsException::class); 63 | $this->shipmentFixtures->get('bar'); 64 | } 65 | 66 | /** 67 | * @return ShipmentInterface 68 | * @throws \Exception 69 | */ 70 | private function createShipment(): ShipmentInterface 71 | { 72 | static $nextId = 1; 73 | /** @var ShipmentInterface $shipment */ 74 | $shipment = Bootstrap::getObjectManager()->create(ShipmentInterface::class); 75 | $shipment->setEntityId($nextId++); 76 | return $shipment; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tests/Theme/ThemeFixtureTest.php: -------------------------------------------------------------------------------- 1 | get(DesignInterface::class); 22 | $this->assertEquals('Magento/blank', $design->getDesignTheme()->getCode()); 23 | } 24 | 25 | public function testCanUseTestThemeAfterRegistering() 26 | { 27 | ThemeFixture::registerTestThemes(); 28 | ThemeFixture::setCurrentTheme('Custom/default'); 29 | /** @var DesignInterface $design */ 30 | $design = Bootstrap::getObjectManager()->get(DesignInterface::class); 31 | $this->assertEquals('Custom/default', $design->getDesignTheme()->getCode()); 32 | $this->assertGreaterThan(0, $design->getDesignTheme()->getId()); 33 | $this->assertEquals('Magento/blank', $design->getDesignTheme()->getParentTheme()->getCode()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Theme/_files/design/frontend/Custom/default/registration.php: -------------------------------------------------------------------------------- 1 | 9 | 10 | Test theme 11 | Magento/blank 12 | 13 | -------------------------------------------------------------------------------- /tests/install-config-mysql.php: -------------------------------------------------------------------------------- 1 | 'DB_HOST', 5 | 'db-user' => 'DB_USER', 6 | 'db-password' => 'DB_PASSWORD', 7 | 'db-name' => 'DB_NAME', 8 | 'db-prefix' => '', 9 | 'backend-frontname' => 'backend', 10 | 'admin-user' => \Magento\TestFramework\Bootstrap::ADMIN_NAME, 11 | 'admin-password' => \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD, 12 | 'admin-email' => \Magento\TestFramework\Bootstrap::ADMIN_EMAIL, 13 | 'admin-firstname' => \Magento\TestFramework\Bootstrap::ADMIN_FIRSTNAME, 14 | 'admin-lastname' => \Magento\TestFramework\Bootstrap::ADMIN_LASTNAME, 15 | 'amqp-host' => '', 16 | 'amqp-port' => '', 17 | 'amqp-user' => '', 18 | 'amqp-password' => '', 19 | ]; 20 | -------------------------------------------------------------------------------- /tests/phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 7 | 15 | 16 | 17 | ../../../vendor/tddwizard/magento2-fixtures/tests 18 | 19 | 20 | 21 | 22 | 23 | ../../../vendor/tddwizard/magento2-fixtures/src 24 | 25 | 26 | 27 | 28 | . 29 | testsuite 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | --------------------------------------------------------------------------------