diff --git a/app/code/Magento/Bundle/composer.json b/app/code/Magento/Bundle/composer.json index aa2b9e12f7701..84c9a97698b71 100644 --- a/app/code/Magento/Bundle/composer.json +++ b/app/code/Magento/Bundle/composer.json @@ -25,7 +25,8 @@ }, "suggest": { "magento/module-webapi": "*", - "magento/module-bundle-sample-data": "*" + "magento/module-bundle-sample-data": "*", + "magento/module-sales-rule": "*" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/Bundle/etc/di.xml b/app/code/Magento/Bundle/etc/di.xml index b7fba3937ded4..733b089dccd4b 100644 --- a/app/code/Magento/Bundle/etc/di.xml +++ b/app/code/Magento/Bundle/etc/di.xml @@ -207,4 +207,11 @@ + + + + false + + + diff --git a/app/code/Magento/Captcha/Test/Unit/Observer/CheckRegisterCheckoutObserverTest.php b/app/code/Magento/Captcha/Test/Unit/Observer/CheckRegisterCheckoutObserverTest.php new file mode 100644 index 0000000000000..89012ef653838 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Unit/Observer/CheckRegisterCheckoutObserverTest.php @@ -0,0 +1,211 @@ +createMock(Onepage::class); + $captchaHelperMock = $this->createMock(CaptchaDataHelper::class); + $this->objectManager = new ObjectManager($this); + $this->actionFlagMock = $this->createMock(ActionFlag::class); + $this->captchaStringResolverMock = $this->createMock(CaptchaStringResolver::class); + $this->captchaModelMock = $this->createMock(CaptchaModel::class); + $this->quoteModelMock = $this->createMock(Quote::class); + $this->controllerMock = $this->createMock(Action::class); + $this->requestMock = $this->createMock(Http::class); + $this->responseMock = $this->createMock(HttpResponse::class); + $this->observer = new Observer(['controller_action' => $this->controllerMock]); + $this->jsonHelperMock = $this->createMock(JsonHelper::class); + + $this->checkRegisterCheckoutObserver = $this->objectManager->getObject( + CheckRegisterCheckoutObserver::class, + [ + 'helper' => $captchaHelperMock, + 'actionFlag' => $this->actionFlagMock, + 'captchaStringResolver' => $this->captchaStringResolverMock, + 'typeOnepage' => $onepageModelTypeMock, + 'jsonHelper' => $this->jsonHelperMock + ] + ); + + $captchaHelperMock->expects($this->once()) + ->method('getCaptcha') + ->with(self::FORM_ID) + ->willReturn($this->captchaModelMock); + $onepageModelTypeMock->expects($this->once()) + ->method('getQuote') + ->willReturn($this->quoteModelMock); + } + + public function testCheckRegisterCheckoutForGuest() + { + $this->quoteModelMock->expects($this->once()) + ->method('getCheckoutMethod') + ->willReturn(Onepage::METHOD_GUEST); + $this->captchaModelMock->expects($this->never()) + ->method('isRequired'); + + $this->checkRegisterCheckoutObserver->execute($this->observer); + } + + public function testCheckRegisterCheckoutWithNoCaptchaRequired() + { + $this->quoteModelMock->expects($this->once()) + ->method('getCheckoutMethod') + ->willReturn(Onepage::METHOD_REGISTER); + $this->captchaModelMock->expects($this->once()) + ->method('isRequired') + ->willReturn(false); + $this->captchaModelMock->expects($this->never()) + ->method('isCorrect'); + + $this->checkRegisterCheckoutObserver->execute($this->observer); + } + + public function testCheckRegisterCheckoutWithIncorrectCaptcha() + { + $captchaValue = 'some_word'; + $encodedJsonValue = '{}'; + + $this->quoteModelMock->expects($this->once()) + ->method('getCheckoutMethod') + ->willReturn(Onepage::METHOD_REGISTER); + $this->captchaModelMock->expects($this->once()) + ->method('isRequired') + ->willReturn(true); + $this->controllerMock->expects($this->once()) + ->method('getRequest') + ->willReturn($this->requestMock); + $this->controllerMock->expects($this->once()) + ->method('getResponse') + ->willReturn($this->responseMock); + $this->controllerMock->expects($this->once()) + ->method('getResponse') + ->willReturn($this->responseMock); + $this->captchaStringResolverMock->expects($this->once()) + ->method('resolve') + ->with($this->requestMock, self::FORM_ID) + ->willReturn($captchaValue); + $this->captchaModelMock->expects($this->once()) + ->method('isCorrect') + ->with($captchaValue) + ->willReturn(false); + $this->actionFlagMock->expects($this->once()) + ->method('set') + ->with('', Action::FLAG_NO_DISPATCH, true); + $this->jsonHelperMock->expects($this->once()) + ->method('jsonEncode') + ->willReturn($encodedJsonValue); + $this->responseMock->expects($this->once()) + ->method('representJson') + ->with($encodedJsonValue); + + $this->checkRegisterCheckoutObserver->execute($this->observer); + } + + public function testCheckRegisterCheckoutWithCorrectCaptcha() + { + $this->quoteModelMock->expects($this->once()) + ->method('getCheckoutMethod') + ->willReturn(Onepage::METHOD_REGISTER); + $this->captchaModelMock->expects($this->once()) + ->method('isRequired') + ->willReturn(true); + $this->controllerMock->expects($this->once()) + ->method('getRequest') + ->willReturn($this->requestMock); + $this->captchaStringResolverMock->expects($this->once()) + ->method('resolve') + ->with($this->requestMock, self::FORM_ID) + ->willReturn('some_word'); + $this->captchaModelMock->expects($this->once()) + ->method('isCorrect') + ->with('some_word') + ->willReturn(true); + $this->actionFlagMock->expects($this->never()) + ->method('set'); + + $this->checkRegisterCheckoutObserver->execute($this->observer); + } +} diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category.php index 13c4353e65204..1e0cb9f197a51 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category.php @@ -3,8 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Controller\Adminhtml; +use Magento\Store\Model\Store; + /** * Catalog category controller */ @@ -44,7 +48,7 @@ public function __construct( protected function _initCategory($getRootInstead = false) { $categoryId = $this->resolveCategoryId(); - $storeId = (int)$this->getRequest()->getParam('store'); + $storeId = $this->resolveStoreId(); $category = $this->_objectManager->create(\Magento\Catalog\Model\Category::class); $category->setStoreId($storeId); @@ -70,7 +74,7 @@ protected function _initCategory($getRootInstead = false) $this->_objectManager->get(\Magento\Framework\Registry::class)->register('category', $category); $this->_objectManager->get(\Magento\Framework\Registry::class)->register('current_category', $category); $this->_objectManager->get(\Magento\Cms\Model\Wysiwyg\Config::class) - ->setStoreId($this->getRequest()->getParam('store')); + ->setStoreId($storeId); return $category; } @@ -79,13 +83,28 @@ protected function _initCategory($getRootInstead = false) * * @return int */ - private function resolveCategoryId() + private function resolveCategoryId() : int { $categoryId = (int)$this->getRequest()->getParam('id', false); return $categoryId ?: (int)$this->getRequest()->getParam('entity_id', false); } + /** + * Resolve store id + * + * Tries to take store id from store HTTP parameter + * @see Store + * + * @return int + */ + private function resolveStoreId() : int + { + $storeId = (int)$this->getRequest()->getParam('store', false); + + return $storeId ?: (int)$this->getRequest()->getParam('store_id', Store::DEFAULT_STORE_ID); + } + /** * Build response for ajax request * diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php index e054a9d49b437..817de6828e48d 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php @@ -69,6 +69,7 @@ class Save extends Attribute * @var LayoutFactory */ private $layoutFactory; + /** * @var Presentation */ @@ -124,6 +125,7 @@ public function execute() { $data = $this->getRequest()->getPostValue(); if ($data) { + $this->preprocessOptionsData($data); $setId = $this->getRequest()->getParam('set'); $attributeSet = null; @@ -313,6 +315,28 @@ public function execute() return $this->returnResult('catalog/*/', [], ['error' => true]); } + /** + * Extract options data from serialized options field and append to data array. + * + * This logic is required to overcome max_input_vars php limit + * that may vary and/or be inaccessible to change on different instances. + * + * @param array $data + * @return void + */ + private function preprocessOptionsData(&$data) + { + if (isset($data['serialized_options'])) { + $serializedOptions = json_decode($data['serialized_options'], JSON_OBJECT_AS_ARRAY); + foreach ($serializedOptions as $serializedOption) { + $option = []; + parse_str($serializedOption, $option); + $data = array_replace_recursive($data, $option); + } + } + unset($data['serialized_options']); + } + /** * @param string $path * @param array $params diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/AbstractModifierTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/AbstractModifierTest.php index af10eeea42fd3..473f1aea33618 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/AbstractModifierTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/AbstractModifierTest.php @@ -63,7 +63,8 @@ protected function setUp() 'getAttributes', 'getStore', 'getAttributeDefaultValue', - 'getExistsStoreValueFlag' + 'getExistsStoreValueFlag', + 'isLockedAttribute' ])->getMockForAbstractClass(); $this->storeMock = $this->getMockBuilder(StoreInterface::class) ->setMethods(['load', 'getId', 'getConfig']) @@ -81,9 +82,6 @@ protected function setUp() $this->arrayManagerMock->expects($this->any()) ->method('set') ->willReturnArgument(1); - $this->arrayManagerMock->expects($this->any()) - ->method('merge') - ->willReturnArgument(1); $this->arrayManagerMock->expects($this->any()) ->method('remove') ->willReturnArgument(1); diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/AttributeSetTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/AttributeSetTest.php index c22dde0b456ac..5fc6231b03735 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/AttributeSetTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/AttributeSetTest.php @@ -5,6 +5,7 @@ */ namespace Magento\Catalog\Test\Unit\Ui\DataProvider\Product\Form\Modifier; +use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier; use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AttributeSet; use Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory; use Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\Collection; @@ -84,7 +85,30 @@ protected function createModel() public function testModifyMeta() { - $this->assertNotEmpty($this->getModel()->modifyMeta(['test_group' => []])); + $modifyMeta = $this->getModel()->modifyMeta(['test_group' => []]); + $this->assertNotEmpty($modifyMeta); + } + + /** + * @param bool $locked + * @dataProvider modifyMetaLockedDataProvider + */ + public function testModifyMetaLocked($locked) + { + $this->productMock->expects($this->any()) + ->method('isLockedAttribute') + ->willReturn($locked); + $modifyMeta = $this->getModel()->modifyMeta([AbstractModifier::DEFAULT_GENERAL_PANEL => []]); + $children = $modifyMeta[AbstractModifier::DEFAULT_GENERAL_PANEL]['children']; + $this->assertEquals( + $locked, + $children['attribute_set_id']['arguments']['data']['config']['disabled'] + ); + } + + public function modifyMetaLockedDataProvider() + { + return [[true], [false]]; } public function testModifyMetaToBeEmpty() diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CategoriesTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CategoriesTest.php index 4daff7e7930e3..5f5913c20209a 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CategoriesTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CategoriesTest.php @@ -114,6 +114,44 @@ public function testModifyMeta() $this->assertArrayHasKey($groupCode, $this->getModel()->modifyMeta($meta)); } + /** + * @param bool $locked + * @dataProvider modifyMetaLockedDataProvider + */ + public function testModifyMetaLocked($locked) + { + $groupCode = 'test_group_code'; + $meta = [ + $groupCode => [ + 'children' => [ + 'category_ids' => [ + 'sortOrder' => 10, + ], + ], + ], + ]; + + $this->arrayManagerMock->expects($this->any()) + ->method('findPath') + ->willReturn('path'); + + $this->productMock->expects($this->any()) + ->method('isLockedAttribute') + ->willReturn($locked); + + $this->arrayManagerMock->expects($this->any()) + ->method('merge') + ->willReturnArgument(2); + + $modifyMeta = $this->createModel()->modifyMeta($meta); + $this->assertEquals($locked, $modifyMeta['arguments']['data']['config']['disabled']); + } + + public function modifyMetaLockedDataProvider() + { + return [[true], [false]]; + } + public function testModifyMetaWithCaching() { $this->arrayManagerMock->expects($this->exactly(2)) diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php index 0426e389d9aeb..22bb712d42f0f 100755 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php @@ -462,7 +462,7 @@ public function testModifyData() * @param bool $productRequired * @param string|null $attrValue * @param array $expected - * @return void + * @param bool $locked * @covers \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Eav::isProductExists * @covers \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Eav::setupAttributeMeta * @dataProvider setupAttributeMetaDataProvider @@ -471,7 +471,8 @@ public function testSetupAttributeMetaDefaultAttribute( $productId, bool $productRequired, $attrValue, - array $expected + array $expected, + $locked = false ) : void { $configPath = 'arguments/data/config'; $groupCode = 'product-details'; @@ -492,6 +493,7 @@ public function testSetupAttributeMetaDefaultAttribute( ]; $this->productMock->method('getId')->willReturn($productId); + $this->productMock->expects($this->any())->method('isLockedAttribute')->willReturn($locked); $this->productAttributeMock->method('getIsRequired')->willReturn($productRequired); $this->productAttributeMock->method('getDefaultValue')->willReturn('required_value'); $this->productAttributeMock->method('getAttributeCode')->willReturn('code'); @@ -520,14 +522,14 @@ public function testSetupAttributeMetaDefaultAttribute( ) ->willReturn($expected); - $this->arrayManagerMock->expects($this->once()) + $this->arrayManagerMock->expects($this->any()) ->method('merge') ->with( $this->anything(), $this->anything(), $this->callback( function ($value) use ($attributeOptionsExpected) { - return $value['options'] === $attributeOptionsExpected; + return isset($value['options']) ? $value['options'] === $attributeOptionsExpected : true; } ) ) @@ -544,6 +546,7 @@ function ($value) use ($attributeOptionsExpected) { /** * @return array + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function setupAttributeMetaDataProvider() { @@ -567,6 +570,26 @@ public function setupAttributeMetaDataProvider() 'sortOrder' => 0, ], ], + 'default_null_prod_not_new_locked_and_required' => [ + 'productId' => 1, + 'productRequired' => true, + 'attrValue' => 'val', + 'expected' => [ + 'dataType' => null, + 'formElement' => null, + 'visible' => null, + 'required' => true, + 'notice' => null, + 'default' => null, + 'label' => new Phrase(null), + 'code' => 'code', + 'source' => 'product-details', + 'scopeLabel' => '', + 'globalScope' => false, + 'sortOrder' => 0, + ], + 'locked' => true, + ], 'default_null_prod_not_new_and_not_required' => [ 'productId' => 1, 'productRequired' => false, @@ -605,6 +628,26 @@ public function setupAttributeMetaDataProvider() 'sortOrder' => 0, ], ], + 'default_null_prod_new_locked_and_not_required' => [ + 'productId' => null, + 'productRequired' => false, + 'attrValue' => null, + 'expected' => [ + 'dataType' => null, + 'formElement' => null, + 'visible' => null, + 'required' => false, + 'notice' => null, + 'default' => 'required_value', + 'label' => new Phrase(null), + 'code' => 'code', + 'source' => 'product-details', + 'scopeLabel' => '', + 'globalScope' => false, + 'sortOrder' => 0, + ], + 'locked' => true, + ], 'default_null_prod_new_and_required' => [ 'productId' => null, 'productRequired' => false, diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/GeneralTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/GeneralTest.php index 78502ae297b52..a9d717db7b7f9 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/GeneralTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/GeneralTest.php @@ -60,6 +60,9 @@ protected function createModel() public function testModifyMeta() { + $this->arrayManagerMock->expects($this->any()) + ->method('merge') + ->willReturnArgument(2); $this->assertNotEmpty($this->getModel()->modifyMeta([ 'first_panel_code' => [ 'arguments' => [ diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/ScheduleDesignUpdateTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/ScheduleDesignUpdateTest.php index d4d4136bf4157..783c6247b9df3 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/ScheduleDesignUpdateTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/ScheduleDesignUpdateTest.php @@ -24,6 +24,9 @@ protected function createModel() public function testModifyMeta() { + $this->arrayManagerMock->expects($this->any()) + ->method('merge') + ->willReturnArgument(1); $this->assertSame([], $this->getModel()->modifyMeta([])); } diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/WebsitesTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/WebsitesTest.php index 997b66861c21b..c3096770729a6 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/WebsitesTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/WebsitesTest.php @@ -76,7 +76,10 @@ class WebsitesTest extends AbstractModifierTest protected function setUp() { - $this->objectManager = new ObjectManager($this); + parent::setUp(); + $this->productMock->expects($this->any()) + ->method('getId') + ->willReturn(self::PRODUCT_ID); $this->assignedWebsites = [self::SECOND_WEBSITE_ID]; $this->websiteMock = $this->getMockBuilder(\Magento\Store\Model\Website::class) ->setMethods(['getId', 'getName']) @@ -101,15 +104,9 @@ protected function setUp() $this->storeRepositoryMock = $this->getMockBuilder(\Magento\Store\Api\StoreRepositoryInterface::class) ->setMethods(['getList']) ->getMockForAbstractClass(); - $this->locatorMock = $this->getMockBuilder(\Magento\Catalog\Model\Locator\LocatorInterface::class) - ->setMethods(['getProduct', 'getWebsiteIds']) - ->getMockForAbstractClass(); $this->productMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class) ->setMethods(['getId']) ->getMockForAbstractClass(); - $this->locatorMock->expects($this->any()) - ->method('getProduct') - ->willReturn($this->productMock); $this->locatorMock->expects($this->any()) ->method('getWebsiteIds') ->willReturn($this->assignedWebsites); @@ -148,9 +145,6 @@ protected function setUp() $this->storeRepositoryMock->expects($this->any()) ->method('getList') ->willReturn([$this->storeViewMock]); - $this->productMock->expects($this->any()) - ->method('getId') - ->willReturn(self::PRODUCT_ID); $this->secondWebsiteMock->expects($this->any()) ->method('getId') ->willReturn($this->assignedWebsites[0]); diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php index 63b7f5faa6562..336aeffa10584 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php @@ -432,7 +432,8 @@ private function getTierPriceStructure($tierPricePath) 'dndConfig' => [ 'enabled' => false, ], - 'disabled' => false, + 'disabled' => + $this->arrayManager->get($tierPricePath . '/arguments/data/config/disabled', $this->meta), 'required' => false, 'sortOrder' => $this->arrayManager->get($tierPricePath . '/arguments/data/config/sortOrder', $this->meta), diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AttributeSet.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AttributeSet.php index a1aacc91f2e47..0733d21bf47d7 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AttributeSet.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AttributeSet.php @@ -108,6 +108,7 @@ public function modifyMeta(array $meta) self::ATTRIBUTE_SET_FIELD_ORDER ), 'multiple' => false, + 'disabled' => $this->locator->getProduct()->isLockedAttribute('attribute_set_id'), ]; } diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php index 7456c1bfef91f..ed737df708ab8 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php @@ -228,6 +228,7 @@ protected function customizeCategoriesField(array $meta) 'componentType' => 'container', 'component' => 'Magento_Ui/js/form/components/group', 'scopeLabel' => __('[GLOBAL]'), + 'disabled' => $this->locator->getProduct()->isLockedAttribute($fieldCode), ], ], ], diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php index 0e6f17d761bc3..7cd81419c0347 100755 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php @@ -41,6 +41,7 @@ * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.TooManyFields) + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @since 101.0.0 */ class Eav extends AbstractModifier @@ -593,6 +594,7 @@ private function isProductExists() public function setupAttributeMeta(ProductAttributeInterface $attribute, $groupCode, $sortOrder) { $configPath = ltrim(static::META_CONFIG_PATH, ArrayManager::DEFAULT_PATH_DELIMITER); + $attributeCode = $attribute->getAttributeCode(); $meta = $this->arrayManager->set($configPath, [], [ 'dataType' => $attribute->getFrontendInput(), 'formElement' => $this->getFormElementsMapValue($attribute->getFrontendInput()), @@ -601,7 +603,7 @@ public function setupAttributeMeta(ProductAttributeInterface $attribute, $groupC 'notice' => $attribute->getNote() === null ? null : __($attribute->getNote()), 'default' => (!$this->isProductExists()) ? $this->getAttributeDefaultValue($attribute) : null, 'label' => __($attribute->getDefaultFrontendLabel()), - 'code' => $attribute->getAttributeCode(), + 'code' => $attributeCode, 'source' => $groupCode, 'scopeLabel' => $this->getScopeLabel($attribute), 'globalScope' => $this->isScopeGlobal($attribute), @@ -631,7 +633,9 @@ public function setupAttributeMeta(ProductAttributeInterface $attribute, $groupC ]); } - if (in_array($attribute->getAttributeCode(), $this->attributesToDisable)) { + $product = $this->locator->getProduct(); + if (in_array($attributeCode, $this->attributesToDisable) + || $product->isLockedAttribute($attributeCode)) { $meta = $this->arrayManager->merge($configPath, $meta, [ 'disabled' => true, ]); diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php index 279b905577689..98de8ea347671 100755 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php @@ -203,7 +203,7 @@ protected function customizeStatusField(array $meta) protected function customizeWeightField(array $meta) { $weightPath = $this->arrayManager->findPath(ProductAttributeInterface::CODE_WEIGHT, $meta, null, 'children'); - + $disabled = $this->arrayManager->get($weightPath . '/arguments/data/config/disabled', $meta); if ($weightPath) { $meta = $this->arrayManager->merge( $weightPath . static::META_CONFIG_PATH, @@ -215,7 +215,7 @@ protected function customizeWeightField(array $meta) ], 'additionalClasses' => 'admin__field-small', 'addafter' => $this->locator->getStore()->getConfig('general/locale/weight_unit'), - 'imports' => [ + 'imports' => $disabled ? [] : [ 'disabled' => '!${$.provider}:' . self::DATA_SCOPE_PRODUCT . '.product_has_weight:value' ] @@ -255,6 +255,7 @@ protected function customizeWeightField(array $meta) ], ], 'value' => (int)$this->locator->getProduct()->getTypeInstance()->hasWeight(), + 'disabled' => $disabled, ] ); } diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php index 298da3d5cd6f2..bab36ce5fc4d8 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php @@ -135,7 +135,6 @@ public function modifyMeta(array $meta) 'collapsible' => true, 'componentType' => Form\Fieldset::NAME, 'dataScope' => self::DATA_SCOPE_PRODUCT, - 'disabled' => false, 'sortOrder' => $this->getNextGroupSortOrder( $meta, 'search-engine-optimization', @@ -196,6 +195,7 @@ protected function getFieldsForFieldset() 'false' => '0', ], 'value' => $isChecked ? (string)$website['id'] : '0', + 'disabled' => $this->locator->getProduct()->isLockedAttribute('website_ids'), ], ], ], diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/options.js b/app/code/Magento/Catalog/view/adminhtml/web/js/options.js index 787516a9abf29..6ea005915763c 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/js/options.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/options.js @@ -13,12 +13,16 @@ define([ 'jquery/ui', 'prototype', 'form', - 'validation' + 'validation', + 'mage/translate' ], function (jQuery, mageTemplate, rg) { 'use strict'; return function (config) { - var attributeOption = { + var optionPanel = jQuery('#manage-options-panel'), + optionsValues = [], + editForm = jQuery('#edit_form'), + attributeOption = { table: $('attribute-options-table'), itemCount: 0, totalItems: 0, @@ -150,7 +154,7 @@ define([ attributeOption.remove(event); }); - jQuery('#manage-options-panel').on('render', function () { + optionPanel.on('render', function () { attributeOption.ignoreValidate(); if (attributeOption.rendered) { @@ -176,7 +180,31 @@ define([ }); }); } + editForm.on('submit', function () { + optionPanel.find('input') + .each(function () { + if (this.disabled) { + return; + } + if (this.type === 'checkbox' || this.type === 'radio') { + if (this.checked) { + optionsValues.push(this.name + '=' + jQuery(this).val()); + } + } else { + optionsValues.push(this.name + '=' + jQuery(this).val()); + } + }); + jQuery('') + .attr({ + type: 'hidden', + name: 'serialized_options' + }) + .val(JSON.stringify(optionsValues)) + .prependTo(editForm); + optionPanel.find('table') + .replaceWith(jQuery('
').text(jQuery.mage.__('Sending attribute values as package.'))); + }); window.attributeOption = attributeOption; window.optionDefaultInputType = attributeOption.getOptionInputType(); diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/breadcrumbs.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/breadcrumbs.phtml index 528b2b5c59f23..063f8857329e5 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/breadcrumbs.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/breadcrumbs.phtml @@ -11,7 +11,7 @@ $viewModel = $block->getData('viewModel'); "breadcrumbs": { "categoryUrlSuffix": "escapeHtml($viewModel->getCategoryUrlSuffix()); ?>", "useCategoryPathInUrl": isCategoryUsedInProductUrl(); ?>, - "product": "escapeHtml($block->escapeJsQuote($viewModel->getProductName(), '"')); ?>" + "product": "escapeHtml($block->escapeJs($viewModel->getProductName())); ?>" } }'>
diff --git a/app/code/Magento/CatalogInventory/Api/Data/StockStatusInterface.php b/app/code/Magento/CatalogInventory/Api/Data/StockStatusInterface.php index 83defa64df250..c9ae6a96a3671 100644 --- a/app/code/Magento/CatalogInventory/Api/Data/StockStatusInterface.php +++ b/app/code/Magento/CatalogInventory/Api/Data/StockStatusInterface.php @@ -14,6 +14,14 @@ */ interface StockStatusInterface extends ExtensibleDataInterface { + /**#@+ + * Stock Status values. + */ + const STATUS_OUT_OF_STOCK = 0; + + const STATUS_IN_STOCK = 1; + /**#@-*/ + /**#@+ * Stock status object data keys */ diff --git a/app/code/Magento/CatalogInventory/Model/Stock/Status.php b/app/code/Magento/CatalogInventory/Model/Stock/Status.php index 9a56c8e8804ec..899056d8f0835 100644 --- a/app/code/Magento/CatalogInventory/Model/Stock/Status.php +++ b/app/code/Magento/CatalogInventory/Model/Stock/Status.php @@ -17,14 +17,6 @@ */ class Status extends AbstractExtensibleModel implements StockStatusInterface { - /**#@+ - * Stock Status values - */ - const STATUS_OUT_OF_STOCK = 0; - - const STATUS_IN_STOCK = 1; - /**#@-*/ - /**#@+ * Field name */ diff --git a/app/code/Magento/CatalogInventory/Ui/DataProvider/Product/Form/Modifier/AdvancedInventory.php b/app/code/Magento/CatalogInventory/Ui/DataProvider/Product/Form/Modifier/AdvancedInventory.php index d15f17530ffbc..7386f133b569a 100644 --- a/app/code/Magento/CatalogInventory/Ui/DataProvider/Product/Form/Modifier/AdvancedInventory.php +++ b/app/code/Magento/CatalogInventory/Ui/DataProvider/Product/Form/Modifier/AdvancedInventory.php @@ -223,6 +223,7 @@ private function prepareMeta() $this->arrayManager->slicePath($pathField, 0, -2) . '/arguments/data/config/sortOrder', $this->meta ) - 1, + 'disabled' => $this->locator->getProduct()->isLockedAttribute($fieldCode), ]; $qty['arguments']['data']['config'] = [ 'component' => 'Magento_CatalogInventory/js/components/qty-validator-changer', diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CustomOptionsTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CustomOptionsTest.php index e2e9fe9b2b1f9..659f7346faba4 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CustomOptionsTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CustomOptionsTest.php @@ -10,6 +10,14 @@ class CustomOptionsTest extends AbstractModifierTest { + protected function setUp() + { + parent::setUp(); + $this->arrayManagerMock->expects($this->any()) + ->method('merge') + ->willReturnArgument(1); + } + /** * {@inheritdoc} */ diff --git a/app/code/Magento/Customer/view/frontend/templates/form/login.phtml b/app/code/Magento/Customer/view/frontend/templates/form/login.phtml index 16206525aa53b..77e250c5de923 100644 --- a/app/code/Magento/Customer/view/frontend/templates/form/login.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/form/login.phtml @@ -42,3 +42,13 @@ + + diff --git a/app/code/Magento/Customer/view/frontend/templates/widget/name.phtml b/app/code/Magento/Customer/view/frontend/templates/widget/name.phtml index 73e9c4fa34bb3..223e43c9bb897 100644 --- a/app/code/Magento/Customer/view/frontend/templates/widget/name.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/widget/name.phtml @@ -28,9 +28,7 @@ $suffix = $block->showSuffix(); ?> getNoWrap()): ?>
- +
@@ -38,10 +36,7 @@ $suffix = $block->showSuffix();
- - +
getPrefixOptions() === false): ?> showSuffix();
- - +
showSuffix(); isMiddlenameRequired(); ?>
- - +
showSuffix();
- - +
showSuffix();
- - +
getSuffixOptions() === false): ?> scopeConfig = $scopeConfig; - $this->clientResolver = $clientResolver ?: - ObjectManager::getInstance()->get(ClientResolver::class); + $this->clientResolver = $clientResolver ?: ObjectManager::getInstance()->get(ClientResolver::class); + $this->engineResolver = $engineResolver ?: ObjectManager::getInstance()->get(EngineResolverInterface::class); $this->prefix = $prefix ?: $this->clientResolver->getCurrentEngine(); } @@ -126,7 +135,7 @@ public function getSearchConfigData($field, $storeId = null) */ public function isElasticsearchEnabled() { - return $this->getSearchConfigData('engine') == self::ENGINE_NAME; + return $this->engineResolver->getCurrentSearchEngine() === self::ENGINE_NAME; } /** diff --git a/app/code/Magento/Elasticsearch/Model/Indexer/Plugin/DependencyUpdaterPlugin.php b/app/code/Magento/Elasticsearch/Model/Indexer/Plugin/DependencyUpdaterPlugin.php new file mode 100644 index 0000000000000..f1b153c98b2af --- /dev/null +++ b/app/code/Magento/Elasticsearch/Model/Indexer/Plugin/DependencyUpdaterPlugin.php @@ -0,0 +1,85 @@ +config = $config; + } + + /** + * Remove index dependency, if it needed, on run reindexing by specifics indexes. + * + * @param Provider $provider + * @param array $dependencies + * @param string $indexerId + * @return array + * @see \Magento\Indexer\Console\Command\IndexerReindexCommand::getIndexers() + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterGetIndexerIdsToRunBefore(Provider $provider, array $dependencies, string $indexerId): array + { + if ($this->isFilteringNeeded($indexerId, CatalogSearchFulltextIndexer::INDEXER_ID)) { + $dependencies = array_diff($dependencies, [CatalogInventoryStockIndexer::INDEXER_ID]); + } + + return $dependencies; + } + + /** + * Remove index dependency, if it needed, on reindex triggers. + * + * @param Provider $provider + * @param array $dependencies + * @param string $indexerId + * @return array + * @see \Magento\Indexer\Model\Indexer\DependencyDecorator + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterGetIndexerIdsToRunAfter(Provider $provider, array $dependencies, string $indexerId): array + { + if ($this->isFilteringNeeded($indexerId, CatalogInventoryStockIndexer::INDEXER_ID)) { + $dependencies = array_diff($dependencies, [CatalogSearchFulltextIndexer::INDEXER_ID]); + } + + return $dependencies; + } + + /** + * @param string $currentIndexerId + * @param string $targetIndexerId + * @return bool + */ + private function isFilteringNeeded(string $currentIndexerId, string $targetIndexerId): bool + { + return (!$this->config->isElasticsearchEnabled() && $targetIndexerId === $currentIndexerId); + } +} diff --git a/app/code/Magento/Elasticsearch/Model/Indexer/Plugin/StockedProductsFilterPlugin.php b/app/code/Magento/Elasticsearch/Model/Indexer/Plugin/StockedProductsFilterPlugin.php new file mode 100644 index 0000000000000..ec18b955a2917 --- /dev/null +++ b/app/code/Magento/Elasticsearch/Model/Indexer/Plugin/StockedProductsFilterPlugin.php @@ -0,0 +1,94 @@ +config = $config; + $this->stockConfiguration = $stockConfiguration; + $this->stockStatusRepository = $stockStatusRepository; + $this->stockStatusCriteriaFactory = $stockStatusCriteriaFactory; + } + + /** + * Filter out of stock options for configurable product. + * + * @param DataProvider $dataProvider + * @param array $indexData + * @param array $productData + * @param int $storeId + * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforePrepareProductIndex( + DataProvider $dataProvider, + array $indexData, + array $productData, + int $storeId + ): array { + if ($this->config->isElasticsearchEnabled() && !$this->stockConfiguration->isShowOutOfStock($storeId)) { + $productIds = array_keys($indexData); + $stockStatusCriteria = $this->stockStatusCriteriaFactory->create(); + $stockStatusCriteria->setProductsFilter($productIds); + $stockStatusCollection = $this->stockStatusRepository->getList($stockStatusCriteria); + $stockStatuses = $stockStatusCollection->getItems(); + $stockStatuses = array_filter($stockStatuses, function (StockStatusInterface $stockStatus) { + return StockStatusInterface::STATUS_IN_STOCK == $stockStatus->getStockStatus(); + }); + $indexData = array_intersect_key($indexData, $stockStatuses); + } + + return [ + $indexData, + $productData, + $storeId, + ]; + } +} diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/Indexer/Plugin/DependencyUpdaterPluginTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/Indexer/Plugin/DependencyUpdaterPluginTest.php new file mode 100644 index 0000000000000..15510843b0e60 --- /dev/null +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/Indexer/Plugin/DependencyUpdaterPluginTest.php @@ -0,0 +1,87 @@ +configMock = $this->getMockBuilder(Config::class) + ->disableOriginalConstructor() + ->getMock(); + $this->configMock->expects($this->exactly(2)) + ->method('isElasticsearchEnabled') + ->willReturnOnConsecutiveCalls(true, false); + $this->providerMock = $this->getMockBuilder(DependencyInfoProvider::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->plugin = new DependencyUpdaterPlugin($this->configMock); + } + + /** + * @return void + */ + public function testAfterGetIndexerIdsToRunBefore(): void + { + $dependencies = [ + CatalogInventoryStockIndexer::INDEXER_ID, + ]; + $indexerId = CatalogSearchFulltextIndexer::INDEXER_ID; + + $indexerIds = $this->plugin->afterGetIndexerIdsToRunBefore($this->providerMock, $dependencies, $indexerId); + $this->assertContains(CatalogInventoryStockIndexer::INDEXER_ID, $indexerIds); + + $indexerIds = $this->plugin->afterGetIndexerIdsToRunBefore($this->providerMock, $dependencies, $indexerId); + $this->assertNotContains(CatalogInventoryStockIndexer::INDEXER_ID, $indexerIds); + } + + /** + * @return void + */ + public function testAfterGetIndexerIdsToRunAfter(): void + { + $dependencies = [ + CatalogSearchFulltextIndexer::INDEXER_ID, + ]; + $indexerId = CatalogInventoryStockIndexer::INDEXER_ID; + + $indexerIds = $this->plugin->afterGetIndexerIdsToRunAfter($this->providerMock, $dependencies, $indexerId); + $this->assertContains(CatalogSearchFulltextIndexer::INDEXER_ID, $indexerIds); + + $indexerIds = $this->plugin->afterGetIndexerIdsToRunAfter($this->providerMock, $dependencies, $indexerId); + $this->assertNotContains(CatalogSearchFulltextIndexer::INDEXER_ID, $indexerIds); + } +} diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/Indexer/Plugin/StockedProductsFilterPluginTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/Indexer/Plugin/StockedProductsFilterPluginTest.php new file mode 100644 index 0000000000000..f66d2532b32ae --- /dev/null +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/Indexer/Plugin/StockedProductsFilterPluginTest.php @@ -0,0 +1,134 @@ +configMock = $this->getMockBuilder(Config::class)->disableOriginalConstructor()->getMock(); + $this->stockConfigurationMock = $this->getMockBuilder(StockConfigurationInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->stockStatusRepositoryMock = $this->getMockBuilder(StockStatusRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->stockStatusCriteriaFactoryMock = $this->getMockBuilder(StockStatusCriteriaInterfaceFactory::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->plugin = new StockedProductsFilterPlugin( + $this->configMock, + $this->stockConfigurationMock, + $this->stockStatusRepositoryMock, + $this->stockStatusCriteriaFactoryMock + ); + } + + /** + * @return void + */ + public function testBeforePrepareProductIndex(): void + { + /** @var DataProvider|\PHPUnit_Framework_MockObject_MockObject $dataProviderMock */ + $dataProviderMock = $this->getMockBuilder(DataProvider::class)->disableOriginalConstructor()->getMock(); + $indexData = [ + 1 => [], + 2 => [], + ]; + $productData = []; + $storeId = 1; + + $this->configMock + ->expects($this->once()) + ->method('isElasticsearchEnabled') + ->willReturn(true); + $this->stockConfigurationMock + ->expects($this->once()) + ->method('isShowOutOfStock') + ->willReturn(false); + + $stockStatusCriteriaMock = $this->getMockBuilder(StockStatusCriteriaInterface::class)->getMock(); + $stockStatusCriteriaMock + ->expects($this->once()) + ->method('setProductsFilter') + ->willReturn(true); + $this->stockStatusCriteriaFactoryMock + ->expects($this->once()) + ->method('create') + ->willReturn($stockStatusCriteriaMock); + + $stockStatusMock = $this->getMockBuilder(StockStatusInterface::class)->getMock(); + $stockStatusMock->expects($this->atLeastOnce()) + ->method('getStockStatus') + ->willReturnOnConsecutiveCalls(Stock::STOCK_IN_STOCK, Stock::STOCK_OUT_OF_STOCK); + $stockStatusCollectionMock = $this->getMockBuilder(StockStatusCollectionInterface::class)->getMock(); + $stockStatusCollectionMock + ->expects($this->once()) + ->method('getItems') + ->willReturn([ + 1 => $stockStatusMock, + 2 => $stockStatusMock, + ]); + $this->stockStatusRepositoryMock + ->expects($this->once()) + ->method('getList') + ->willReturn($stockStatusCollectionMock); + + list ($indexData, $productData, $storeId) = $this->plugin->beforePrepareProductIndex( + $dataProviderMock, + $indexData, + $productData, + $storeId + ); + + $this->assertEquals([1], array_keys($indexData)); + } +} diff --git a/app/code/Magento/Elasticsearch/composer.json b/app/code/Magento/Elasticsearch/composer.json index 8e379579641d3..a6b4f93ade579 100644 --- a/app/code/Magento/Elasticsearch/composer.json +++ b/app/code/Magento/Elasticsearch/composer.json @@ -10,6 +10,7 @@ "magento/module-eav": "*", "magento/module-search": "*", "magento/module-store": "*", + "magento/module-catalog-inventory": "*", "magento/framework": "*", "elasticsearch/elasticsearch": "~2.0|~5.1" }, diff --git a/app/code/Magento/Elasticsearch/etc/di.xml b/app/code/Magento/Elasticsearch/etc/di.xml index b3ba240ae239c..2d569eecfee58 100644 --- a/app/code/Magento/Elasticsearch/etc/di.xml +++ b/app/code/Magento/Elasticsearch/etc/di.xml @@ -189,6 +189,12 @@ + + + + + + diff --git a/app/code/Magento/Elasticsearch/etc/indexer.xml b/app/code/Magento/Elasticsearch/etc/indexer.xml new file mode 100644 index 0000000000000..245829396a67b --- /dev/null +++ b/app/code/Magento/Elasticsearch/etc/indexer.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/app/code/Magento/Paypal/Model/Ipn.php b/app/code/Magento/Paypal/Model/Ipn.php index a370bbc77ffb2..9107762c54b69 100644 --- a/app/code/Magento/Paypal/Model/Ipn.php +++ b/app/code/Magento/Paypal/Model/Ipn.php @@ -7,9 +7,9 @@ namespace Magento\Paypal\Model; use Exception; +use Magento\Framework\Exception\LocalizedException; use Magento\Sales\Model\Order\Email\Sender\CreditmemoSender; use Magento\Sales\Model\Order\Email\Sender\OrderSender; -use Magento\Paypal\Model\Info; /** * PayPal Instant Payment Notification processor model @@ -164,11 +164,11 @@ protected function _processOrder() case Info::TXN_TYPE_NEW_CASE: $this->_registerDispute(); break; - // handle new adjustment is created + // handle new adjustment is created case Info::TXN_TYPE_ADJUSTMENT: $this->_registerAdjustment(); break; - //handle new transaction created + //handle new transaction created default: $this->_registerTransaction(); break; @@ -239,16 +239,16 @@ protected function _registerTransaction() case Info::PAYMENTSTATUS_COMPLETED: $this->_registerPaymentCapture(true); break; - // the holded payment was denied on paypal side + // the holded payment was denied on paypal side case Info::PAYMENTSTATUS_DENIED: $this->_registerPaymentDenial(); break; - // customer attempted to pay via bank account, but failed + // customer attempted to pay via bank account, but failed case Info::PAYMENTSTATUS_FAILED: // cancel order $this->_registerPaymentFailure(); break; - // payment was obtained, but money were not captured yet + // payment was obtained, but money were not captured yet case Info::PAYMENTSTATUS_PENDING: $this->_registerPaymentPending(); break; @@ -263,7 +263,7 @@ protected function _registerTransaction() case Info::PAYMENTSTATUS_REFUNDED: $this->_registerPaymentRefund(); break; - // authorization expire/void + // authorization expire/void case Info::PAYMENTSTATUS_EXPIRED: // break is intentionally omitted case Info::PAYMENTSTATUS_VOIDED: @@ -288,24 +288,12 @@ protected function _registerPaymentCapture($skipFraudDetection = false) $parentTransactionId = $this->getRequestData('parent_txn_id'); $this->_importPaymentInformation(); $payment = $this->_order->getPayment(); - $payment->setTransactionId( - $this->getRequestData('txn_id') - ); - $payment->setCurrencyCode( - $this->getRequestData('mc_currency') - ); - $payment->setPreparedMessage( - $this->_createIpnComment('') - ); - $payment->setParentTransactionId( - $parentTransactionId - ); - $payment->setShouldCloseParentTransaction( - 'Completed' === $this->getRequestData('auth_status') - ); - $payment->setIsTransactionClosed( - 0 - ); + $payment->setTransactionId($this->getRequestData('txn_id')); + $payment->setCurrencyCode($this->getRequestData('mc_currency')); + $payment->setPreparedMessage($this->_createIpnComment('')); + $payment->setParentTransactionId($parentTransactionId); + $payment->setShouldCloseParentTransaction('Completed' === $this->getRequestData('auth_status')); + $payment->setIsTransactionClosed(0); $payment->registerCaptureNotification( $this->getRequestData('mc_gross'), $skipFraudDetection && $parentTransactionId @@ -318,9 +306,9 @@ protected function _registerPaymentCapture($skipFraudDetection = false) $this->orderSender->send($this->_order); $this->_order->addStatusHistoryComment( __('You notified customer about invoice #%1.', $invoice->getIncrementId()) - )->setIsCustomerNotified( - true - )->save(); + ) + ->setIsCustomerNotified(true) + ->save(); } } @@ -334,15 +322,13 @@ protected function _registerPaymentDenial() { try { $this->_importPaymentInformation(); - $this->_order->getPayment()->setTransactionId( - $this->getRequestData('txn_id') - )->setNotificationResult( - true - )->setIsTransactionClosed( - true - )->deny(false); + $this->_order->getPayment() + ->setTransactionId($this->getRequestData('txn_id')) + ->setNotificationResult(true) + ->setIsTransactionClosed(true) + ->deny(false); $this->_order->save(); - } catch (\Magento\Framework\Exception\LocalizedException $e) { + } catch (LocalizedException $e) { if ($e->getMessage() != __('We cannot cancel this order.')) { throw $e; } @@ -386,13 +372,11 @@ public function _registerPaymentPending() $this->_importPaymentInformation(); - $this->_order->getPayment()->setPreparedMessage( - $this->_createIpnComment($this->_paypalInfo->explainPendingReason($reason)) - )->setTransactionId( - $this->getRequestData('txn_id') - )->setIsTransactionClosed( - 0 - )->update(false); + $this->_order->getPayment() + ->setPreparedMessage($this->_createIpnComment($this->_paypalInfo->explainPendingReason($reason))) + ->setTransactionId($this->getRequestData('txn_id')) + ->setIsTransactionClosed(0) + ->update(false); $this->_order->save(); } @@ -409,19 +393,12 @@ protected function _registerPaymentAuthorization() $payment->update(true); } else { $this->_importPaymentInformation(); - $payment->setPreparedMessage( - $this->_createIpnComment('') - )->setTransactionId( - $this->getRequestData('txn_id') - )->setParentTransactionId( - $this->getRequestData('parent_txn_id') - )->setCurrencyCode( - $this->getRequestData('mc_currency') - )->setIsTransactionClosed( - 0 - )->registerAuthorizationNotification( - $this->getRequestData('mc_gross') - ); + $payment->setPreparedMessage($this->_createIpnComment('')) + ->setTransactionId($this->getRequestData('txn_id')) + ->setParentTransactionId($this->getRequestData('parent_txn_id')) + ->setCurrencyCode($this->getRequestData('mc_currency')) + ->setIsTransactionClosed(0) + ->registerAuthorizationNotification($this->getRequestData('mc_gross')); } if (!$this->_order->getEmailSent()) { $this->orderSender->send($this->_order); @@ -449,12 +426,13 @@ protected function _registerPaymentReversal() { $reasonCode = $this->getRequestData('reason_code'); $reasonComment = $this->_paypalInfo->explainReasonCode($reasonCode); - $notificationAmount = $this->_order->getBaseCurrency()->formatTxt( - $this->getRequestData('mc_gross') + $this->getRequestData('mc_fee') - ); + $notificationAmount = $this->_order->getBaseCurrency() + ->formatTxt( + $this->getRequestData('mc_gross') + $this->getRequestData('mc_fee') + ); $paymentStatus = $this->_filterPaymentStatus($this->getRequestData('payment_status')); $orderStatus = $paymentStatus == - Info::PAYMENTSTATUS_REVERSED ? Info::ORDER_STATUS_REVERSED : Info::ORDER_STATUS_CANCELED_REVERSAL; + Info::PAYMENTSTATUS_REVERSED ? Info::ORDER_STATUS_REVERSED : Info::ORDER_STATUS_CANCELED_REVERSAL; //Change order status to PayPal Reversed/PayPal Cancelled Reversal if it is possible. $message = __( 'IPN "%1". %2 Transaction amount %3. Transaction ID: "%4"', @@ -464,8 +442,9 @@ protected function _registerPaymentReversal() $this->getRequestData('txn_id') ); $this->_order->setStatus($orderStatus); - $this->_order->save(); - $this->_order->addStatusHistoryComment($message, $orderStatus)->setIsCustomerNotified(false)->save(); + $this->_order->addStatusHistoryComment($message, $orderStatus) + ->setIsCustomerNotified(false) + ->save(); } /** @@ -478,17 +457,12 @@ protected function _registerPaymentRefund() $this->_importPaymentInformation(); $reason = $this->getRequestData('reason_code'); $isRefundFinal = !$this->_paypalInfo->isReversalDisputable($reason); - $payment = $this->_order->getPayment()->setPreparedMessage( - $this->_createIpnComment($this->_paypalInfo->explainReasonCode($reason)) - )->setTransactionId( - $this->getRequestData('txn_id') - )->setParentTransactionId( - $this->getRequestData('parent_txn_id') - )->setIsTransactionClosed( - $isRefundFinal - )->registerRefundNotification( - -1 * $this->getRequestData('mc_gross') - ); + $payment = $this->_order->getPayment() + ->setPreparedMessage($this->_createIpnComment($this->_paypalInfo->explainReasonCode($reason))) + ->setTransactionId($this->getRequestData('txn_id')) + ->setParentTransactionId($this->getRequestData('parent_txn_id')) + ->setIsTransactionClosed($isRefundFinal) + ->registerRefundNotification(-1 * $this->getRequestData('mc_gross')); $this->_order->save(); // TODO: there is no way to close a capture right now @@ -498,9 +472,9 @@ protected function _registerPaymentRefund() $this->creditmemoSender->send($creditMemo); $this->_order->addStatusHistoryComment( __('You notified customer about creditmemo #%1.', $creditMemo->getIncrementId()) - )->setIsCustomerNotified( - true - )->save(); + ) + ->setIsCustomerNotified(true) + ->save(); } } @@ -513,19 +487,14 @@ protected function _registerPaymentVoid() { $this->_importPaymentInformation(); - $parentTxnId = $this->getRequestData( - 'transaction_entity' - ) == 'auth' ? $this->getRequestData( - 'txn_id' - ) : $this->getRequestData( - 'parent_txn_id' - ); + $parentTxnId = $this->getRequestData('transaction_entity') == 'auth' + ? $this->getRequestData('txn_id') + : $this->getRequestData('parent_txn_id'); - $this->_order->getPayment()->setPreparedMessage( - $this->_createIpnComment('') - )->setParentTransactionId( - $parentTxnId - )->registerVoidNotification(); + $this->_order->getPayment() + ->setPreparedMessage($this->_createIpnComment('')) + ->setParentTransactionId($parentTxnId) + ->registerVoidNotification(); $this->_order->save(); } @@ -546,14 +515,14 @@ protected function _importPaymentInformation() // collect basic information $from = []; foreach ([ - Info::PAYER_ID, - 'payer_email' => Info::PAYER_EMAIL, - Info::PAYER_STATUS, - Info::ADDRESS_STATUS, - Info::PROTECTION_EL, - Info::PAYMENT_STATUS, - Info::PENDING_REASON, - ] as $privateKey => $publicKey) { + Info::PAYER_ID, + 'payer_email' => Info::PAYER_EMAIL, + Info::PAYER_STATUS, + Info::ADDRESS_STATUS, + Info::PROTECTION_EL, + Info::PAYMENT_STATUS, + Info::PENDING_REASON, + ] as $privateKey => $publicKey) { if (is_int($privateKey)) { $privateKey = $publicKey; } diff --git a/app/code/Magento/Persistent/Observer/CheckExpirePersistentQuoteObserver.php b/app/code/Magento/Persistent/Observer/CheckExpirePersistentQuoteObserver.php index 2aaf0f30fe71d..f3720960ca6e5 100644 --- a/app/code/Magento/Persistent/Observer/CheckExpirePersistentQuoteObserver.php +++ b/app/code/Magento/Persistent/Observer/CheckExpirePersistentQuoteObserver.php @@ -50,6 +50,20 @@ class CheckExpirePersistentQuoteObserver implements ObserverInterface */ protected $_persistentData = null; + /** + * Request + * + * @var \Magento\Framework\App\RequestInterface + */ + private $request; + + /** + * Checkout Page path + * + * @var string + */ + private $checkoutPagePath = 'checkout'; + /** * @param \Magento\Persistent\Helper\Session $persistentSession * @param \Magento\Persistent\Helper\Data $persistentData @@ -57,6 +71,7 @@ class CheckExpirePersistentQuoteObserver implements ObserverInterface * @param \Magento\Framework\Event\ManagerInterface $eventManager * @param \Magento\Customer\Model\Session $customerSession * @param \Magento\Checkout\Model\Session $checkoutSession + * @param \Magento\Framework\App\RequestInterface $request */ public function __construct( \Magento\Persistent\Helper\Session $persistentSession, @@ -64,7 +79,8 @@ public function __construct( \Magento\Persistent\Model\QuoteManager $quoteManager, \Magento\Framework\Event\ManagerInterface $eventManager, \Magento\Customer\Model\Session $customerSession, - \Magento\Checkout\Model\Session $checkoutSession + \Magento\Checkout\Model\Session $checkoutSession, + \Magento\Framework\App\RequestInterface $request ) { $this->_persistentSession = $persistentSession; $this->quoteManager = $quoteManager; @@ -72,6 +88,7 @@ public function __construct( $this->_checkoutSession = $checkoutSession; $this->_eventManager = $eventManager; $this->_persistentData = $persistentData; + $this->request = $request; } /** @@ -90,12 +107,32 @@ public function execute(\Magento\Framework\Event\Observer $observer) !$this->_persistentSession->isPersistent() && !$this->_customerSession->isLoggedIn() && $this->_checkoutSession->getQuoteId() && - !$observer->getControllerAction() instanceof \Magento\Checkout\Controller\Onepage - // persistent session does not expire on onepage checkout page to not spoil customer group id + !$this->isRequestFromCheckoutPage($this->request) + // persistent session does not expire on onepage checkout page ) { $this->_eventManager->dispatch('persistent_session_expired'); $this->quoteManager->expire(); $this->_customerSession->setCustomerId(null)->setCustomerGroupId(null); } } + + /** + * Check current request is coming from onepage checkout page. + * + * @param \Magento\Framework\App\RequestInterface $request + * @return bool + */ + private function isRequestFromCheckoutPage(\Magento\Framework\App\RequestInterface $request): bool + { + $requestUri = (string)$request->getRequestUri(); + $refererUri = (string)$request->getServer('HTTP_REFERER'); + + /** @var bool $isCheckoutPage */ + $isCheckoutPage = ( + false !== strpos($requestUri, $this->checkoutPagePath) || + false !== strpos($refererUri, $this->checkoutPagePath) + ); + + return $isCheckoutPage; + } } diff --git a/app/code/Magento/Persistent/Test/Unit/Observer/CheckExpirePersistentQuoteObserverTest.php b/app/code/Magento/Persistent/Test/Unit/Observer/CheckExpirePersistentQuoteObserverTest.php index a52e22a960e0b..8cad0b9f2dd89 100644 --- a/app/code/Magento/Persistent/Test/Unit/Observer/CheckExpirePersistentQuoteObserverTest.php +++ b/app/code/Magento/Persistent/Test/Unit/Observer/CheckExpirePersistentQuoteObserverTest.php @@ -49,24 +49,39 @@ class CheckExpirePersistentQuoteObserverTest extends \PHPUnit\Framework\TestCase */ protected $eventManagerMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\App\RequestInterface + */ + private $requestMock; + + /** + * @inheritdoc + */ protected function setUp() { $this->sessionMock = $this->createMock(\Magento\Persistent\Helper\Session::class); $this->customerSessionMock = $this->createMock(\Magento\Customer\Model\Session::class); $this->persistentHelperMock = $this->createMock(\Magento\Persistent\Helper\Data::class); - $this->observerMock - = $this->createPartialMock(\Magento\Framework\Event\Observer::class, ['getControllerAction', - '__wakeUp']); + $this->observerMock = $this->createPartialMock( + \Magento\Framework\Event\Observer::class, + ['getControllerAction','__wakeUp'] + ); $this->quoteManagerMock = $this->createMock(\Magento\Persistent\Model\QuoteManager::class); $this->eventManagerMock = $this->createMock(\Magento\Framework\Event\ManagerInterface::class); $this->checkoutSessionMock = $this->createMock(\Magento\Checkout\Model\Session::class); + $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\RequestInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getRequestUri', 'getServer']) + ->getMockForAbstractClass(); + $this->model = new \Magento\Persistent\Observer\CheckExpirePersistentQuoteObserver( $this->sessionMock, $this->persistentHelperMock, $this->quoteManagerMock, $this->eventManagerMock, $this->customerSessionMock, - $this->checkoutSessionMock + $this->checkoutSessionMock, + $this->requestMock ); } @@ -76,7 +91,7 @@ public function testExecuteWhenCanNotApplyPersistentData() ->expects($this->once()) ->method('canProcess') ->with($this->observerMock) - ->will($this->returnValue(false)); + ->willReturn(false); $this->persistentHelperMock->expects($this->never())->method('isEnabled'); $this->model->execute($this->observerMock); } @@ -87,31 +102,97 @@ public function testExecuteWhenPersistentIsNotEnabled() ->expects($this->once()) ->method('canProcess') ->with($this->observerMock) - ->will($this->returnValue(true)); - $this->persistentHelperMock->expects($this->once())->method('isEnabled')->will($this->returnValue(false)); + ->willReturn(true); + $this->persistentHelperMock->expects($this->once())->method('isEnabled')->willReturn(false); $this->eventManagerMock->expects($this->never())->method('dispatch'); $this->model->execute($this->observerMock); } - public function testExecuteWhenPersistentIsEnabled() - { + /** + * Test method \Magento\Persistent\Observer\CheckExpirePersistentQuoteObserver::execute when persistent is enabled. + * + * @param string $refererUri + * @param string $requestUri + * @param \PHPUnit_Framework_MockObject_Matcher_InvokedCount $expireCounter + * @param \PHPUnit_Framework_MockObject_Matcher_InvokedCount $dispatchCounter + * @param \PHPUnit_Framework_MockObject_Matcher_InvokedCount $setCustomerIdCounter + * @return void + * @dataProvider requestDataProvider + */ + public function testExecuteWhenPersistentIsEnabled( + string $refererUri, + string $requestUri, + \PHPUnit_Framework_MockObject_Matcher_InvokedCount $expireCounter, + \PHPUnit_Framework_MockObject_Matcher_InvokedCount $dispatchCounter, + \PHPUnit_Framework_MockObject_Matcher_InvokedCount $setCustomerIdCounter + ): void { $this->persistentHelperMock ->expects($this->once()) ->method('canProcess') ->with($this->observerMock) - ->will($this->returnValue(true)); - $this->persistentHelperMock->expects($this->once())->method('isEnabled')->will($this->returnValue(true)); - $this->sessionMock->expects($this->once())->method('isPersistent')->will($this->returnValue(false)); - $this->customerSessionMock->expects($this->once())->method('isLoggedIn')->will($this->returnValue(false)); - $this->checkoutSessionMock->expects($this->once())->method('getQuoteId')->will($this->returnValue(10)); - $this->observerMock->expects($this->once())->method('getControllerAction'); - $this->eventManagerMock->expects($this->once())->method('dispatch'); - $this->quoteManagerMock->expects($this->once())->method('expire'); + ->willReturn(true); + $this->persistentHelperMock->expects($this->once())->method('isEnabled')->willReturn(true); + $this->sessionMock->expects($this->once())->method('isPersistent')->willReturn(false); $this->customerSessionMock - ->expects($this->once()) + ->expects($this->atLeastOnce()) + ->method('isLoggedIn') + ->willReturn(false); + $this->checkoutSessionMock + ->expects($this->atLeastOnce()) + ->method('getQuoteId') + ->willReturn(10); + $this->eventManagerMock->expects($dispatchCounter)->method('dispatch'); + $this->quoteManagerMock->expects($expireCounter)->method('expire'); + $this->customerSessionMock + ->expects($setCustomerIdCounter) ->method('setCustomerId') ->with(null) - ->will($this->returnSelf()); + ->willReturnSelf(); + $this->requestMock->expects($this->atLeastOnce())->method('getRequestUri')->willReturn($refererUri); + $this->requestMock + ->expects($this->atLeastOnce()) + ->method('getServer') + ->with('HTTP_REFERER') + ->willReturn($requestUri); $this->model->execute($this->observerMock); } + + /** + * Request Data Provider + * + * @return array + */ + public function requestDataProvider() + { + return [ + [ + 'refererUri' => 'checkout', + 'requestUri' => 'index', + 'expireCounter' => $this->never(), + 'dispatchCounter' => $this->never(), + 'setCustomerIdCounter' => $this->never(), + ], + [ + 'refererUri' => 'checkout', + 'requestUri' => 'checkout', + 'expireCounter' => $this->never(), + 'dispatchCounter' => $this->never(), + 'setCustomerIdCounter' => $this->never(), + ], + [ + 'refererUri' => 'index', + 'requestUri' => 'checkout', + 'expireCounter' => $this->never(), + 'dispatchCounter' => $this->never(), + 'setCustomerIdCounter' => $this->never(), + ], + [ + 'refererUri' => 'index', + 'requestUri' => 'index', + 'expireCounter' => $this->once(), + 'dispatchCounter' => $this->once(), + 'setCustomerIdCounter' => $this->once(), + ], + ]; + } } diff --git a/app/code/Magento/Rule/Model/Action/AbstractAction.php b/app/code/Magento/Rule/Model/Action/AbstractAction.php index fb15edf8a4893..4d56f6cc56edc 100644 --- a/app/code/Magento/Rule/Model/Action/AbstractAction.php +++ b/app/code/Magento/Rule/Model/Action/AbstractAction.php @@ -49,13 +49,16 @@ public function __construct( $this->loadAttributeOptions()->loadOperatorOptions()->loadValueOptions(); - foreach (array_keys($this->getAttributeOption()) as $attr) { - $this->setAttribute($attr); - break; + $attributes = $this->getAttributeOption(); + if ($attributes) { + reset($attributes); + $this->setAttribute(key($attributes)); } - foreach (array_keys($this->getOperatorOption()) as $operator) { - $this->setOperator($operator); - break; + + $operators = $this->getOperatorOption(); + if ($operators) { + reset($operators); + $this->setOperator(key($operators)); } } diff --git a/app/code/Magento/Rule/Model/Condition/Combine.php b/app/code/Magento/Rule/Model/Condition/Combine.php index 24ed1cb497472..48873aec66295 100644 --- a/app/code/Magento/Rule/Model/Condition/Combine.php +++ b/app/code/Magento/Rule/Model/Condition/Combine.php @@ -46,10 +46,8 @@ public function __construct(Context $context, array $data = []) $this->loadAggregatorOptions(); $options = $this->getAggregatorOptions(); if ($options) { - foreach (array_keys($options) as $aggregator) { - $this->setAggregator($aggregator); - break; - } + reset($options); + $this->setAggregator(key($options)); } } @@ -90,9 +88,10 @@ public function getAggregatorName() public function getAggregatorElement() { if ($this->getAggregator() === null) { - foreach (array_keys($this->getAggregatorOption()) as $key) { - $this->setAggregator($key); - break; + $options = $this->getAggregatorOption(); + if ($options) { + reset($options); + $this->setAggregator(key($options)); } } return $this->getForm()->addField( diff --git a/app/code/Magento/Rule/view/adminhtml/web/conditions-data-normalizer.js b/app/code/Magento/Rule/view/adminhtml/web/conditions-data-normalizer.js index c9c36c4fa585a..8eebc9ef0bd40 100644 --- a/app/code/Magento/Rule/view/adminhtml/web/conditions-data-normalizer.js +++ b/app/code/Magento/Rule/view/adminhtml/web/conditions-data-normalizer.js @@ -14,11 +14,11 @@ define([ */ var ConditionsDataNormalizer = function () { this.patterns = { - validate: /^[a-z0-9_-][a-z0-9_-]*(?:\[(?:\d*|[a-z0-9_-]+)\])*$/i, - key: /[a-z0-9_-]+|(?=\[\])/gi, + validate: /^[a-z0-9_.-][a-z0-9_.-]*(?:\[(?:\d*|[a-z0-9_.-]+)\])*$/i, + key: /[a-z0-9_.-]+|(?=\[\])/gi, push: /^$/, fixed: /^\d+$/, - named: /^[a-z0-9_-]+$/i + named: /^[a-z0-9_.-]+$/i }; }; diff --git a/app/code/Magento/Sales/Model/Order/Payment/State/RegisterCaptureNotificationCommand.php b/app/code/Magento/Sales/Model/Order/Payment/State/RegisterCaptureNotificationCommand.php index ee12b459118c1..d38e58d7341c1 100644 --- a/app/code/Magento/Sales/Model/Order/Payment/State/RegisterCaptureNotificationCommand.php +++ b/app/code/Magento/Sales/Model/Order/Payment/State/RegisterCaptureNotificationCommand.php @@ -35,7 +35,7 @@ public function __construct(StatusResolver $statusResolver = null) */ public function execute(OrderPaymentInterface $payment, $amount, OrderInterface $order) { - $state = Order::STATE_PROCESSING; + $state = $order->getState() ?: Order::STATE_PROCESSING; $status = null; $message = 'Registered notification about captured amount of %1.'; diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Payment/State/RegisterCaptureNotificationCommandTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Payment/State/RegisterCaptureNotificationCommandTest.php index 32ea9d8869344..1b762fafe0b71 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Payment/State/RegisterCaptureNotificationCommandTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Payment/State/RegisterCaptureNotificationCommandTest.php @@ -32,26 +32,29 @@ class RegisterCaptureNotificationCommandTest extends \PHPUnit\Framework\TestCase * * @param bool $isTransactionPending * @param bool $isFraudDetected + * @param string|null $currentState * @param string $expectedState * @param string $expectedStatus * @param string $expectedMessage - * + * @return void * @dataProvider commandResultDataProvider */ public function testExecute( - $isTransactionPending, - $isFraudDetected, - $expectedState, - $expectedStatus, - $expectedMessage - ) { + bool $isTransactionPending, + bool $isFraudDetected, + $currentState, + string $expectedState, + string $expectedStatus, + string $expectedMessage + ): void { + $order = $this->getOrder($currentState); $actualReturn = (new RegisterCaptureNotificationCommand($this->getStatusResolver()))->execute( $this->getPayment($isTransactionPending, $isFraudDetected), $this->amount, - $this->getOrder() + $order ); - $this->assertOrderStateAndStatus($this->getOrder(), $expectedState, $expectedStatus); + $this->assertOrderStateAndStatus($order, $expectedState, $expectedStatus); self::assertEquals(__($expectedMessage, $this->amount), $actualReturn); } @@ -64,30 +67,42 @@ public function commandResultDataProvider() [ false, false, + Order::STATE_COMPLETE, + Order::STATE_COMPLETE, + $this->newOrderStatus, + 'Registered notification about captured amount of %1.', + ], + [ + false, + false, + null, Order::STATE_PROCESSING, $this->newOrderStatus, - 'Registered notification about captured amount of %1.' + 'Registered notification about captured amount of %1.', ], [ true, false, + Order::STATE_PROCESSING, Order::STATE_PAYMENT_REVIEW, $this->newOrderStatus, - 'An amount of %1 will be captured after being approved at the payment gateway.' + 'An amount of %1 will be captured after being approved at the payment gateway.', ], [ false, true, + Order::STATE_PROCESSING, Order::STATE_PAYMENT_REVIEW, Order::STATUS_FRAUD, - 'Order is suspended as its capture amount %1 is suspected to be fraudulent.' + 'Order is suspended as its capture amount %1 is suspected to be fraudulent.', ], [ true, true, + Order::STATE_PROCESSING, Order::STATE_PAYMENT_REVIEW, Order::STATUS_FRAUD, - 'Order is suspended as its capture amount %1 is suspected to be fraudulent.' + 'Order is suspended as its capture amount %1 is suspected to be fraudulent.', ], ]; } @@ -107,15 +122,19 @@ private function getStatusResolver() } /** + * @param string|null $state * @return Order|MockObject */ - private function getOrder() + private function getOrder($state) { + /** @var Order|MockObject $order */ $order = $this->getMockBuilder(Order::class) ->disableOriginalConstructor() + ->setMethods(['getBaseCurrency', 'getOrderStatusByState']) ->getMock(); $order->method('getBaseCurrency') ->willReturn($this->getCurrency()); + $order->setState($state); return $order; } @@ -159,7 +178,7 @@ private function getCurrency() */ private function assertOrderStateAndStatus($order, $expectedState, $expectedStatus) { - $order->method('setState')->with($expectedState); - $order->method('setStatus')->with($expectedStatus); + self::assertEquals($expectedState, $order->getState(), 'The order {state} should match.'); + self::assertEquals($expectedStatus, $order->getStatus(), 'The order {status} should match.'); } } diff --git a/app/code/Magento/SalesRule/Model/Quote/ChildrenValidationLocator.php b/app/code/Magento/SalesRule/Model/Quote/ChildrenValidationLocator.php new file mode 100644 index 0000000000000..af1f61c187129 --- /dev/null +++ b/app/code/Magento/SalesRule/Model/Quote/ChildrenValidationLocator.php @@ -0,0 +1,53 @@ + + * [ + * 'ProductType1' => true, + * 'ProductType2' => false + * ] + * + */ + public function __construct( + array $productTypeChildrenValidationMap = [] + ) { + $this->productTypeChildrenValidationMap = $productTypeChildrenValidationMap; + } + + /** + * Checks necessity to validate rule on item's children. + * + * @param QuoteItem $item + * @return bool + */ + public function isChildrenValidationRequired(QuoteItem $item): bool + { + $type = $item->getProduct()->getTypeId(); + if (isset($this->productTypeChildrenValidationMap[$type])) { + return (bool)$this->productTypeChildrenValidationMap[$type]; + } + + return true; + } +} diff --git a/app/code/Magento/SalesRule/Model/RulesApplier.php b/app/code/Magento/SalesRule/Model/RulesApplier.php index 06a4e252bf60e..f771a4f1e3892 100644 --- a/app/code/Magento/SalesRule/Model/RulesApplier.php +++ b/app/code/Magento/SalesRule/Model/RulesApplier.php @@ -6,6 +6,9 @@ namespace Magento\SalesRule\Model; use Magento\Quote\Model\Quote\Address; +use Magento\SalesRule\Model\Quote\ChildrenValidationLocator; +use Magento\Framework\App\ObjectManager; +use Magento\SalesRule\Model\Rule\Action\Discount\CalculatorFactory; /** * Class RulesApplier @@ -25,19 +28,33 @@ class RulesApplier */ protected $validatorUtility; + /** + * @var ChildrenValidationLocator + */ + private $childrenValidationLocator; + + /** + * @var CalculatorFactory + */ + private $calculatorFactory; + /** * @param \Magento\SalesRule\Model\Rule\Action\Discount\CalculatorFactory $calculatorFactory * @param \Magento\Framework\Event\ManagerInterface $eventManager * @param \Magento\SalesRule\Model\Utility $utility + * @param ChildrenValidationLocator|null $childrenValidationLocator */ public function __construct( \Magento\SalesRule\Model\Rule\Action\Discount\CalculatorFactory $calculatorFactory, \Magento\Framework\Event\ManagerInterface $eventManager, - \Magento\SalesRule\Model\Utility $utility + \Magento\SalesRule\Model\Utility $utility, + ChildrenValidationLocator $childrenValidationLocator = null ) { $this->calculatorFactory = $calculatorFactory; $this->validatorUtility = $utility; $this->_eventManager = $eventManager; + $this->childrenValidationLocator = $childrenValidationLocator + ?: ObjectManager::getInstance()->get(ChildrenValidationLocator::class); } /** @@ -61,6 +78,9 @@ public function applyRules($item, $rules, $skipValidation, $couponCode) } if (!$skipValidation && !$rule->getActions()->validate($item)) { + if (!$this->childrenValidationLocator->isChildrenValidationRequired($item)) { + continue; + } $childItems = $item->getChildren(); $isContinue = true; if (!empty($childItems)) { diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/Quote/ChildrenValidationLocatorTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/Quote/ChildrenValidationLocatorTest.php new file mode 100644 index 0000000000000..abb8d791d74c4 --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Unit/Model/Quote/ChildrenValidationLocatorTest.php @@ -0,0 +1,104 @@ +objectManager = new ObjectManager($this); + + $this->productTypeChildrenValidationMap = [ + 'type1' => true, + 'type2' => false, + ]; + + $this->quoteItemMock = $this->getMockBuilder(QuoteItem::class) + ->disableOriginalConstructor() + ->setMethods(['getProduct']) + ->getMockForAbstractClass(); + + $this->productMock = $this->getMockBuilder(Product::class) + ->disableOriginalConstructor() + ->setMethods(['getTypeId']) + ->getMock(); + + $this->model = $this->objectManager->getObject( + ChildrenValidationLocator::class, + [ + 'productTypeChildrenValidationMap' => $this->productTypeChildrenValidationMap, + ] + ); + } + + /** + * @dataProvider productTypeDataProvider + * @param string $type + * @param bool $expected + * + * @return void + */ + public function testIsChildrenValidationRequired(string $type, bool $expected): void + { + $this->quoteItemMock->expects($this->once()) + ->method('getProduct') + ->willReturn($this->productMock); + + $this->productMock->expects($this->once()) + ->method('getTypeId') + ->willReturn($type); + + $this->assertEquals($this->model->isChildrenValidationRequired($this->quoteItemMock), $expected); + } + + /** + * @return array + */ + public function productTypeDataProvider(): array + { + return [ + ['type1', true], + ['type2', false], + ['type3', true], + ]; + } +} diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/RulesApplierTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/RulesApplierTest.php index 814048c2ac1d0..37c839d413d4b 100644 --- a/app/code/Magento/SalesRule/Test/Unit/Model/RulesApplierTest.php +++ b/app/code/Magento/SalesRule/Test/Unit/Model/RulesApplierTest.php @@ -6,6 +6,9 @@ namespace Magento\SalesRule\Test\Unit\Model; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class RulesApplierTest extends \PHPUnit\Framework\TestCase { /** @@ -28,6 +31,11 @@ class RulesApplierTest extends \PHPUnit\Framework\TestCase */ protected $validatorUtility; + /** + * @var \Magento\SalesRule\Model\Quote\ChildrenValidationLocator|\PHPUnit_Framework_MockObject_MockObject + */ + protected $childrenValidationLocator; + protected function setUp() { $this->calculatorFactory = $this->createMock( @@ -38,11 +46,15 @@ protected function setUp() \Magento\SalesRule\Model\Utility::class, ['canProcessRule', 'minFix', 'deltaRoundingFix', 'getItemQty'] ); - + $this->childrenValidationLocator = $this->createPartialMock( + \Magento\SalesRule\Model\Quote\ChildrenValidationLocator::class, + ['isChildrenValidationRequired'] + ); $this->rulesApplier = new \Magento\SalesRule\Model\RulesApplier( $this->calculatorFactory, $this->eventManager, - $this->validatorUtility + $this->validatorUtility, + $this->childrenValidationLocator ); } @@ -84,6 +96,10 @@ public function testApplyRulesWhenRuleWithStopRulesProcessingIsUsed($isChildren, $item->setDiscountCalculationPrice($positivePrice); $item->setData('calculation_price', $positivePrice); + $this->childrenValidationLocator->expects($this->any()) + ->method('isChildrenValidationRequired') + ->willReturn(true); + $this->validatorUtility->expects($this->atLeastOnce()) ->method('canProcessRule') ->will($this->returnValue(true)); diff --git a/app/code/Magento/Signifyd/Block/Fingerprint.php b/app/code/Magento/Signifyd/Block/Fingerprint.php index 7afa092b3d0da..db76fc6c94468 100644 --- a/app/code/Magento/Signifyd/Block/Fingerprint.php +++ b/app/code/Magento/Signifyd/Block/Fingerprint.php @@ -85,6 +85,8 @@ public function getSignifydOrderSessionId() */ public function isModuleActive() { - return $this->config->isActive(); + $storeId = $this->quoteSession->getQuote()->getStoreId(); + + return $this->config->isActive($storeId); } } diff --git a/app/code/Magento/Signifyd/Model/Config.php b/app/code/Magento/Signifyd/Model/Config.php index b68380ee15bf3..15d3608bd38c4 100644 --- a/app/code/Magento/Signifyd/Model/Config.php +++ b/app/code/Magento/Signifyd/Model/Config.php @@ -34,13 +34,15 @@ public function __construct(ScopeConfigInterface $scopeConfig) * If this config option set to false no Signifyd integration should be available * (only possibility to configure Signifyd setting in admin) * + * @param int|null $storeId * @return bool */ - public function isActive() + public function isActive($storeId = null): bool { $enabled = $this->scopeConfig->isSetFlag( 'fraud_protection/signifyd/active', - ScopeInterface::SCOPE_STORE + ScopeInterface::SCOPE_STORE, + $storeId ); return $enabled; } @@ -51,13 +53,15 @@ public function isActive() * @see https://www.signifyd.com/docs/api/#/introduction/authentication * @see https://app.signifyd.com/settings * + * @param int|null $storeId * @return string */ - public function getApiKey() + public function getApiKey($storeId = null): string { $apiKey = $this->scopeConfig->getValue( 'fraud_protection/signifyd/api_key', - ScopeInterface::SCOPE_STORE + ScopeInterface::SCOPE_STORE, + $storeId ); return $apiKey; } @@ -66,13 +70,15 @@ public function getApiKey() * Base URL to Signifyd REST API. * Usually equals to https://api.signifyd.com/v2 and should not be changed * + * @param int|null $storeId * @return string */ - public function getApiUrl() + public function getApiUrl($storeId = null): string { $apiUrl = $this->scopeConfig->getValue( 'fraud_protection/signifyd/api_url', - ScopeInterface::SCOPE_STORE + ScopeInterface::SCOPE_STORE, + $storeId ); return $apiUrl; } @@ -80,13 +86,15 @@ public function getApiUrl() /** * If is "true" extra information about interaction with Signifyd API are written to debug.log file * + * @param int|null $storeId * @return bool */ - public function isDebugModeEnabled() + public function isDebugModeEnabled($storeId = null): bool { $debugModeEnabled = $this->scopeConfig->isSetFlag( 'fraud_protection/signifyd/debug', - ScopeInterface::SCOPE_STORE + ScopeInterface::SCOPE_STORE, + $storeId ); return $debugModeEnabled; } diff --git a/app/code/Magento/Signifyd/Model/SignifydGateway/ApiClient.php b/app/code/Magento/Signifyd/Model/SignifydGateway/ApiClient.php index 0950ca1e22cfa..2d6d57a510ae3 100644 --- a/app/code/Magento/Signifyd/Model/SignifydGateway/ApiClient.php +++ b/app/code/Magento/Signifyd/Model/SignifydGateway/ApiClient.php @@ -36,12 +36,13 @@ public function __construct( * * @param string $url * @param string $method - * @param array $params + * @param array $params + * @param int|null $storeId * @return array */ - public function makeApiCall($url, $method, array $params = []) + public function makeApiCall($url, $method, array $params = [], $storeId = null): array { - $result = $this->requestBuilder->doRequest($url, $method, $params); + $result = $this->requestBuilder->doRequest($url, $method, $params, $storeId); return $result; } diff --git a/app/code/Magento/Signifyd/Model/SignifydGateway/Client/HttpClientFactory.php b/app/code/Magento/Signifyd/Model/SignifydGateway/Client/HttpClientFactory.php index 41006bd7d1e0e..2a9b933b98b5d 100644 --- a/app/code/Magento/Signifyd/Model/SignifydGateway/Client/HttpClientFactory.php +++ b/app/code/Magento/Signifyd/Model/SignifydGateway/Client/HttpClientFactory.php @@ -73,12 +73,13 @@ public function __construct( * @param string $url * @param string $method * @param array $params + * @param int|null $storeId * @return ZendClient */ - public function create($url, $method, array $params = []) + public function create($url, $method, array $params = [], $storeId = null): ZendClient { - $apiKey = $this->getApiKey(); - $apiUrl = $this->buildFullApiUrl($url); + $apiKey = $this->getApiKey($storeId); + $apiUrl = $this->buildFullApiUrl($url, $storeId); $client = $this->createNewClient(); $client->setHeaders( @@ -107,22 +108,24 @@ private function createNewClient() * Signifyd API key for merchant account. * * @see https://www.signifyd.com/docs/api/#/introduction/authentication + * @param int|null $storeId * @return string */ - private function getApiKey() + private function getApiKey($storeId): string { - return $this->config->getApiKey(); + return $this->config->getApiKey($storeId); } /** * Full URL for Singifyd API based on relative URL. * * @param string $url + * @param int|null $storeId * @return string */ - private function buildFullApiUrl($url) + private function buildFullApiUrl($url, $storeId): string { - $baseApiUrl = $this->getBaseApiUrl(); + $baseApiUrl = $this->getBaseApiUrl($storeId); $fullUrl = $baseApiUrl . self::$urlSeparator . ltrim($url, self::$urlSeparator); return $fullUrl; @@ -131,11 +134,12 @@ private function buildFullApiUrl($url) /** * Base Sigifyd API URL without trailing slash. * + * @param int|null $storeId * @return string */ - private function getBaseApiUrl() + private function getBaseApiUrl($storeId): string { - $baseApiUrl = $this->config->getApiUrl(); + $baseApiUrl = $this->config->getApiUrl($storeId); return rtrim($baseApiUrl, self::$urlSeparator); } diff --git a/app/code/Magento/Signifyd/Model/SignifydGateway/Client/RequestBuilder.php b/app/code/Magento/Signifyd/Model/SignifydGateway/Client/RequestBuilder.php index 2ab4395e1990d..ee079a74d345f 100644 --- a/app/code/Magento/Signifyd/Model/SignifydGateway/Client/RequestBuilder.php +++ b/app/code/Magento/Signifyd/Model/SignifydGateway/Client/RequestBuilder.php @@ -5,8 +5,6 @@ */ namespace Magento\Signifyd\Model\SignifydGateway\Client; -use Magento\Framework\HTTP\ZendClient; - /** * Class RequestBuilder * Creates HTTP client, sends request to Signifyd and handles response @@ -50,13 +48,14 @@ public function __construct( * * @param string $url * @param string $method - * @param array $params + * @param array $params + * @param int|null $storeId * @return array */ - public function doRequest($url, $method, array $params = []) + public function doRequest($url, $method, array $params = [], $storeId = null): array { - $client = $this->clientCreator->create($url, $method, $params); - $response = $this->requestSender->send($client); + $client = $this->clientCreator->create($url, $method, $params, $storeId); + $response = $this->requestSender->send($client, $storeId); $result = $this->responseHandler->handle($response); return $result; diff --git a/app/code/Magento/Signifyd/Model/SignifydGateway/Client/RequestSender.php b/app/code/Magento/Signifyd/Model/SignifydGateway/Client/RequestSender.php index 38128a799fd59..a63331e055c1c 100644 --- a/app/code/Magento/Signifyd/Model/SignifydGateway/Client/RequestSender.php +++ b/app/code/Magento/Signifyd/Model/SignifydGateway/Client/RequestSender.php @@ -39,15 +39,16 @@ public function __construct( * debug information is recorded to debug.log. * * @param ZendClient $client + * @param int|null $storeId * @return \Zend_Http_Response * @throws ApiCallException */ - public function send(ZendClient $client) + public function send(ZendClient $client, $storeId = null): \Zend_Http_Response { try { $response = $client->request(); - $this->debuggerFactory->create()->success( + $this->debuggerFactory->create($storeId)->success( $client->getUri(true), $client->getLastRequest(), $response->getStatus() . ' ' . $response->getMessage(), @@ -56,7 +57,7 @@ public function send(ZendClient $client) return $response; } catch (\Exception $e) { - $this->debuggerFactory->create()->failure( + $this->debuggerFactory->create($storeId)->failure( $client->getUri(true), $client->getLastRequest(), $e diff --git a/app/code/Magento/Signifyd/Model/SignifydGateway/Debugger/DebuggerFactory.php b/app/code/Magento/Signifyd/Model/SignifydGateway/Debugger/DebuggerFactory.php index 19408e99ae02e..1e61a313899cc 100644 --- a/app/code/Magento/Signifyd/Model/SignifydGateway/Debugger/DebuggerFactory.php +++ b/app/code/Magento/Signifyd/Model/SignifydGateway/Debugger/DebuggerFactory.php @@ -44,11 +44,12 @@ public function __construct( /** * Create debugger instance * + * @param int|null $storeId * @return DebuggerInterface */ - public function create() + public function create($storeId = null): DebuggerInterface { - if (!$this->config->isDebugModeEnabled()) { + if (!$this->config->isDebugModeEnabled($storeId)) { return $this->objectManager->get(BlackHole::class); } diff --git a/app/code/Magento/Signifyd/Model/SignifydGateway/Gateway.php b/app/code/Magento/Signifyd/Model/SignifydGateway/Gateway.php index ddcaa6cd696f2..9f7a053c58724 100644 --- a/app/code/Magento/Signifyd/Model/SignifydGateway/Gateway.php +++ b/app/code/Magento/Signifyd/Model/SignifydGateway/Gateway.php @@ -5,8 +5,9 @@ */ namespace Magento\Signifyd\Model\SignifydGateway; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Signifyd\Api\CaseRepositoryInterface; use Magento\Signifyd\Model\SignifydGateway\Request\CreateCaseBuilderInterface; -use Magento\Signifyd\Model\SignifydGateway\ApiClient; /** * Signifyd Gateway. @@ -53,18 +54,34 @@ class Gateway */ private $apiClient; + /** + * @var OrderRepositoryInterface + */ + private $orderRepository; + + /** + * @var CaseRepositoryInterface + */ + private $caseRepository; + /** * Gateway constructor. * * @param CreateCaseBuilderInterface $createCaseBuilder * @param ApiClient $apiClient + * @param OrderRepositoryInterface $orderRepository + * @param CaseRepositoryInterface $caseRepository */ public function __construct( CreateCaseBuilderInterface $createCaseBuilder, - ApiClient $apiClient + ApiClient $apiClient, + OrderRepositoryInterface $orderRepository, + CaseRepositoryInterface $caseRepository ) { $this->createCaseBuilder = $createCaseBuilder; $this->apiClient = $apiClient; + $this->orderRepository = $orderRepository; + $this->caseRepository = $caseRepository; } /** @@ -78,11 +95,13 @@ public function __construct( public function createCase($orderId) { $caseParams = $this->createCaseBuilder->build($orderId); + $storeId = $this->getStoreIdFromOrder($orderId); $caseCreationResult = $this->apiClient->makeApiCall( '/cases', 'POST', - $caseParams + $caseParams, + $storeId ); if (!isset($caseCreationResult['investigationId'])) { @@ -102,12 +121,14 @@ public function createCase($orderId) */ public function submitCaseForGuarantee($signifydCaseId) { + $storeId = $this->getStoreIdFromCase($signifydCaseId); $guaranteeCreationResult = $this->apiClient->makeApiCall( '/guarantees', 'POST', [ 'caseId' => $signifydCaseId, - ] + ], + $storeId ); $disposition = $this->processDispositionResult($guaranteeCreationResult); @@ -124,12 +145,14 @@ public function submitCaseForGuarantee($signifydCaseId) */ public function cancelGuarantee($caseId) { + $storeId = $this->getStoreIdFromCase($caseId); $result = $this->apiClient->makeApiCall( '/cases/' . $caseId . '/guarantee', 'PUT', [ 'guaranteeDisposition' => self::GUARANTEE_CANCELED - ] + ], + $storeId ); $disposition = $this->processDispositionResult($result); @@ -172,4 +195,31 @@ private function processDispositionResult(array $result) return $disposition; } + + /** + * Returns store id by case. + * + * @param int $caseId + * @return int|null + */ + private function getStoreIdFromCase(int $caseId) + { + $case = $this->caseRepository->getByCaseId($caseId); + $orderId = $case->getOrderId(); + + return $this->getStoreIdFromOrder($orderId); + } + + /** + * Returns store id from order. + * + * @param int $orderId + * @return int|null + */ + private function getStoreIdFromOrder(int $orderId) + { + $order = $this->orderRepository->get($orderId); + + return $order->getStoreId(); + } } diff --git a/app/code/Magento/Signifyd/Observer/PlaceOrder.php b/app/code/Magento/Signifyd/Observer/PlaceOrder.php index 3798522dbe506..8415bc006b8aa 100644 --- a/app/code/Magento/Signifyd/Observer/PlaceOrder.php +++ b/app/code/Magento/Signifyd/Observer/PlaceOrder.php @@ -55,10 +55,6 @@ public function __construct( */ public function execute(Observer $observer) { - if (!$this->signifydIntegrationConfig->isActive()) { - return; - } - $orders = $this->extractOrders( $observer->getEvent() ); @@ -68,7 +64,10 @@ public function execute(Observer $observer) } foreach ($orders as $order) { - $this->createCaseForOrder($order); + $storeId = $order->getStoreId(); + if ($this->signifydIntegrationConfig->isActive($storeId)) { + $this->createCaseForOrder($order); + } } } diff --git a/app/code/Magento/Signifyd/Test/Unit/Model/SignifydGateway/Client/HttpClientFactoryTest.php b/app/code/Magento/Signifyd/Test/Unit/Model/SignifydGateway/Client/HttpClientFactoryTest.php index 776e8a75b9646..4aefd63355773 100644 --- a/app/code/Magento/Signifyd/Test/Unit/Model/SignifydGateway/Client/HttpClientFactoryTest.php +++ b/app/code/Magento/Signifyd/Test/Unit/Model/SignifydGateway/Client/HttpClientFactoryTest.php @@ -101,14 +101,17 @@ public function testCreateHttpClient() public function testCreateWithParams() { $param = ['id' => 1]; + $storeId = 1; $json = '{"id":1}'; $this->config->expects($this->once()) ->method('getApiKey') + ->with($storeId) ->willReturn('testKey'); $this->config->expects($this->once()) ->method('getApiUrl') + ->with($storeId) ->willReturn(self::$dummy); $this->dataEncoder->expects($this->once()) @@ -121,7 +124,7 @@ public function testCreateWithParams() ->with($this->equalTo($json), 'application/json') ->willReturnSelf(); - $client = $this->httpClient->create('url', 'method', $param); + $client = $this->httpClient->create('url', 'method', $param, $storeId); $this->assertInstanceOf(ZendClient::class, $client); } diff --git a/app/code/Magento/Signifyd/Test/Unit/Model/SignifydGateway/GatewayTest.php b/app/code/Magento/Signifyd/Test/Unit/Model/SignifydGateway/GatewayTest.php index c34e64f469f77..2a05189e0e393 100644 --- a/app/code/Magento/Signifyd/Test/Unit/Model/SignifydGateway/GatewayTest.php +++ b/app/code/Magento/Signifyd/Test/Unit/Model/SignifydGateway/GatewayTest.php @@ -5,7 +5,10 @@ */ namespace Magento\Signifyd\Test\Unit\Model\SignifydGateway; -use \PHPUnit\Framework\TestCase as TestCase; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Signifyd\Api\CaseRepositoryInterface; +use Magento\Signifyd\Api\Data\CaseInterface; use PHPUnit_Framework_MockObject_MockObject as MockObject; use Magento\Signifyd\Model\SignifydGateway\Gateway; use Magento\Signifyd\Model\SignifydGateway\GatewayException; @@ -30,6 +33,16 @@ class GatewayTest extends \PHPUnit\Framework\TestCase */ private $gateway; + /** + * @var OrderRepositoryInterface|MockObject + */ + private $orderRepository; + + /** + * @var CaseRepositoryInterface|MockObject + */ + private $caseRepository; + public function setUp() { $this->createCaseBuilder = $this->getMockBuilder(CreateCaseBuilderInterface::class) @@ -39,16 +52,27 @@ public function setUp() ->disableOriginalConstructor() ->getMock(); + $this->orderRepository = $this->getMockBuilder(OrderRepositoryInterface::class) + ->getMockForAbstractClass(); + + $this->caseRepository= $this->getMockBuilder(CaseRepositoryInterface::class) + ->getMockForAbstractClass(); + $this->gateway = new Gateway( $this->createCaseBuilder, - $this->apiClient + $this->apiClient, + $this->orderRepository, + $this->caseRepository ); } public function testCreateCaseForSpecifiedOrder() { $dummyOrderId = 1; + $dummyStoreId = 2; $dummySignifydInvestigationId = 42; + + $this->withOrderEntity($dummyOrderId, $dummyStoreId); $this->apiClient ->method('makeApiCall') ->willReturn([ @@ -68,7 +92,10 @@ public function testCreateCaseForSpecifiedOrder() public function testCreateCaseCallsValidApiMethod() { $dummyOrderId = 1; + $dummyStoreId = 2; $dummySignifydInvestigationId = 42; + + $this->withOrderEntity($dummyOrderId, $dummyStoreId); $this->createCaseBuilder ->method('build') ->willReturn([]); @@ -79,7 +106,8 @@ public function testCreateCaseCallsValidApiMethod() ->with( $this->equalTo('/cases'), $this->equalTo('POST'), - $this->isType('array') + $this->isType('array'), + $this->equalTo($dummyStoreId) ) ->willReturn([ 'investigationId' => $dummySignifydInvestigationId @@ -92,7 +120,10 @@ public function testCreateCaseCallsValidApiMethod() public function testCreateCaseNormalFlow() { $dummyOrderId = 1; + $dummyStoreId = 2; $dummySignifydInvestigationId = 42; + + $this->withOrderEntity($dummyOrderId, $dummyStoreId); $this->createCaseBuilder ->method('build') ->willReturn([]); @@ -113,7 +144,10 @@ public function testCreateCaseNormalFlow() public function testCreateCaseWithFailedApiCall() { $dummyOrderId = 1; + $dummyStoreId = 2; $apiCallFailureMessage = 'Api call failed'; + + $this->withOrderEntity($dummyOrderId, $dummyStoreId); $this->createCaseBuilder ->method('build') ->willReturn([]); @@ -129,6 +163,9 @@ public function testCreateCaseWithFailedApiCall() public function testCreateCaseWithMissedResponseRequiredData() { $dummyOrderId = 1; + $dummyStoreId = 2; + + $this->withOrderEntity($dummyOrderId, $dummyStoreId); $this->createCaseBuilder ->method('build') ->willReturn([]); @@ -145,7 +182,10 @@ public function testCreateCaseWithMissedResponseRequiredData() public function testCreateCaseWithAdditionalResponseData() { $dummyOrderId = 1; + $dummyStoreId = 2; $dummySignifydInvestigationId = 42; + + $this->withOrderEntity($dummyOrderId, $dummyStoreId); $this->createCaseBuilder ->method('build') ->willReturn([]); @@ -167,8 +207,10 @@ public function testCreateCaseWithAdditionalResponseData() public function testSubmitCaseForGuaranteeCallsValidApiMethod() { $dummySygnifydCaseId = 42; + $dummyStoreId = 1; $dummyDisposition = 'APPROVED'; + $this->withCaseEntity($dummySygnifydCaseId, $dummyStoreId); $this->apiClient ->expects($this->atLeastOnce()) ->method('makeApiCall') @@ -177,7 +219,8 @@ public function testSubmitCaseForGuaranteeCallsValidApiMethod() $this->equalTo('POST'), $this->equalTo([ 'caseId' => $dummySygnifydCaseId - ]) + ]), + $this->equalTo($dummyStoreId) )->willReturn([ 'disposition' => $dummyDisposition ]); @@ -189,8 +232,10 @@ public function testSubmitCaseForGuaranteeCallsValidApiMethod() public function testSubmitCaseForGuaranteeWithFailedApiCall() { $dummySygnifydCaseId = 42; + $dummyStoreId = 1; $apiCallFailureMessage = 'Api call failed'; + $this->withCaseEntity($dummySygnifydCaseId, $dummyStoreId); $this->apiClient ->method('makeApiCall') ->willThrowException(new ApiCallException($apiCallFailureMessage)); @@ -204,10 +249,12 @@ public function testSubmitCaseForGuaranteeWithFailedApiCall() public function testSubmitCaseForGuaranteeReturnsDisposition() { $dummySygnifydCaseId = 42; + $dummyStoreId = 1; $dummyDisposition = 'APPROVED'; $dummyGuaranteeId = 123; $dummyRereviewCount = 0; + $this->withCaseEntity($dummySygnifydCaseId, $dummyStoreId); $this->apiClient ->method('makeApiCall') ->willReturn([ @@ -227,9 +274,11 @@ public function testSubmitCaseForGuaranteeReturnsDisposition() public function testSubmitCaseForGuaranteeWithMissedDisposition() { $dummySygnifydCaseId = 42; + $dummyStoreId = 1; $dummyGuaranteeId = 123; $dummyRereviewCount = 0; + $this->withCaseEntity($dummySygnifydCaseId, $dummyStoreId); $this->apiClient ->method('makeApiCall') ->willReturn([ @@ -244,8 +293,10 @@ public function testSubmitCaseForGuaranteeWithMissedDisposition() public function testSubmitCaseForGuaranteeWithUnexpectedDisposition() { $dummySygnifydCaseId = 42; + $dummyStoreId = 1; $dummyUnexpectedDisposition = 'UNEXPECTED'; + $this->withCaseEntity($dummySygnifydCaseId, $dummyStoreId); $this->apiClient ->method('makeApiCall') ->willReturn([ @@ -263,7 +314,9 @@ public function testSubmitCaseForGuaranteeWithUnexpectedDisposition() public function testSubmitCaseForGuaranteeWithExpectedDisposition($dummyExpectedDisposition) { $dummySygnifydCaseId = 42; + $dummyStoreId = 1; + $this->withCaseEntity($dummySygnifydCaseId, $dummyStoreId); $this->apiClient ->method('makeApiCall') ->willReturn([ @@ -290,11 +343,20 @@ public function testSubmitCaseForGuaranteeWithExpectedDisposition($dummyExpected public function testCancelGuarantee() { $caseId = 123; + $dummyStoreId = 1; + $this->withCaseEntity($caseId, $dummyStoreId); $this->apiClient->expects(self::once()) ->method('makeApiCall') - ->with('/cases/' . $caseId . '/guarantee', 'PUT', ['guaranteeDisposition' => Gateway::GUARANTEE_CANCELED]) - ->willReturn(['disposition' => Gateway::GUARANTEE_CANCELED]); + ->with( + '/cases/' . $caseId . '/guarantee', + 'PUT', + ['guaranteeDisposition' => Gateway::GUARANTEE_CANCELED], + $dummyStoreId + ) + ->willReturn( + ['disposition' => Gateway::GUARANTEE_CANCELED] + ); $result = $this->gateway->cancelGuarantee($caseId); self::assertEquals(Gateway::GUARANTEE_CANCELED, $result); @@ -310,10 +372,17 @@ public function testCancelGuarantee() public function testCancelGuaranteeWithUnexpectedDisposition() { $caseId = 123; + $dummyStoreId = 1; + $this->withCaseEntity($caseId, $dummyStoreId); $this->apiClient->expects(self::once()) ->method('makeApiCall') - ->with('/cases/' . $caseId . '/guarantee', 'PUT', ['guaranteeDisposition' => Gateway::GUARANTEE_CANCELED]) + ->with( + '/cases/' . $caseId . '/guarantee', + 'PUT', + ['guaranteeDisposition' => Gateway::GUARANTEE_CANCELED], + $dummyStoreId + ) ->willReturn(['disposition' => Gateway::GUARANTEE_DECLINED]); $result = $this->gateway->cancelGuarantee($caseId); @@ -331,4 +400,46 @@ public function supportedGuaranteeDispositionsProvider() 'UNREQUESTED' => ['UNREQUESTED'], ]; } + + /** + * Specifies order entity mock execution. + * + * @param int $orderId + * @param int $storeId + * @return void + */ + private function withOrderEntity(int $orderId, int $storeId): void + { + $orderEntity = $this->getMockBuilder(OrderInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $orderEntity->method('getStoreId') + ->willReturn($storeId); + $this->orderRepository->method('get') + ->with($orderId) + ->willReturn($orderEntity); + } + + /** + * Specifies case entity mock execution. + * + * @param int $caseId + * @param int $storeId + * @return void + */ + private function withCaseEntity(int $caseId, int $storeId): void + { + $orderId = 1; + + $caseEntity = $this->getMockBuilder(CaseInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $caseEntity->method('getOrderId') + ->willReturn($orderId); + $this->caseRepository->method('getByCaseId') + ->with($caseId) + ->willReturn($caseEntity); + + $this->withOrderEntity($orderId, $storeId); + } } diff --git a/app/code/Magento/Signifyd/Test/Unit/Observer/PlaceOrderTest.php b/app/code/Magento/Signifyd/Test/Unit/Observer/PlaceOrderTest.php index e2870953ec280..4e7edddf7b948 100644 --- a/app/code/Magento/Signifyd/Test/Unit/Observer/PlaceOrderTest.php +++ b/app/code/Magento/Signifyd/Test/Unit/Observer/PlaceOrderTest.php @@ -97,7 +97,10 @@ protected function setUp() */ public function testExecuteWithDisabledModule() { - $this->withActiveSignifydIntegration(false); + $orderId = 1; + $storeId = 2; + $this->withActiveSignifydIntegration(false, $storeId); + $this->withOrderEntity($orderId, $storeId); $this->creationService->expects(self::never()) ->method('createForOrder'); @@ -113,7 +116,7 @@ public function testExecuteWithDisabledModule() public function testExecuteWithoutOrder() { $this->withActiveSignifydIntegration(true); - $this->withOrderEntity(null); + $this->withOrderEntity(null, null); $this->creationService->expects(self::never()) ->method('createForOrder'); @@ -129,8 +132,9 @@ public function testExecuteWithoutOrder() public function testExecuteWithOfflinePayment() { $orderId = 1; - $this->withActiveSignifydIntegration(true); - $this->withOrderEntity($orderId); + $storeId = 2; + $this->withActiveSignifydIntegration(true, $storeId); + $this->withOrderEntity($orderId, $storeId); $this->withAvailablePaymentMethod(false); $this->creationService->expects(self::never()) @@ -147,10 +151,11 @@ public function testExecuteWithOfflinePayment() public function testExecuteWithFailedCaseCreation() { $orderId = 1; + $storeId = 2; $exceptionMessage = __('Case with the same order id already exists.'); - $this->withActiveSignifydIntegration(true); - $this->withOrderEntity($orderId); + $this->withActiveSignifydIntegration(true, $storeId); + $this->withOrderEntity($orderId, $storeId); $this->withAvailablePaymentMethod(true); $this->creationService->method('createForOrder') @@ -172,9 +177,10 @@ public function testExecuteWithFailedCaseCreation() public function testExecute() { $orderId = 1; + $storeId = 2; - $this->withActiveSignifydIntegration(true); - $this->withOrderEntity($orderId); + $this->withActiveSignifydIntegration(true, $storeId); + $this->withOrderEntity($orderId, $storeId); $this->withAvailablePaymentMethod(true); $this->creationService @@ -190,10 +196,11 @@ public function testExecute() /** * Specifies order entity mock execution. * - * @param int $orderId + * @param int|null $orderId + * @param int|null $storeId * @return void */ - private function withOrderEntity($orderId) + private function withOrderEntity($orderId, $storeId): void { $this->orderEntity = $this->getMockBuilder(OrderInterface::class) ->disableOriginalConstructor() @@ -201,6 +208,8 @@ private function withOrderEntity($orderId) $this->orderEntity->method('getEntityId') ->willReturn($orderId); + $this->orderEntity->method('getStoreId') + ->willReturn($storeId); $this->observer->method('getEvent') ->willReturn($this->event); @@ -214,11 +223,13 @@ private function withOrderEntity($orderId) * Specifies config mock execution. * * @param bool $isActive + * @param int|null $storeId * @return void */ - private function withActiveSignifydIntegration($isActive) + private function withActiveSignifydIntegration(bool $isActive, $storeId = null): void { $this->config->method('isActive') + ->with($storeId) ->willReturn($isActive); } diff --git a/app/code/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Plugin/Save.php b/app/code/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Plugin/Save.php index dfda76372df1f..383c97a166d34 100644 --- a/app/code/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Plugin/Save.php +++ b/app/code/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Plugin/Save.php @@ -11,7 +11,7 @@ use Magento\Swatches\Model\Swatch; /** - * Class Save + * Plugin for product attribute save controller. */ class Save { @@ -24,7 +24,17 @@ class Save public function beforeDispatch(Attribute\Save $subject, RequestInterface $request) { $data = $request->getPostValue(); + if (isset($data['frontend_input'])) { + //Data is serialized to overcome issues caused by max_input_vars value if it's modification is unavailable. + //See subject controller code and comments for more info. + if (isset($data['serialized_swatch_values']) + && in_array($data['frontend_input'], ['swatch_visual', 'swatch_text']) + ) { + $data['serialized_options'] = $data['serialized_swatch_values']; + unset($data['serialized_swatch_values']); + } + switch ($data['frontend_input']) { case 'swatch_visual': $data[Swatch::SWATCH_INPUT_TYPE_KEY] = Swatch::SWATCH_INPUT_TYPE_VISUAL; diff --git a/app/code/Magento/Swatches/view/adminhtml/web/js/product-attributes.js b/app/code/Magento/Swatches/view/adminhtml/web/js/product-attributes.js index 1187e6bc4fdaa..01411523108cf 100644 --- a/app/code/Magento/Swatches/view/adminhtml/web/js/product-attributes.js +++ b/app/code/Magento/Swatches/view/adminhtml/web/js/product-attributes.js @@ -414,6 +414,8 @@ define([ }; $(function () { + var editForm = $('#edit_form'); + $('#frontend_input').bind('change', function () { swatchProductAttributes.bindAttributeInputType(); }); @@ -427,6 +429,34 @@ define([ $('.attribute-popup .collapse, [data-role="advanced_fieldset-content"]') .collapsable() .collapse('hide'); + + editForm.on('submit', function () { + var activePanel, + swatchValues = [], + swatchVisualPanel = $('#swatch-visual-options-panel'), + swatchTextPanel = $('#swatch-text-options-panel'); + + activePanel = swatchTextPanel.is(':visible') ? swatchTextPanel : swatchVisualPanel; + + activePanel + .find('table input') + .each(function () { + swatchValues.push(this.name + '=' + $(this).val()); + }); + + $('') + .attr({ + type: 'hidden', + name: 'serialized_swatch_values' + }) + .val(JSON.stringify(swatchValues)) + .prependTo(editForm); + + [swatchVisualPanel, swatchTextPanel].forEach(function (el) { + $(el).find('table') + .replaceWith($('
').text($.mage.__('Sending swatch values as package.'))); + }); + }); }); window.saveAttributeInNewSet = swatchProductAttributes.saveAttributeInNewSet; diff --git a/app/code/Magento/Ui/view/base/web/js/modal/modal.js b/app/code/Magento/Ui/view/base/web/js/modal/modal.js index af2b31f7eab43..6c9b4b89bec7a 100644 --- a/app/code/Magento/Ui/view/base/web/js/modal/modal.js +++ b/app/code/Magento/Ui/view/base/web/js/modal/modal.js @@ -336,11 +336,12 @@ define([ * Set z-index and margin for modal and overlay. */ _setActive: function () { - var zIndex = this.modal.zIndex(); + var zIndex = this.modal.zIndex(), + baseIndex = zIndex + this._getVisibleCount(); + this.overlay.zIndex(++baseIndex); this.prevOverlayIndex = this.overlay.zIndex(); - this.modal.zIndex(zIndex + this._getVisibleCount()); - this.overlay.zIndex(zIndex + (this._getVisibleCount() - 1)); + this.modal.zIndex(this.overlay.zIndex() + 1); if (this._getVisibleSlideCount()) { this.modal.css('marginLeft', this.options.modalLeftMargin * this._getVisibleSlideCount()); @@ -354,7 +355,14 @@ define([ this.modal.removeAttr('style'); if (this.overlay) { - this.overlay.zIndex(this.prevOverlayIndex); + // In cases when one modal is closed but there is another modal open (e.g. admin notifications) + // to avoid collisions between overlay and modal zIndexes + // overlay zIndex is set to be less than modal one + if (this._getVisibleCount() === 1) { + this.overlay.zIndex(this.prevOverlayIndex - 1); + } else { + this.overlay.zIndex(this.prevOverlayIndex); + } } }, diff --git a/app/code/Magento/Wishlist/view/frontend/templates/item/list.phtml b/app/code/Magento/Wishlist/view/frontend/templates/item/list.phtml index c2172afab94de..ef02768c46a83 100644 --- a/app/code/Magento/Wishlist/view/frontend/templates/item/list.phtml +++ b/app/code/Magento/Wishlist/view/frontend/templates/item/list.phtml @@ -19,7 +19,7 @@ $columns = $block->getColumns();
  • - setItem($item); echo $column->toHtml($item);?> + setItem($item)->toHtml();?>
  • diff --git a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_toolbar.less b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_toolbar.less index a0bd5e695a621..aceccb06d47f7 100644 --- a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_toolbar.less +++ b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_toolbar.less @@ -188,11 +188,10 @@ .lib-icon-font-symbol(@icon-list); } - .limiter { - float: right; - - .products.wrapper ~ .toolbar & { + .toolbar { + .products.wrapper ~ & .limiter { display: block; + float: right; } } } diff --git a/app/design/frontend/Magento/blank/web/css/source/_extends.less b/app/design/frontend/Magento/blank/web/css/source/_extends.less index a36934111eb06..c177f91e9e7e8 100644 --- a/app/design/frontend/Magento/blank/web/css/source/_extends.less +++ b/app/design/frontend/Magento/blank/web/css/source/_extends.less @@ -1402,7 +1402,7 @@ &.active { > .title { .lib-icon-font-symbol( - @_icon-font-content: @icon-prev, + @_icon-font-content: @icon-up, @_icon-font-position: after ); } diff --git a/app/design/frontend/Magento/blank/web/css/source/_sections.less b/app/design/frontend/Magento/blank/web/css/source/_sections.less index d5d6e5a3d105c..f0a3518c92a8b 100644 --- a/app/design/frontend/Magento/blank/web/css/source/_sections.less +++ b/app/design/frontend/Magento/blank/web/css/source/_sections.less @@ -20,9 +20,7 @@ .media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) { .product.data.items { - .lib-data-tabs( - @_tab-content-border-top-status: true - ); + .lib-data-tabs(); } } diff --git a/app/design/frontend/Magento/blank/web/css/source/_variables.less b/app/design/frontend/Magento/blank/web/css/source/_variables.less index 5d946dd8a826e..256bc796dba94 100644 --- a/app/design/frontend/Magento/blank/web/css/source/_variables.less +++ b/app/design/frontend/Magento/blank/web/css/source/_variables.less @@ -15,6 +15,16 @@ @font-family-name__base: 'Open Sans'; @font-family__base: @font-family-name__base, @font-family__sans-serif; +// +// Sections variables +// _____________________________________________ + +// +// Tabs +// --------------------------------------------- +@tab-content__border-top-status: true; + + // // Sidebar // --------------------------------------------- diff --git a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less index 01b8c7be93aec..d0382d34d39fc 100644 --- a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less +++ b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less @@ -167,11 +167,11 @@ .column.main { .product { &-items { - margin-left: -@indent__base; + margin-left: 0; } &-item { - padding-left: @indent__base; + padding-left: 0; } } } @@ -277,8 +277,8 @@ } .sidebar & { .product-item-photo { - top: 9px; left: 9px; + top: 9px; } } } @@ -310,8 +310,8 @@ .actions-primary + .actions-secondary { display: table-cell; padding-left: 10px; - width: 50%; vertical-align: middle; + width: 50%; > .action { margin-right: 10px; @@ -443,7 +443,7 @@ .product-item { margin-left: calc(~'(100% - 4 * 24.439%) / 3'); - padding: 0; + padding: 5px; width: 24.439%; &:nth-child(4n + 1) { diff --git a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_toolbar.less b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_toolbar.less index 580abf264cadc..0997b9739125d 100644 --- a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_toolbar.less +++ b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_toolbar.less @@ -233,10 +233,10 @@ } } - .limiter { - float: right; - .products.wrapper ~ .toolbar & { + .toolbar { + .products.wrapper ~ & .limiter { display: block; + float: right; } } } diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Analytics/Data/UserData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Analytics/Data/UserData.xml index 65ddfa7bb9559..c15e6004ed64a 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Analytics/Data/UserData.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Analytics/Data/UserData.xml @@ -18,5 +18,16 @@ en_US true 123123q + + + restrictedWebUser + restricted + webUser + restrictedWebUser@example.com + 123123q + 123123q + en_US + true + 123123q diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Data/BundleLinkData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Data/BundleLinkData.xml new file mode 100644 index 0000000000000..65add76a12af3 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Data/BundleLinkData.xml @@ -0,0 +1,21 @@ + + + + + + + + + 1 + 1 + 1.11 + 1 + 1 + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Data/BundleOptionData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Data/BundleOptionData.xml new file mode 100644 index 0000000000000..02f70ec15cab8 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Data/BundleOptionData.xml @@ -0,0 +1,21 @@ + + + + + + bundle-option-dropdown + true + dropdown + 1 + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Data/CustomAttributeData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Data/CustomAttributeData.xml new file mode 100644 index 0000000000000..c7f150e7ad6fb --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Data/CustomAttributeData.xml @@ -0,0 +1,22 @@ + + + + + price_type + 0 + + + price_type + 1 + + + price_view + 1 + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Data/ProductData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Data/ProductData.xml index a8598ded6ec17..69741ccd5021a 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Data/ProductData.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Data/ProductData.xml @@ -19,4 +19,18 @@ 1 bundleproduct + + Api Bundle Product + api-bundle-product + bundle + 4 + 4 + 1 + api-bundle-product + EavStockItem + ApiProductDescription + ApiProductShortDescription + CustomAttributeDynamicPrice + CustomAttributePriceView + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Metadata/bundle_link-meta.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Metadata/bundle_link-meta.xml new file mode 100644 index 0000000000000..be881a7e98d65 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Metadata/bundle_link-meta.xml @@ -0,0 +1,24 @@ + + + + + + application/json + + string + integer + integer + integer + boolean + number + integer + integer + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Metadata/bundle_option-meta.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Metadata/bundle_option-meta.xml new file mode 100644 index 0000000000000..991c01ec4c6f0 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Metadata/bundle_option-meta.xml @@ -0,0 +1,21 @@ + + + + + + application/json + + string + boolean + string + integer + string + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Metadata/bundle_options-meta.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Metadata/bundle_options-meta.xml new file mode 100644 index 0000000000000..a81d5dda6a40b --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Metadata/bundle_options-meta.xml @@ -0,0 +1,14 @@ + + + + + + application/json + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/AddProductToCartActionGroup.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/AddProductToCartActionGroup.xml index 6caa4fef770b1..9380c3052a5f5 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/AddProductToCartActionGroup.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/AddProductToCartActionGroup.xml @@ -14,5 +14,6 @@ + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/AdminCategoryActionGroup.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/AdminCategoryActionGroup.xml index 96e40e348a6f2..e8e5d0730fd7f 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/AdminCategoryActionGroup.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/AdminCategoryActionGroup.xml @@ -57,7 +57,7 @@ - + @@ -81,7 +81,7 @@ - + @@ -118,6 +118,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -128,6 +190,26 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/AdminProductActionGroup.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/AdminProductActionGroup.xml index 22835c7fa2e54..3ef8b961a81af 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/AdminProductActionGroup.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/AdminProductActionGroup.xml @@ -77,14 +77,14 @@ - + - - + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/CustomOptionsActionGroup.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/CustomOptionsActionGroup.xml new file mode 100644 index 0000000000000..0409c3f195013 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/CustomOptionsActionGroup.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Data/ProductData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Data/ProductData.xml index 34110d0417661..3c6e7d3ddc319 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Data/ProductData.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Data/ProductData.xml @@ -154,12 +154,13 @@ EavStockItem CustomAttributeProductUrlKey - + Image1 1.00 Upload File Yes magento-logo.png + magento-logo MagentoLogo @@ -281,4 +282,41 @@ EavStockItem ApiProductNewsFromDate + + Double Quote" + doubleQuote + simple + 4 + 10.00 + 4 + 1 + 1000 + 1 + EavStockItem + + + + + + + + + + + + api-simple-product + simple + 4 + 4 + Api Simple Product + 1.00 + + + api-simple-product + simple + 4 + 4 + Api Simple Product + 100.00 + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Data/ProductOptionData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Data/ProductOptionData.xml index f2f6e1a6f552f..2abc557d44d96 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Data/ProductOptionData.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Data/ProductOptionData.xml @@ -18,6 +18,16 @@ fixed 0 + + + OptionField2 + field + true + 1 + 20 + fixed + 0 + OptionArea diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Metadata/product-meta.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Metadata/product-meta.xml index edbf1133d4fae..b04b4bb98c854 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Metadata/product-meta.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Metadata/product-meta.xml @@ -60,7 +60,10 @@ boolean - + + application/json + + application/json @@ -115,7 +118,43 @@ boolean - + + application/json + + + application/json + + + + application/json + + string + string + integer + number + integer + integer + string + string + string + integer + product_extension_attribute + + product_link + + + custom_attribute_array + + + product_option + + + + + + application/json + + application/json diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Metadata/product_link-meta.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Metadata/product_link-meta.xml index a6d418dc675e7..899dc3a7f4a8c 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Metadata/product_link-meta.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Metadata/product_link-meta.xml @@ -14,9 +14,9 @@ string string integer - - product_link_extension_attribute - + + integer + string @@ -24,8 +24,8 @@ string string integer - - product_link_extension_attribute - + + integer + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Metadata/product_links-meta.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Metadata/product_links-meta.xml new file mode 100644 index 0000000000000..34e8d0fca6833 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Metadata/product_links-meta.xml @@ -0,0 +1,21 @@ + + + + + + application/json + + product_link + + + + application/json + product_link + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminCategoryBasicFieldSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminCategoryBasicFieldSection.xml index 26f56f6176f98..7de2390e5348a 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminCategoryBasicFieldSection.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminCategoryBasicFieldSection.xml @@ -11,9 +11,12 @@
    + + +
    diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminCategoryMainActionsSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminCategoryMainActionsSection.xml index 3c806fbbea63f..e726e5bfb7c63 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminCategoryMainActionsSection.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminCategoryMainActionsSection.xml @@ -11,5 +11,8 @@
    + + +
    diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminCategorySEOSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminCategorySEOSection.xml index 679cd2d7bee0d..0e01660d5fce9 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminCategorySEOSection.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminCategorySEOSection.xml @@ -11,6 +11,8 @@
    + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminCategorySidebarTreeSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminCategorySidebarTreeSection.xml index e914c80c3e6ac..5e080bbb7fdba 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminCategorySidebarTreeSection.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminCategorySidebarTreeSection.xml @@ -14,5 +14,7 @@ + +
    diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductAttributeSetSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductAttributeSetSection.xml index 19e59d1cd22e6..011b2fce3a780 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductAttributeSetSection.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductAttributeSetSection.xml @@ -17,4 +17,8 @@
    +
    + + +
    diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductCategoryCreationSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductCategoryCreationSection.xml new file mode 100644 index 0000000000000..a4566115099ef --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductCategoryCreationSection.xml @@ -0,0 +1,22 @@ + + + + +
    + + + + + + + + + +
    +
    diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductContentSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductContentSection.xml new file mode 100644 index 0000000000000..12a00ae8b3777 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductContentSection.xml @@ -0,0 +1,16 @@ + + + + +
    + + + +
    +
    diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductCustomizableOptionsSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductCustomizableOptionsSection.xml index 702de0a8e1803..cb80dade856a7 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductCustomizableOptionsSection.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductCustomizableOptionsSection.xml @@ -29,5 +29,14 @@ + + + + + + + + + \ No newline at end of file diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductImagesSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductImagesSection.xml index a9749ac677964..b31bcaf04e4b6 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductImagesSection.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductImagesSection.xml @@ -14,5 +14,6 @@ + \ No newline at end of file diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/StorefrontCategoryProductSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/StorefrontCategoryProductSection.xml index e736378ca8ca0..e8df8650e8dd8 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/StorefrontCategoryProductSection.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/StorefrontCategoryProductSection.xml @@ -19,5 +19,6 @@ + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/StorefrontProductInfoMainSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/StorefrontProductInfoMainSection.xml index 5abc388a7d65d..a981f125c5e90 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/StorefrontProductInfoMainSection.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/StorefrontProductInfoMainSection.xml @@ -18,12 +18,20 @@ + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminAddImageForCategoryTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminAddImageForCategoryTest.xml index ba90296d12380..04e34871c8b73 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminAddImageForCategoryTest.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminAddImageForCategoryTest.xml @@ -43,6 +43,6 @@ - + \ No newline at end of file diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminCreateCategoryFromProductPageTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminCreateCategoryFromProductPageTest.xml new file mode 100644 index 0000000000000..7c81f4472e92a --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminCreateCategoryFromProductPageTest.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminUpdateCategoryStoreUrlKeyTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminUpdateCategoryStoreUrlKeyTest.xml new file mode 100644 index 0000000000000..e29890befd860 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminUpdateCategoryStoreUrlKeyTest.xml @@ -0,0 +1,77 @@ + + + + + + + + + + <description value="SEO-friendly URL should update regardless of scope or redirect change."/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-92338"/> + <group value="category"/> + </annotations> + <after> + <actionGroup ref="DeleteCategory" stepKey="deleteCategory"> + <argument name="categoryEntity" value="_defaultCategory"/> + </actionGroup> + <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> + </after> + + <!-- Create category, change store view to default --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="navigateToCategoryPage"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <actionGroup ref="CreateCategory" stepKey="createCategory"> + <argument name="categoryEntity" value="_defaultCategory"/> + </actionGroup> + + <!--Switch to "Default Store View" scope--> + <actionGroup ref="switchCategoryStoreView" stepKey="SwitchStoreView"> + <argument name="Store" value="_defaultStore.name"/> + <argument name="CatName" value="_defaultCategory.name"/> + </actionGroup> + <!--See "Use Default Value" checkboxes--> + <seeElement selector="{{AdminCategoryBasicFieldSection.enableUseDefault}}" stepKey="seeUseDefaultEnable"/> + <seeElement selector="{{AdminCategoryBasicFieldSection.includeInMenuUseDefault}}" stepKey="seeUseDefaultMenu"/> + <seeElement selector="{{AdminCategoryBasicFieldSection.categoryNameUseDefault}}" stepKey="seeUseDefaultName"/> + <!-- Update SEO key, uncheck "Create Redirect", confirm in frontend --> + <click selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="openSeoSection"/> + <uncheckOption selector="{{AdminCategorySEOSection.UrlKeyDefaultValueCheckbox}}" stepKey="uncheckUseDefaultUrlKey"/> + <fillField selector="{{AdminCategorySEOSection.UrlKeyInput}}" userInput="{{_defaultCategory.name_lwr}}-hattest" stepKey="enterURLKey"/> + <uncheckOption selector="{{AdminCategorySEOSection.UrlKeyRedirectCheckbox}}" stepKey="uncheckRedirect1"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveCategoryAfterFirstSeoUpdate"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="assertSuccessMessage"/> + <amOnPage url="" stepKey="goToStorefront"/> + <waitForPageLoad stepKey="waitForFrontendLoad"/> + <click stepKey="clickCategory" selector="{{StorefrontHeaderSection.NavigationCategoryByName(_defaultCategory.name)}}"/> + <see selector="{{StorefrontCategoryMainSection.CategoryTitle}}" userInput="{{_defaultCategory.name}}" stepKey="assertCategoryOnStorefront"/> + <seeInTitle userInput="{{_defaultCategory.name}}" stepKey="seeCategoryNameInTitle"/> + <seeInCurrentUrl stepKey="verifyUrlKey" url="{{_defaultCategory.name_lwr}}-hattest.html"/> + + <!-- Update SEO key to original, uncheck "Create Redirect", confirm in frontend, delete category --> + <!--Switch to "Default Store View" scope--> + <actionGroup ref="switchCategoryStoreView" stepKey="SwitchStoreView2"> + <argument name="Store" value="_defaultStore.name"/> + <argument name="CatName" value="_defaultCategory.name"/> + </actionGroup> + <click selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="openSeoSection2"/> + <fillField selector="{{AdminCategorySEOSection.UrlKeyInput}}" userInput="{{_defaultCategory.name_lwr}}" stepKey="enterOriginalURLKey"/> + <uncheckOption selector="{{AdminCategorySEOSection.UrlKeyRedirectCheckbox}}" stepKey="uncheckRedirect2"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveCategoryAfterOriginalSeoKey"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="assertSuccessMessageAfterOriginalSeoKey"/> + <amOnPage url="" stepKey="goToStorefrontAfterOriginalSeoKey"/> + <waitForPageLoad stepKey="waitForFrontendLoadAfterOriginalSeoKey"/> + <click stepKey="clickCategoryAfterOriginalSeoKey" selector="{{StorefrontHeaderSection.NavigationCategoryByName(_defaultCategory.name)}}"/> + <see selector="{{StorefrontCategoryMainSection.CategoryTitle}}" userInput="{{_defaultCategory.name}}" stepKey="assertCategoryOnStorefront2"/> + <seeInTitle userInput="{{_defaultCategory.name}}" stepKey="seeCategoryNameInTitle2"/> + <seeInCurrentUrl stepKey="verifyUrlKeyAfterOriginalSeoKey" url="{{_defaultCategory.name_lwr}}.html"/> + </test> +</tests> \ No newline at end of file diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdvanceCatalogSearchSimpleProductTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdvanceCatalogSearchSimpleProductTest.xml new file mode 100644 index 0000000000000..a302fa58ec241 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdvanceCatalogSearchSimpleProductTest.xml @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdvanceCatalogSearchSimpleProductByNameTest"> + <annotations> + <features value="Catalog"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search simple product with product name"/> + <description value="Guest customer should be able to advance search simple product with product name"/> + <severity value="MAJOR"/> + <testCaseId value="MC-132"/> + <group value="Catalog"/> + </annotations> + <before> + <createData entity="ApiProductWithDescription" stepKey="product"/> + </before> + <after> + <deleteData createDataKey="product" stepKey="delete"/> + </after> + </test> + <test name="AdvanceCatalogSearchSimpleProductBySkuTest"> + <annotations> + <features value="Catalog"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search simple product with product sku"/> + <description value="Guest customer should be able to advance search simple product with product sku"/> + <severity value="MAJOR"/> + <testCaseId value="MC-133"/> + <group value="Catalog"/> + </annotations> + <before> + <createData entity="ApiProductWithDescription" stepKey="product"/> + </before> + <after> + <deleteData createDataKey="product" stepKey="delete"/> + </after> + </test> + <test name="AdvanceCatalogSearchSimpleProductByDescriptionTest"> + <annotations> + <features value="Catalog"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search simple product with product description"/> + <description value="Guest customer should be able to advance search simple product with product description"/> + <severity value="MAJOR"/> + <testCaseId value="MC-134"/> + <group value="Catalog"/> + </annotations> + <before> + <createData entity="ApiProductWithDescription" stepKey="product"/> + </before> + <after> + <deleteData createDataKey="product" stepKey="delete"/> + </after> + </test> + <test name="AdvanceCatalogSearchSimpleProductByShortDescriptionTest"> + <annotations> + <features value="Catalog"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search simple product with product short description"/> + <description value="Guest customer should be able to advance search simple product with product short description"/> + <severity value="MAJOR"/> + <testCaseId value="MC-135"/> + <group value="Catalog"/> + </annotations> + <before> + <createData entity="ApiProductWithDescription" stepKey="product"/> + </before> + <after> + <deleteData createDataKey="product" stepKey="delete"/> + </after> + </test> + <test name="AdvanceCatalogSearchSimpleProductByPriceTest"> + <annotations> + <features value="Catalog"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search simple product with product price"/> + <description value="Guest customer should be able to advance search simple product with product price"/> + <severity value="MAJOR"/> + <testCaseId value="MC-136"/> + <group value="Catalog"/> + </annotations> + <before> + <createData entity="ApiProductWithDescription" stepKey="product"/> + <getData entity="GetProduct" stepKey="arg1"> + <requiredEntity createDataKey="product"/> + </getData> + <getData entity="GetProduct" stepKey="arg2"> + <requiredEntity createDataKey="product"/> + </getData> + <getData entity="GetProduct" stepKey="arg3"> + <requiredEntity createDataKey="product"/> + </getData> + </before> + <after> + <deleteData createDataKey="product" stepKey="delete"/> + </after> + </test> +</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/EndToEndB2CAdminTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/EndToEndB2CAdminTest.xml index 7ce754e8f009b..0f466ccc3d994 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/EndToEndB2CAdminTest.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/EndToEndB2CAdminTest.xml @@ -39,7 +39,7 @@ <argument name="product" value="SimpleProduct"/> </actionGroup> <actionGroup ref="addProductImage" stepKey="addImageForProductSimple"> - <argument name="image" value="ImageUpload"/> + <argument name="image" value="ProductImage"/> </actionGroup> <actionGroup ref="saveProductForm" stepKey="saveSimpleProduct"/> <click selector="{{AdminProductFormActionSection.backButton}}" stepKey="clickBackToGridSimple"/> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml index cc33059dd10b6..cf7f996736eb3 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml @@ -32,6 +32,7 @@ <!--Create Store view --> <amOnPage url="{{AdminSystemStorePage.url}}" stepKey="amOnAdminSystemStorePage"/> + <waitForPageLoad stepKey="waitForAdminSystemStorePage"/> <click selector="{{AdminStoresMainActionsSection.createStoreViewButton}}" stepKey="createStoreViewButton"/> <waitForPageLoad stepKey="waitForProductPageLoad"/> <selectOption userInput="Second Store" selector="{{AdminNewStoreSection.storeGrpDropdown}}" stepKey="selectStoreGroup"/> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/SimpleProductTwoCustomOptionsTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/SimpleProductTwoCustomOptionsTest.xml new file mode 100644 index 0000000000000..2710002d625d7 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/SimpleProductTwoCustomOptionsTest.xml @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="SimpleProductTwoCustomOptionsTest"> + <annotations> + <features value="Catalog"/> + <stories value="Create simple product with two custom options" /> + <title value="Admin should be able to create simple product with two custom options"/> + <description value="Admin should be able to create simple product with two custom options"/> + <severity value="AVERAGE"/> + <testCaseId value="MC-248"/> + <group value="Catalog"/> + </annotations> + <before> + <!-- log in as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!--Create product--> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> + <waitForPageLoad stepKey="waitForProductIndexPage"/> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateSimpleProduct"> + <argument name="product" value="SimpleProduct3"/> + </actionGroup> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillSimpleProductMain"> + <argument name="product" value="SimpleProduct3"/> + </actionGroup> + </before> + <after> + <!-- Delete the created product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="SimpleProduct3"/> + </actionGroup> + <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + </after> + + <!-- opens the custom option panel and clicks add options --> + <click stepKey="openCustomizableOptions" selector="{{AdminProductCustomizableOptionsSection.customezableOptions}}"/> + <waitForPageLoad stepKey="waitForCustomOptionsOpen"/> + + <!-- Create a custom option with 2 values --> + <actionGroup ref="CreateCustomRadioOptions" stepKey="createCustomOption1"> + <argument name="customOptionName" value="ProductOptionRadiobutton.title"/> + <argument name="productOption" value="ProductOptionField"/> + <argument name="productOption2" value="ProductOptionField2"/> + </actionGroup> + + <!-- Create another custom option with 2 values --> + <actionGroup ref="CreateCustomRadioOptions" stepKey="createCustomOption2"> + <argument name="customOptionName" value="ProductOptionRadiobutton.title"/> + <argument name="productOption" value="ProductOptionField"/> + <argument name="productOption2" value="ProductOptionField2"/> + </actionGroup> + + <!-- Save the product --> + <click stepKey="saveProduct" selector="{{AdminProductFormActionSection.saveButton}}"/> + <waitForPageLoad stepKey="waitForProductSaved"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="assertSuccess"/> + + <!-- navigate to the created product page --> + <amOnPage url="/{{SimpleProduct3.name}}.html" stepKey="goToCreatedProduct"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + + <!-- Check to make sure all of the created names are there --> + <see stepKey="assertNameInFirstOption" selector="{{StorefrontProductInfoMainSection.nthCustomOption('1')}}" userInput="{{ProductOptionField.title}}"/> + <see stepKey="assertNameInSecondOption" selector="{{StorefrontProductInfoMainSection.nthCustomOption('2')}}" userInput="{{ProductOptionField.title}}"/> + <see stepKey="assertSecondNameInFirstOption" selector="{{StorefrontProductInfoMainSection.nthCustomOption('1')}}" userInput="{{ProductOptionField2.title}}"/> + <see stepKey="assertSecondNameInSecondOption" selector="{{StorefrontProductInfoMainSection.nthCustomOption('2')}}" userInput="{{ProductOptionField2.title}}"/> + + <!-- Check to see that all of the created prices are there --> + <see stepKey="assertPriceInFirstOption" selector="{{StorefrontProductInfoMainSection.nthCustomOption('1')}}" userInput="{{ProductOptionField.price}}"/> + <see stepKey="assertPriceInSecondOption" selector="{{StorefrontProductInfoMainSection.nthCustomOption('2')}}" userInput="{{ProductOptionField.price}}"/> + <see stepKey="assertSecondPriceInFirstOption" selector="{{StorefrontProductInfoMainSection.nthCustomOption('1')}}" userInput="{{ProductOptionField2.price}}"/> + <see stepKey="assertSecondPriceInSecondOption" selector="{{StorefrontProductInfoMainSection.nthCustomOption('2')}}" userInput="{{ProductOptionField2.price}}"/> + + <!-- select two of the radio buttons --> + <click stepKey="selectFirstCustomOption" selector="{{StorefrontProductInfoMainSection.nthCustomOptionInput('1','2')}}"/> + <click stepKey="selectSecondCustomOption" selector="{{StorefrontProductInfoMainSection.nthCustomOptionInput('2','1')}}"/> + + <!-- Check that the price has actually changed --> + <see stepKey="assertPriceHasChanged" selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="153.00"/> + </test> +</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/StorefrontProductNameWithDoubleQuote.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/StorefrontProductNameWithDoubleQuote.xml new file mode 100644 index 0000000000000..a848515aee873 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/StorefrontProductNameWithDoubleQuote.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="StorefrontProductNameWithDoubleQuote"> + <annotations> + <title value="Product with double quote in name"/> + <description value="Product with a double quote in the name should appear correctly on the storefront"/> + <severity value="CRITICAL"/> + <group value="product"/> + <testCaseId value="MAGETWO-92384"/> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + </after> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create product via admin--> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="goToProductIndex"/> + <waitForPageLoad stepKey="waitForProductIndexPage"/> + <actionGroup ref="goToCreateProductPage" stepKey="goToProductCreatePage"> + <argument name="product" value="SimpleProductNameWithDoubleQuote"/> + </actionGroup> + <actionGroup ref="fillMainProductForm" stepKey="fillProductForm"> + <argument name="product" value="SimpleProductNameWithDoubleQuote"/> + </actionGroup> + <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$$createCategory.name$$]" stepKey="selectCategory"/> + <actionGroup ref="addProductImage" stepKey="addImageToProduct"> + <argument name="image" value="ProductImage"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + + <!--Check product in category listing--> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="goToCategoryPage"/> + <seeElement selector="{{StorefrontCategoryProductSection.ProductImageByNameAndSrc(SimpleProductNameWithDoubleQuote.name, ProductImage.fileName)}}" stepKey="seeCorrectImageCategoryPage"/> + <see selector="{{StorefrontCategoryProductSection.ProductTitleByName(SimpleProductNameWithDoubleQuote.name)}}" userInput="{{SimpleProductNameWithDoubleQuote.name}}" stepKey="seeCorrectNameCategoryPage"/> + <see selector="{{StorefrontCategoryProductSection.ProductPriceByName(SimpleProductNameWithDoubleQuote.name)}}" userInput="${{SimpleProductNameWithDoubleQuote.price}}" stepKey="seeCorrectPriceCategoryPage"/> + <!--Open product display page--> + <click selector="{{StorefrontCategoryProductSection.ProductTitleByName(SimpleProductNameWithDoubleQuote.name)}}" stepKey="clickProductToGoProductPage"/> + <waitForPageLoad stepKey="waitForProductDisplayPageLoad"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{SimpleProductNameWithDoubleQuote.name}}" stepKey="seeCorrectName"/> + <see selector="{{StorefrontProductInfoMainSection.productSku}}" userInput="{{SimpleProductNameWithDoubleQuote.sku}}" stepKey="seeCorrectSku"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="${{SimpleProductNameWithDoubleQuote.price}}" stepKey="seeCorrectPrice"/> + <seeElement selector="{{StorefrontProductInfoMainSection.productImageSrc(ProductImage.fileName)}}" stepKey="seeCorrectImage"/> + <see selector="{{StorefrontProductInfoMainSection.stock}}" userInput="In Stock" stepKey="seeInStock"/> + <see selector="{{StorefrontNavigationSection.breadcrumbs}}" userInput="$$createCategory.name$$" stepKey="seeCorrectBreadCrumbCategory"/> + <see selector="{{StorefrontNavigationSection.breadcrumbs}}" userInput="{{SimpleProductNameWithDoubleQuote.name}}" stepKey="seeCorrectBreadCrumbProduct"/> + + <!--Remove product--> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="SimpleProductNameWithDoubleQuote"/> + </actionGroup> + </test> +</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/CatalogSearch/Test/AdvanceCatalogSearchSimpleProductTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/CatalogSearch/Test/AdvanceCatalogSearchSimpleProductTest.xml new file mode 100644 index 0000000000000..11bc308902ca0 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/CatalogSearch/Test/AdvanceCatalogSearchSimpleProductTest.xml @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdvanceCatalogSearchSimpleProductByNameTest"> + <annotations> + <features value="CatalogSearch"/> + <group value="CatalogSearch"/> + </annotations> + <actionGroup ref="GoToStoreViewAdvancedCatalogSearchActionGroup" stepKey="GoToStoreViewAdvancedCatalogSearchActionGroup"/> + <actionGroup ref="StorefrontAdvancedCatalogSearchByProductNameActionGroup" stepKey="search"> + <argument name="name" value="$$product.name$$"/> + </actionGroup> + <actionGroup ref="StorefrontCheckAdvancedSearchResultActionGroup" stepKey="StorefrontCheckAdvancedSearchResult"/> + <see userInput="1 item" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.itemFound}}" stepKey="see"/> + <see userInput="$$product.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.productName}}" stepKey="seeProductName"/> + </test> + <test name="AdvanceCatalogSearchSimpleProductBySkuTest"> + <annotations> + <features value="CatalogSearch"/> + <group value="CatalogSearch"/> + </annotations> + <actionGroup ref="GoToStoreViewAdvancedCatalogSearchActionGroup" stepKey="GoToStoreViewAdvancedCatalogSearchActionGroup"/> + <actionGroup ref="StorefrontAdvancedCatalogSearchByProductSkuActionGroup" stepKey="search"> + <argument name="sku" value="$$product.sku$$"/> + </actionGroup> + <actionGroup ref="StorefrontCheckAdvancedSearchResultActionGroup" stepKey="StorefrontCheckAdvancedSearchResult"/> + <see userInput="1 item" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.itemFound}}" stepKey="see"/> + <see userInput="$$product.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.productName}}" stepKey="seeProductName"/> + </test> + <test name="AdvanceCatalogSearchSimpleProductByDescriptionTest"> + <annotations> + <features value="CatalogSearch"/> + <group value="CatalogSearch"/> + </annotations> + <actionGroup ref="GoToStoreViewAdvancedCatalogSearchActionGroup" stepKey="GoToStoreViewAdvancedCatalogSearchActionGroup"/> + <actionGroup ref="StorefrontAdvancedCatalogSearchByDescriptionActionGroup" stepKey="search"> + <argument name="description" value="$$product.custom_attributes[description]$$"/> + </actionGroup> + <actionGroup ref="StorefrontCheckAdvancedSearchResultActionGroup" stepKey="StorefrontCheckAdvancedSearchResult"/> + <see userInput="1 item" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.itemFound}}" stepKey="see"/> + <see userInput="$$product.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.productName}}" stepKey="seeProductName"/> + </test> + <test name="AdvanceCatalogSearchSimpleProductByShortDescriptionTest"> + <annotations> + <features value="CatalogSearch"/> + <group value="CatalogSearch"/> + </annotations> + <actionGroup ref="GoToStoreViewAdvancedCatalogSearchActionGroup" stepKey="GoToStoreViewAdvancedCatalogSearchActionGroup"/> + <actionGroup ref="StorefrontAdvancedCatalogSearchByShortDescriptionActionGroup" stepKey="search"> + <argument name="shortDescription" value="$$product.custom_attributes[short_description]$$"/> + </actionGroup> + <actionGroup ref="StorefrontCheckAdvancedSearchResultActionGroup" stepKey="StorefrontCheckAdvancedSearchResult"/> + <see userInput="1 item" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.itemFound}}" stepKey="see"/> + <see userInput="$$product.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.productName}}" stepKey="seeProductName"/> + </test> + <test name="AdvanceCatalogSearchSimpleProductByPriceTest"> + <annotations> + <features value="CatalogSearch"/> + <group value="CatalogSearch"/> + </annotations> + <actionGroup ref="GoToStoreViewAdvancedCatalogSearchActionGroup" stepKey="GoToStoreViewAdvancedCatalogSearchActionGroup"/> + <actionGroup ref="StorefrontAdvancedCatalogSearchByProductNameAndPriceActionGroup" stepKey="search"> + <argument name="name" value="$$arg1.name$$"/> + <argument name="priceFrom" value="$$arg2.price$$"/> + <argument name="priceTo" value="$$arg3.price$$"/> + </actionGroup> + <actionGroup ref="StorefrontCheckAdvancedSearchResultActionGroup" stepKey="StorefrontCheckAdvancedSearchResult"/> + <see userInput="1 item" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.itemFound}}" stepKey="see"/> + <see userInput="$$arg1.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.productName}}" stepKey="seeProductName"/> + </test> +</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/CatalogSearch/Test/AdvanceCatalogSearchSimpleProductsTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/CatalogSearch/Test/AdvanceCatalogSearchSimpleProductsTest.xml deleted file mode 100644 index f52a86810842e..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/CatalogSearch/Test/AdvanceCatalogSearchSimpleProductsTest.xml +++ /dev/null @@ -1,140 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="AdvanceCatalogSearchSimpleProductByNameTest"> - <annotations> - <features value="CatalogSearch"/> - <stories value="Advanced Catalog Product Search for all product types"/> - <title value="Guest customer should be able to advance search simple product with product name"/> - <description value="Guest customer should be able to advance search simple product with product name"/> - <severity value="MAJOR"/> - <testCaseId value="MC-132"/> - <group value="CatalogSearch"/> - </annotations> - <before> - <createData entity="ApiProductWithDescription" stepKey="createProductOne"/> - </before> - <after> - <deleteData createDataKey="createProductOne" stepKey="deleteProductOne"/> - </after> - - <actionGroup ref="GoToStoreViewAdvancedCatalogSearchActionGroup" stepKey="GoToStoreViewAdvancedCatalogSearchActionGroup"/> - <actionGroup ref="StorefrontAdvancedCatalogSearchByProductNameActionGroup" stepKey="search"> - <argument name="name" value="$$createProductOne.name$$"/> - </actionGroup> - <actionGroup ref="StorefrontCheckAdvancedSearchResultActionGroup" stepKey="StorefrontCheckAdvancedSearchResult"/> - <see userInput="1 item" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.itemFound}}" stepKey="see"/> - </test> - <test name="AdvanceCatalogSearchSimpleProductBySkuTest"> - <annotations> - <features value="CatalogSearch"/> - <stories value="Advanced Catalog Product Search for all product types"/> - <title value="Guest customer should be able to advance search simple product with product sku"/> - <description value="Guest customer should be able to advance search simple product with product sku"/> - <severity value="MAJOR"/> - <testCaseId value="MC-133"/> - <group value="CatalogSearch"/> - </annotations> - <before> - <createData entity="ApiProductWithDescription" stepKey="createProductOne"/> - </before> - <after> - <deleteData createDataKey="createProductOne" stepKey="deleteProductOne"/> - </after> - - <actionGroup ref="GoToStoreViewAdvancedCatalogSearchActionGroup" stepKey="GoToStoreViewAdvancedCatalogSearchActionGroup"/> - <actionGroup ref="StorefrontAdvancedCatalogSearchByProductSkuActionGroup" stepKey="search"> - <argument name="sku" value="$$createProductOne.sku$$"/> - </actionGroup> - <actionGroup ref="StorefrontCheckAdvancedSearchResultActionGroup" stepKey="StorefrontCheckAdvancedSearchResult"/> - <see userInput="1 item" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.itemFound}}" stepKey="see"/> - <see userInput="$$createProductOne.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.productName}}" stepKey="seeProductName"/> - </test> - - <test name="AdvanceCatalogSearchSimpleProductByDescriptionTest"> - <annotations> - <features value="CatalogSearch"/> - <stories value="Advanced Catalog Product Search for all product types"/> - <title value="Guest customer should be able to advance search simple product with product description"/> - <description value="Guest customer should be able to advance search simple product with product description"/> - <severity value="MAJOR"/> - <testCaseId value="MC-134"/> - <group value="CatalogSearch"/> - </annotations> - <before> - <createData entity="ApiProductWithDescription" stepKey="createProductOne"/> - </before> - <after> - <deleteData createDataKey="createProductOne" stepKey="deleteProductOne"/> - </after> - - <actionGroup ref="GoToStoreViewAdvancedCatalogSearchActionGroup" stepKey="GoToStoreViewAdvancedCatalogSearchActionGroup"/> - <actionGroup ref="StorefrontAdvancedCatalogSearchByDescriptionActionGroup" stepKey="search"> - <argument name="description" value="$$createProductOne.custom_attributes[description]$$"/> - </actionGroup> - <actionGroup ref="StorefrontCheckAdvancedSearchResultActionGroup" stepKey="StorefrontCheckAdvancedSearchResult"/> - <see userInput="1 item" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.itemFound}}" stepKey="see"/> - <see userInput="$$createProductOne.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.productName}}" stepKey="seeProductName"/> - </test> - - <test name="AdvanceCatalogSearchSimpleProductByShortDescriptionTest"> - <annotations> - <features value="CatalogSearch"/> - <stories value="Advanced Catalog Product Search for all product types"/> - <title value="Guest customer should be able to advance search simple product with product short description"/> - <description value="Guest customer should be able to advance search simple product with product short description"/> - <severity value="MAJOR"/> - <testCaseId value="MC-135"/> - <group value="CatalogSearch"/> - </annotations> - <before> - <createData entity="ApiProductWithDescription" stepKey="createProductOne"/> - </before> - <after> - <deleteData createDataKey="createProductOne" stepKey="deleteProductOne"/> - </after> - - <actionGroup ref="GoToStoreViewAdvancedCatalogSearchActionGroup" stepKey="GoToStoreViewAdvancedCatalogSearchActionGroup"/> - <actionGroup ref="StorefrontAdvancedCatalogSearchByShortDescriptionActionGroup" stepKey="search"> - <argument name="shortDescription" value="$$createProductOne.custom_attributes[short_description]$$"/> - </actionGroup> - <actionGroup ref="StorefrontCheckAdvancedSearchResultActionGroup" stepKey="StorefrontCheckAdvancedSearchResult"/> - <see userInput="1 item" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.itemFound}}" stepKey="see"/> - <see userInput="$$createProductOne.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.productName}}" stepKey="seeProductName"/> - </test> - - <test name="AdvanceCatalogSearchSimpleProductByPriceTest"> - <annotations> - <features value="CatalogSearch"/> - <stories value="Advanced Catalog Product Search for all product types"/> - <title value="Guest customer should be able to advance search simple product with product price"/> - <description value="Guest customer should be able to advance search simple product with product price"/> - <severity value="MAJOR"/> - <testCaseId value="MC-136"/> - <group value="CatalogSearch"/> - </annotations> - <before> - <createData entity="ApiProductWithDescription" stepKey="createProductOne"/> - </before> - <after> - <deleteData createDataKey="createProductOne" stepKey="deleteProductOne"/> - </after> - - <actionGroup ref="GoToStoreViewAdvancedCatalogSearchActionGroup" stepKey="GoToStoreViewAdvancedCatalogSearchActionGroup"/> - <actionGroup ref="StorefrontAdvancedCatalogSearchByProductNameAndPriceActionGroup" stepKey="search"> - <argument name="name" value="$$createProductOne.name$$"/> - <argument name="priceFrom" value="$$createProductOne.price$$"/> - <argument name="priceTo" value="$$createProductOne.price$$"/> - </actionGroup> - <actionGroup ref="StorefrontCheckAdvancedSearchResultActionGroup" stepKey="StorefrontCheckAdvancedSearchResult"/> - <see userInput="1 item" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.itemFound}}" stepKey="see"/> - <see userInput="$$createProductOne.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.productName}}" stepKey="seeProductName"/> - </test> -</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/CatalogSearch/Test/AdvanceCatalogSearchVirtualProductsTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/CatalogSearch/Test/AdvanceCatalogSearchVirtualProductsTest.xml deleted file mode 100644 index 80b1b294fecd5..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/CatalogSearch/Test/AdvanceCatalogSearchVirtualProductsTest.xml +++ /dev/null @@ -1,141 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="AdvanceCatalogSearchVirtualProductByNameTest"> - <annotations> - <features value="CatalogSearch"/> - <stories value="Advanced Catalog Product Search for all product types"/> - <title value="Guest customer should be able to advance search virtual product with product name"/> - <description value="Guest customer should be able to advance search virtual product with product name"/> - <severity value="MAJOR"/> - <testCaseId value="MC-137"/> - <group value="CatalogSearch"/> - </annotations> - <before> - <createData entity="ApiVirtualProductWithDescription" stepKey="createProductOne"/> - </before> - <after> - <deleteData createDataKey="createProductOne" stepKey="deleteProductOne"/> - </after> - - <actionGroup ref="GoToStoreViewAdvancedCatalogSearchActionGroup" stepKey="GoToStoreViewAdvancedCatalogSearchActionGroup"/> - <actionGroup ref="StorefrontAdvancedCatalogSearchByProductNameActionGroup" stepKey="search"> - <argument name="name" value="$$createProductOne.name$$"/> - </actionGroup> - <actionGroup ref="StorefrontCheckAdvancedSearchResultActionGroup" stepKey="StorefrontCheckAdvancedSearchResult"/> - <see userInput="1 item" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.itemFound}}" stepKey="see"/> - <see userInput="$$createProductOne.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.productName}}" stepKey="seeProductName"/> - </test> - <test name="AdvanceCatalogSearchVirtualProductBySkuTest"> - <annotations> - <features value="CatalogSearch"/> - <stories value="Advanced Catalog Product Search for all product types"/> - <title value="Guest customer should be able to advance search virtual product with product sku"/> - <description value="Guest customer should be able to advance search virtual product with product sku"/> - <severity value="MAJOR"/> - <testCaseId value="MC-162"/> - <group value="CatalogSearch"/> - </annotations> - <before> - <createData entity="ApiVirtualProductWithDescription" stepKey="createProductOne"/> - </before> - <after> - <deleteData createDataKey="createProductOne" stepKey="deleteProductOne"/> - </after> - - <actionGroup ref="GoToStoreViewAdvancedCatalogSearchActionGroup" stepKey="GoToStoreViewAdvancedCatalogSearchActionGroup"/> - <actionGroup ref="StorefrontAdvancedCatalogSearchByProductSkuActionGroup" stepKey="search"> - <argument name="sku" value="$$createProductOne.sku$$"/> - </actionGroup> - <actionGroup ref="StorefrontCheckAdvancedSearchResultActionGroup" stepKey="StorefrontCheckAdvancedSearchResult"/> - <see userInput="1 item" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.itemFound}}" stepKey="see"/> - <see userInput="$$createProductOne.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.productName}}" stepKey="seeProductName"/> - </test> - - <test name="AdvanceCatalogSearchVirtualProductByDescriptionTest"> - <annotations> - <features value="CatalogSearch"/> - <stories value="Advanced Catalog Product Search for all product types"/> - <title value="Guest customer should be able to advance search virtual product with product description"/> - <description value="Guest customer should be able to advance search virtual product with product description"/> - <severity value="MAJOR"/> - <testCaseId value="MC-163"/> - <group value="CatalogSearch"/> - </annotations> - <before> - <createData entity="ApiVirtualProductWithDescription" stepKey="createProductOne"/> - </before> - <after> - <deleteData createDataKey="createProductOne" stepKey="deleteProductOne"/> - </after> - - <actionGroup ref="GoToStoreViewAdvancedCatalogSearchActionGroup" stepKey="GoToStoreViewAdvancedCatalogSearchActionGroup"/> - <actionGroup ref="StorefrontAdvancedCatalogSearchByDescriptionActionGroup" stepKey="search"> - <argument name="description" value="$$createProductOne.custom_attributes[description]$$"/> - </actionGroup> - <actionGroup ref="StorefrontCheckAdvancedSearchResultActionGroup" stepKey="StorefrontCheckAdvancedSearchResult"/> - <see userInput="1 item" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.itemFound}}" stepKey="see"/> - <see userInput="$$createProductOne.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.productName}}" stepKey="seeProductName"/> - </test> - - <test name="AdvanceCatalogSearchVirtualProductByShortDescriptionTest"> - <annotations> - <features value="CatalogSearch"/> - <stories value="Advanced Catalog Product Search for all product types"/> - <title value="Guest customer should be able to advance search virtual product with product short description"/> - <description value="Guest customer should be able to advance search virtual product with product short description"/> - <severity value="MAJOR"/> - <testCaseId value="MC-164"/> - <group value="CatalogSearch"/> - </annotations> - <before> - <createData entity="ApiVirtualProductWithDescription" stepKey="createProductOne"/> - </before> - <after> - <deleteData createDataKey="createProductOne" stepKey="deleteProductOne"/> - </after> - - <actionGroup ref="GoToStoreViewAdvancedCatalogSearchActionGroup" stepKey="GoToStoreViewAdvancedCatalogSearchActionGroup"/> - <actionGroup ref="StorefrontAdvancedCatalogSearchByShortDescriptionActionGroup" stepKey="search"> - <argument name="shortDescription" value="$$createProductOne.custom_attributes[short_description]$$"/> - </actionGroup> - <actionGroup ref="StorefrontCheckAdvancedSearchResultActionGroup" stepKey="StorefrontCheckAdvancedSearchResult"/> - <see userInput="1 item" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.itemFound}}" stepKey="see"/> - <see userInput="$$createProductOne.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.productName}}" stepKey="seeProductName"/> - </test> - - <test name="AdvanceCatalogSearchVirtualProductByPriceTest"> - <annotations> - <features value="CatalogSearch"/> - <stories value="Advanced Catalog Product Search for all product types"/> - <title value="Guest customer should be able to advance search virtual product with product price"/> - <description value="Guest customer should be able to advance search virtual product with product price"/> - <severity value="MAJOR"/> - <testCaseId value="MC-165"/> - <group value="CatalogSearch"/> - </annotations> - <before> - <createData entity="ApiVirtualProductWithDescription" stepKey="createProductOne"/> - </before> - <after> - <deleteData createDataKey="createProductOne" stepKey="deleteProductOne"/> - </after> - - <actionGroup ref="GoToStoreViewAdvancedCatalogSearchActionGroup" stepKey="GoToStoreViewAdvancedCatalogSearchActionGroup"/> - <actionGroup ref="StorefrontAdvancedCatalogSearchByProductNameAndPriceActionGroup" stepKey="search"> - <argument name="name" value="$$createProductOne.name$$"/> - <argument name="priceFrom" value="$$createProductOne.price$$"/> - <argument name="priceTo" value="$$createProductOne.price$$"/> - </actionGroup> - <actionGroup ref="StorefrontCheckAdvancedSearchResultActionGroup" stepKey="StorefrontCheckAdvancedSearchResult"/> - <see userInput="1 item" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.itemFound}}" stepKey="see"/> - <see userInput="$$createProductOne.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.productName}}" stepKey="seeProductName"/> - </test> -</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Checkout/Page/GuestCheckoutReviewAndPaymentsPage.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Checkout/Page/GuestCheckoutReviewAndPaymentsPage.xml new file mode 100644 index 0000000000000..3fb6e99ed6730 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Checkout/Page/GuestCheckoutReviewAndPaymentsPage.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + <page name="GuestCheckoutReviewAndPaymentsPage" url="/checkout/#payment" area="storefront" module="Magento_Checkout"> + <section name="CheckoutPaymentSection"/> + </page> +</pages> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Checkout/Section/CheckoutCartSummarySection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Checkout/Section/CheckoutCartSummarySection.xml index 32a57a14cd9da..248070940542c 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Checkout/Section/CheckoutCartSummarySection.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Checkout/Section/CheckoutCartSummarySection.xml @@ -15,5 +15,9 @@ <element name="total" type="text" selector="//*[@id='cart-totals']//tr[@class='grand totals']//td//span[@class='price']"/> <element name="proceedToCheckout" type="button" selector=".action.primary.checkout span" timeout="30"/> <element name="discountAmount" type="text" selector="td[data-th='Discount']"/> + <element name="shippingHeading" type="button" selector="#block-shipping-heading"/> + <element name="postcode" type="input" selector="input[name='postcode']"/> + <element name="stateProvince" type="select" selector="select[name='region_id']"/> + <element name="country" type="select" selector="select[name='country_id']"/> </section> </sections> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Checkout/Section/CheckoutShippingGuestInfoSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Checkout/Section/CheckoutShippingGuestInfoSection.xml index f20ae4dac07d5..f2cca89be6c18 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Checkout/Section/CheckoutShippingGuestInfoSection.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Checkout/Section/CheckoutShippingGuestInfoSection.xml @@ -17,5 +17,7 @@ <element name="region" type="select" selector="select[name=region_id]"/> <element name="postcode" type="input" selector="input[name=postcode]"/> <element name="telephone" type="input" selector="input[name=telephone]"/> + <element name="next" type="button" selector="button.button.action.continue.primary" timeout="30"/> + <element name="firstShippingMethod" type="radio" selector=".row:nth-of-type(1) .col-method .radio"/> </section> </sections> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Cms/Data/CmsPageData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Cms/Data/CmsPageData.xml index 857ac2a2f1b1f..25e55ea756afb 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Cms/Data/CmsPageData.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Cms/Data/CmsPageData.xml @@ -37,6 +37,7 @@ <data key="price">1.00</data> <data key="file_type">Upload File</data> <data key="shareable">Yes</data> + <data key="file">magento.jpg</data> <data key="value">magento.jpg</data> <data key="fileName">magento</data> <data key="extension">jpg</data> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Data/ConfigurableProductData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Data/ConfigurableProductData.xml index 0a2429fa8f9e5..70e758a51409d 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Data/ConfigurableProductData.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Data/ConfigurableProductData.xml @@ -34,6 +34,19 @@ <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> </entity> + <entity name="ApiConfigurableProductWithDescription" type="product"> + <data key="sku" unique="suffix">api-configurable-product</data> + <data key="type_id">configurable</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="name" unique="suffix">API Configurable Product</data> + <data key="urlKey" unique="suffix">api-configurable-product</data> + <data key="status">1</data> + <data key="quantity">100</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute_array">ApiProductDescription</requiredEntity> + <requiredEntity type="custom_attribute_array">ApiProductShortDescription</requiredEntity> + </entity> <entity name="ConfigurableProductAddChild" type="ConfigurableProductAddChild"> <var key="sku" entityKey="sku" entityType="product" /> <var key="childSku" entityKey="sku" entityType="product2"/> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Section/StorefrontProductInfoMainSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Section/StorefrontProductInfoMainSection.xml index cebb76e68a6cc..45bf866551319 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Section/StorefrontProductInfoMainSection.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Section/StorefrontProductInfoMainSection.xml @@ -14,5 +14,8 @@ <element name="productAttributeOptions1" type="select" selector="#product-options-wrapper div[tabindex='0'] option"/> <element name="productAttributeOptionsSelectButton" type="select" selector="#product-options-wrapper .super-attribute-select"/> <element name="productAttributeOptionsError" type="text" selector="//div[@class='mage-error']"/> + <!-- Parameter is the order number of the attribute on the page (1 is the newest) --> + <element name="nthAttributeOnPage" type="block" selector="tr:nth-of-type({{numElement}}) .data" parameterized="true"/> + <element name="stockIndication" type="block" selector=".stock" /> </section> </sections> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminConfigurableProductOutOfStockTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminConfigurableProductOutOfStockTest.xml new file mode 100644 index 0000000000000..96651e303c5f2 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminConfigurableProductOutOfStockTest.xml @@ -0,0 +1,134 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdminConfigurableProductOutOfStockTest"> + <annotations> + <features value="ConfigurableProduct"/> + <stories value="Product visibility when in stock/out of stock"/> + <title value="Configurable Product goes 'Out of Stock' if all associated Simple Products are 'Out of Stock'"/> + <description value="Configurable Product goes 'Out of Stock' if all associated Simple Products are 'Out of Stock'"/> + <testCaseId value="MC-181"/> + <group value="ConfigurableProduct"/> + </annotations> + <before> + <!-- TODO: This should be converted to an actionGroup once MQE-993 is fixed. --> + <!-- Create the category to put the product in --> + <createData entity="ApiCategory" stepKey="createCategory"/> + + <!-- Create the configurable product based on the data in the /data folder --> + <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!-- Make the configurable product have two options, that are children of the default attribute set --> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Create the 2 children that will be a part of the configurable product --> + <createData entity="ApiSimpleOne" stepKey="createConfigChildProduct1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + </createData> + <createData entity="ApiSimpleTwo" stepKey="createConfigChildProduct2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + + <!-- Assign the two products to the configurable product --> + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct1"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild2"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct2"/> + </createData> + + <!-- log in --> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + </before> + + <after> + <amOnPage url="admin/admin/auth/logout/" stepKey="logout"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigChildProduct1" stepKey="deleteConfigChildProduct1"/> + <deleteData createDataKey="createConfigChildProduct2" stepKey="deleteConfigChildProduct2"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> + </after> + + <!-- Check to make sure that the configurable product shows up as in stock --> + <amOnPage url="/{{ApiConfigurableProduct.urlKey}}2.html" stepKey="goToConfigProductPage"/> + <waitForPageLoad stepKey="waitForStoreFrontLoad"/> + <see stepKey="lookForOutOfStock" selector="{{StorefrontProductInfoMainSection.stockIndication}}" userInput="IN STOCK" /> + + <!-- Find the first simple product that we just created using the product grid and go to its page--> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPage"/> + <waitForPageLoad stepKey="waitForAdminProductGridLoad"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFiltersInitial"/> + <actionGroup ref="filterProductGridBySku" stepKey="findCreatedProduct"> + <argument name="product" value="ApiSimpleOne"/> + </actionGroup> + <waitForPageLoad stepKey="waitForFiltersToBeApplied"/> + <click selector="{{AdminProductGridSection.firstRow}}" stepKey="clickOnProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + + <!-- Edit the quantity of the simple first product as 0 --> + <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="0" stepKey="fillProductQuantity"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveProduct"/> + <waitForPageLoad stepKey="waitForProductPageSaved"/> + + <!-- Check to make sure that the configurable product shows up as in stock --> + <amOnPage url="/{{ApiConfigurableProduct.urlKey}}2.html" stepKey="goToConfigProductPage2"/> + <waitForPageLoad stepKey="waitForStoreFrontLoad2"/> + <see stepKey="lookForOutOfStock2" selector="{{StorefrontProductInfoMainSection.stockIndication}}" userInput="IN STOCK"/> + + <!-- Find the second simple product that we just created using the product grid and go to its page--> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPage2"/> + <waitForPageLoad stepKey="waitForAdminProductGridLoad2"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFiltersInitial2"/> + <actionGroup ref="filterProductGridBySku" stepKey="findCreatedProduct2"> + <argument name="product" value="ApiSimpleTwo"/> + </actionGroup> + <waitForPageLoad stepKey="waitForFiltersToBeApplied2"/> + <click selector="{{AdminProductGridSection.firstRow}}" stepKey="clickOnProductPage2"/> + <waitForPageLoad stepKey="waitForProductPageLoad2"/> + + <!-- Edit the quantity of the second simple product as 0 --> + <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="0" stepKey="fillProductQuantity2"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveProduct2"/> + <waitForPageLoad stepKey="waitForProductPageSaved2"/> + + <!-- Check to make sure that the configurable product shows up as out of stock --> + <amOnPage url="/{{ApiConfigurableProduct.urlKey}}2.html" stepKey="goToConfigProductPage3"/> + <waitForPageLoad stepKey="waitForStoreFrontLoad3"/> + <see stepKey="lookForOutOfStock3" selector="{{StorefrontProductInfoMainSection.stockIndication}}" userInput="OUT OF STOCK"/> + </test> +</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminConfigurableProductUpdateAttributeTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminConfigurableProductUpdateAttributeTest.xml new file mode 100644 index 0000000000000..2a2c331aa158f --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminConfigurableProductUpdateAttributeTest.xml @@ -0,0 +1,142 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdminConfigurableProductUpdateAttributeTest"> + <annotations> + <features value="ConfigurableProduct"/> + <stories value="Edit a configurable product in admin"/> + <title value="Admin should be able to update existing attributes of a configurable product"/> + <description value="Admin should be able to update existing attributes of a configurable product"/> + <severity value="AVERAGE"/> + <testCaseId value="MC-179"/> + <group value="ConfigurableProduct"/> + </annotations> + + <before> + <!-- Create the attribute we will be modifying --> + <createData entity="productAttributeWithTwoOptions" stepKey="createModifiableProductAttribute"/> + + <!-- Create the two attributes the product will have --> + <createData entity="productAttributeOption1" stepKey="createModifiableProductAttributeOption1"> + <requiredEntity createDataKey="createModifiableProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createModifiableProductAttributeOption2"> + <requiredEntity createDataKey="createModifiableProductAttribute"/> + </createData> + + <!-- Add the product to the default set --> + <createData entity="AddToDefaultSet" stepKey="createModifiableAddToAttributeSet"> + <requiredEntity createDataKey="createModifiableProductAttribute"/> + </createData> + + <!-- TODO: This should be converted to an actionGroup once MQE-993 is fixed. --> + <!-- Create the category the product will be a part of --> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + + <!-- Create the two attributes the product will have --> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + + <!-- Add the product to the default set --> + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + + <!-- Get the two attributes --> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Create the two children product --> + <createData entity="ApiSimpleOne" stepKey="createConfigChildProduct1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + </createData> + <createData entity="ApiSimpleTwo" stepKey="createConfigChildProduct2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + + <!-- Create the two configurable product with both children --> + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct1"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild2"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct2"/> + </createData> + + <!-- login --> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + </before> + + <after> + <amOnPage url="admin/admin/auth/logout/" stepKey="logout"/> + + <!-- Delete everything that was created in the before block --> + <deleteData createDataKey="createCategory" stepKey="deleteCatagory" /> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigChildProduct1" stepKey="deleteConfigChildProduct1"/> + <deleteData createDataKey="createConfigChildProduct2" stepKey="deleteConfigChildProduct2"/> + <deleteData createDataKey="createModifiableProductAttribute" stepKey="deleteModifiableProductAttribute"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> + </after> + + <!-- Get the current option of the attribute before it was changed --> + <amOnPage url="/{{ApiConfigurableProduct.urlKey}}2.html" stepKey="goToConfigProductPage"/> + <waitForPageLoad stepKey="waitForStoreFrontProductPageLoad"/> + + <grabTextFrom stepKey="getBeforeOption" selector="{{StorefrontProductInfoMainSection.nthAttributeOnPage('1')}}"/> + + <!-- Find the product that we just created using the product grid --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPage"/> + <waitForPageLoad stepKey="waitForAdminProductPageLoad"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFiltersInitial"/> + <actionGroup ref="filterProductGridBySku" stepKey="findCreatedProduct"> + <argument name="product" value="ApiConfigurableProduct"/> + </actionGroup> + <waitForPageLoad stepKey="waitForProductFilterLoad"/> + + <click selector="{{AdminProductGridSection.firstRow}}" stepKey="clickOnProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + + <!-- change the option on the first attribute --> + <selectOption stepKey="clickFirstAttribute" selector="{{ModifyAttributes.nthExistingAttribute($$createModifiableProductAttribute.default_frontend_label$$)}}" userInput="option1"/> + + <!-- Save the product --> + <click stepKey="saveProductAttribute" selector="{{AdminProductFormActionSection.saveButton}}"/> + <see stepKey="assertSuccess" selector="{{AdminProductMessagesSection.successMessage}}" userInput="You saved the product."/> + + <!-- Go back to the configurable product page and check to see if it has changed --> + <amOnPage url="/{{ApiConfigurableProduct.urlKey}}2.html" stepKey="goToConfigProductPage2"/> + <waitForPageLoad stepKey="waitForStoreFrontProductPageLoad2"/> + <grabTextFrom stepKey="getCurrentOption" selector="{{StorefrontProductInfoMainSection.nthAttributeOnPage('1')}}"/> + <assertNotEquals expected="{$getBeforeOption}" expectedType="string" actual="{$getCurrentOption}" actualType="string" stepKey="assertNotEquals"/> + + </test> +</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/Data/LinkData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/Data/LinkData.xml index 6276f340f85a6..1498f4b96b3ab 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/Data/LinkData.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/Data/LinkData.xml @@ -41,4 +41,13 @@ <data key="file_type">URL</data> <data key="file">https://static.magento.com/sites/all/themes/mag_redesign/images/magento-logo.svg</data> </entity> + <entity name="ApiDownloadableLink" type="downloadable_link"> + <data key="title" unique="suffix">Api Downloadable Link</data> + <data key="price">2.00</data> + <data key="link_type">url</data> + <data key="shareable">No</data> + <data key="number_of_downloads">1000</data> + <data key="sort_order">0</data> + <data key="link_url">https://static.magento.com/sites/all/themes/mag_redesign/images/magento-logo.svg</data> + </entity> </entities> \ No newline at end of file diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/Data/ProductData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/Data/ProductData.xml index 427f2577a8ab9..f71ebd481a97d 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/Data/ProductData.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/Data/ProductData.xml @@ -19,4 +19,19 @@ <data key="status">1</data> <data key="urlKey" unique="suffix">downloadableproduct</data> </entity> + <entity name="ApiDownloadableProduct" type="product"> + <data key="sku" unique="suffix">api-downloadable-product</data> + <data key="type_id">downloadable</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="name" unique="suffix">Api Downloadable Product</data> + <data key="price">123.00</data> + <data key="urlKey" unique="suffix">api-downloadable-product</data> + <data key="status">1</data> + <data key="quantity">100</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute_array">ApiProductDescription</requiredEntity> + <requiredEntity type="custom_attribute_array">ApiProductShortDescription</requiredEntity> + <requiredEntity type="downloadable_link">apiDownloadableLink</requiredEntity> + </entity> </entities> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/Metadata/downloadable_link-meta.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/Metadata/downloadable_link-meta.xml new file mode 100644 index 0000000000000..dc86c4e8d7957 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/Metadata/downloadable_link-meta.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + <operation name="CreateDownloadableLink" dataType="downloadable_link" type="create" auth="adminOauth" url="/V1/products/{sku}/downloadable-links" method="POST"> + <contentType>application/json</contentType> + <object dataType="downloadable_link" key="link"> + <field key="title">string</field> + <field key="sort_order">integer</field> + <field key="is_shareable">integer</field> + <field key="price">number</field> + <field key="number_of_downloads">integer</field> + <field key="link_type">string</field> + <field key="link_file">string</field> + <field key="link_file_content">link_file_content</field> + <field key="file_data">string</field> + <field key="link_url">string</field> + <field key="sample_type">string</field> + <field key="sample_file">string</field> + <field key="sample_file_content">sample_file_content</field> + <field key="sample_url">string</field> + </object> + <field key="isGlobalScopeContent">boolean</field> + </operation> +</operations> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/Metadata/link_file_content-meta.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/Metadata/link_file_content-meta.xml new file mode 100644 index 0000000000000..72f643e06800d --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/Metadata/link_file_content-meta.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + <operation name="CreateLinkFileContent" dataType="link_file_content" type="create"> + <field key="file_data">string</field> + <field key="name">string</field> + </operation> +</operations> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/Metadata/sample_file_content-meta.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/Metadata/sample_file_content-meta.xml new file mode 100644 index 0000000000000..144ce67bb25bb --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/Metadata/sample_file_content-meta.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + <operation name="CreateSampleFileContent" dataType="sample_file_content" type="create"> + <field key="file_data">string</field> + <field key="name">string</field> + </operation> +</operations> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GroupedProduct/Data/GroupedProductData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GroupedProduct/Data/GroupedProductData.xml index 019b0d24dec52..9960d698a7861 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GroupedProduct/Data/GroupedProductData.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GroupedProduct/Data/GroupedProductData.xml @@ -17,4 +17,15 @@ <data key="urlKey" unique="suffix">groupedproduct</data> <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> </entity> + <entity name="ApiGroupedProduct" type="product3"> + <data key="sku" unique="suffix">api-grouped-product</data> + <data key="type_id">grouped</data> + <data key="attribute_set_id">4</data> + <data key="name" unique="suffix">Api Grouped Product</data> + <data key="status">1</data> + <data key="urlKey" unique="suffix">api-grouped-product</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute_array">ApiProductDescription</requiredEntity> + <requiredEntity type="custom_attribute_array">ApiProductShortDescription</requiredEntity> + </entity> </entities> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GroupedProduct/Data/ProductLinkData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GroupedProduct/Data/ProductLinkData.xml new file mode 100644 index 0000000000000..9a5df1e379a86 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GroupedProduct/Data/ProductLinkData.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="ProductLinkSimple1" type="product_link"> + <var key="sku" entityKey="sku" entityType="product3"/> + <var key="linked_product_sku" entityKey="sku" entityType="product"/> + <data key="link_type">associated</data> + <data key="linked_product_type">simple</data> + <data key="position">1</data> + <requiredEntity type="product_link_extension_attribute">Qty1000</requiredEntity> + </entity> + <entity name="ProductLinkSimple2" type="product_link"> + <var key="sku" entityKey="sku" entityType="product3"/> + <var key="linked_product_sku" entityKey="sku" entityType="product"/> + <data key="link_type">associated</data> + <data key="linked_product_type">simple</data> + <data key="position">2</data> + <requiredEntity type="product_link_extension_attribute">Qty1000</requiredEntity> + </entity> +</entities> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GroupedProduct/Data/ProductLinkExtensionAttributeData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GroupedProduct/Data/ProductLinkExtensionAttributeData.xml new file mode 100644 index 0000000000000..5f5dcb3a0ef4f --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GroupedProduct/Data/ProductLinkExtensionAttributeData.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="Qty1000" type="product_link_extension_attribute"> + <data key="qty">1000</data> + </entity> +</entities> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GroupedProduct/Data/ProductLinksData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GroupedProduct/Data/ProductLinksData.xml new file mode 100644 index 0000000000000..523517aa70080 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GroupedProduct/Data/ProductLinksData.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="OneSimpleProductLink" type="product_links"> + <requiredEntity type="product_link">ProductLinkSimple1</requiredEntity> + </entity> + <entity name="OneMoreSimpleProductLink" type="product_links"> + <requiredEntity type="product_link">ProductLinkSimple2</requiredEntity> + </entity> + <entity name="TwoSimpleProductLinks" type="product_links"> + <array key="items"> + <item>ProductLinkSimple1</item> + <item>ProductLinkSimple2</item> + </array> + </entity> +</entities> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Persistent/Data/PersistentData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Persistent/Data/PersistentData.xml new file mode 100644 index 0000000000000..4ba2e1ae73824 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Persistent/Data/PersistentData.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="PersistentConfigDefault" type="persistent_config_state"> + <requiredEntity type="persistent_options_enabled">persistentDefaultState</requiredEntity> + </entity> + <entity name="persistentDefaultState" type="persistent_options_enabled"> + <data key="value">0</data> + </entity> + + <entity name="PersistentConfigEnabled" type="persistent_config_state"> + <requiredEntity type="persistent_options_enabled">persistentEnabledState</requiredEntity> + </entity> + <entity name="persistentEnabledState" type="persistent_options_enabled"> + <data key="value">1</data> + </entity> +</entities> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Persistent/Metadata/persistent_config-meta.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Persistent/Metadata/persistent_config-meta.xml new file mode 100644 index 0000000000000..69a835aa703eb --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Persistent/Metadata/persistent_config-meta.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + <operation name="CreatePersistentConfigState" dataType="persistent_config_state" type="create" auth="adminFormKey" url="/admin/system_config/save/section/persistent/" method="POST"> + <object key="groups" dataType="persistent_config_state"> + <object key="options" dataType="persistent_config_state"> + <object key="fields" dataType="persistent_config_state"> + <object key="enabled" dataType="persistent_options_enabled"> + <field key="value">string</field> + </object> + </object> + </object> + </object> + </operation> +</operations> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Persistent/Test/GuestCheckoutWithEnabledPersistentTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Persistent/Test/GuestCheckoutWithEnabledPersistentTest.xml new file mode 100644 index 0000000000000..f7f76da7d3895 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Persistent/Test/GuestCheckoutWithEnabledPersistentTest.xml @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="GuestCheckoutWithEnabledPersistentTest"> + <annotations> + <features value="Persistent"/> + <title value="Guest Checkout with Enabled Persistent"/> + <description value="Checkout data must be restored after page checkout reload."/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-92453"/> + <group value="persistent"/> + </annotations> + <before> + <createData entity="PersistentConfigEnabled" stepKey="enablePersistent"/> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <createData entity="PersistentConfigDefault" stepKey="setDefaultPersistentState"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + </after> + <!-- Add simple product to cart --> + <actionGroup ref="AddSimpleProductToCart" stepKey="addProductToCart1"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + <!-- Navigate to checkout --> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="navigateToCheckoutFromMinicart"/> + <!-- Fill Shipping Address form --> + <fillField selector="{{CheckoutShippingGuestInfoSection.email}}" userInput="{{CustomerEntityOne.email}}" stepKey="enterEmail"/> + <fillField selector="{{CheckoutShippingGuestInfoSection.firstName}}" userInput="{{CustomerEntityOne.firstname}}" stepKey="enterFirstName"/> + <fillField selector="{{CheckoutShippingGuestInfoSection.lastName}}" userInput="{{CustomerEntityOne.lastname}}" stepKey="enterLastName"/> + <fillField selector="{{CheckoutShippingGuestInfoSection.street}}" userInput="{{CustomerAddressSimple.street[0]}}" stepKey="enterStreet"/> + <fillField selector="{{CheckoutShippingGuestInfoSection.city}}" userInput="{{CustomerAddressSimple.city}}" stepKey="enterCity"/> + <selectOption selector="{{CheckoutShippingGuestInfoSection.region}}" userInput="{{CustomerAddressSimple.state}}" stepKey="selectRegion"/> + <fillField selector="{{CheckoutShippingGuestInfoSection.postcode}}" userInput="{{CustomerAddressSimple.postcode}}" stepKey="enterPostcode"/> + <fillField selector="{{CheckoutShippingGuestInfoSection.telephone}}" userInput="{{CustomerAddressSimple.telephone}}" stepKey="enterTelephone"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> + <click selector="{{CheckoutShippingGuestInfoSection.firstShippingMethod}}" stepKey="selectFirstShippingMethod"/> + <!-- Check that have the same values after page reload --> + <amOnPage url="{{CheckoutPage.url}}" stepKey="amOnCheckoutShippingInfoPage"/> + <waitForPageLoad stepKey="waitForShippingPageReload"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.email}}" userInput="{{CustomerEntityOne.email}}" stepKey="seeEmailOnCheckout" /> + <seeInField selector="{{CheckoutShippingGuestInfoSection.firstName}}" userInput="{{CustomerEntityOne.firstName}}" stepKey="seeFirstnameOnCheckout" /> + <seeInField selector="{{CheckoutShippingGuestInfoSection.lastName}}" userInput="{{CustomerEntityOne.lastName}}" stepKey="seeLastnameOnCheckout" /> + <seeInField selector="{{CheckoutShippingGuestInfoSection.street}}" userInput="{{CustomerAddressSimple.street[0]}}" stepKey="seeStreetOnCheckout" /> + <seeInField selector="{{CheckoutShippingGuestInfoSection.city}}" userInput="{{CustomerAddressSimple.city}}" stepKey="seeCityOnCheckout" /> + <seeInField selector="{{CheckoutShippingGuestInfoSection.region}}" userInput="{{CustomerAddressSimple.state}}" stepKey="seeStateOnCheckout" /> + <seeInField selector="{{CheckoutShippingGuestInfoSection.postcode}}" userInput="{{CustomerAddressSimple.postcode}}" stepKey="seePostcodeOnCheckout" /> + <seeInField selector="{{CheckoutShippingGuestInfoSection.telephone}}" userInput="{{CustomerAddressSimple.telephone}}" stepKey="seePhoneOnCheckout" /> + <!-- Click next button to open payment section --> + <click selector="{{CheckoutShippingGuestInfoSection.next}}" stepKey="clickNext"/> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="guestSelectCheckMoneyOrderPayment" /> + <!-- Reload payment section --> + <amOnPage url="{{GuestCheckoutReviewAndPaymentsPage.url}}" stepKey="amOnCheckoutPaymentsPage"/> + <waitForElement selector="{{CheckoutPaymentSection.placeOrder}}" time="30" stepKey="waitForPlaceOrderButton2"/> + <!-- Check that address block contains correct information --> + <see selector="{{CheckoutPaymentSection.billingAddress}}" userInput="{{CustomerAddressSimple.firstName}}" stepKey="seeBilllingFirstName" /> + <see selector="{{CheckoutPaymentSection.billingAddress}}" userInput="{{CustomerAddressSimple.lastName}}" stepKey="seeBilllingLastName" /> + <see selector="{{CheckoutPaymentSection.billingAddress}}" userInput="{{CustomerAddressSimple.street[0]}}" stepKey="seeBilllingStreet" /> + <see selector="{{CheckoutPaymentSection.billingAddress}}" userInput="{{CustomerAddressSimple.city}}" stepKey="seeBilllingCity" /> + <see selector="{{CheckoutPaymentSection.billingAddress}}" userInput="{{CustomerAddressSimple.state}}" stepKey="seeBilllingState" /> + <see selector="{{CheckoutPaymentSection.billingAddress}}" userInput="{{CustomerAddressSimple.postcode}}" stepKey="seeBilllingPostcode" /> + <see selector="{{CheckoutPaymentSection.billingAddress}}" userInput="{{CustomerAddressSimple.telephone}}" stepKey="seeBilllingTelephone" /> + <!-- Check that "Ship To" block contains correct information --> + <see selector="{{CheckoutPaymentSection.shipToInfomation}}" userInput="{{CustomerAddressSimple.firstName}}" stepKey="seeShipToFirstName" /> + <see selector="{{CheckoutPaymentSection.shipToInfomation}}" userInput="{{CustomerAddressSimple.lastName}}" stepKey="seeShipToLastName" /> + <see selector="{{CheckoutPaymentSection.shipToInfomation}}" userInput="{{CustomerAddressSimple.street[0]}}" stepKey="seeShipToStreet" /> + <see selector="{{CheckoutPaymentSection.shipToInfomation}}" userInput="{{CustomerAddressSimple.city}}" stepKey="seeShipToCity" /> + <see selector="{{CheckoutPaymentSection.shipToInfomation}}" userInput="{{CustomerAddressSimple.state}}" stepKey="seeShipToState" /> + <see selector="{{CheckoutPaymentSection.shipToInfomation}}" userInput="{{CustomerAddressSimple.postcode}}" stepKey="seeShipToPostcode" /> + <see selector="{{CheckoutPaymentSection.shipToInfomation}}" userInput="{{CustomerAddressSimple.telephone}}" stepKey="seeShipToTelephone" /> + </test> +</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/ActionGroup/AdminCartPriceRuleActionGroup.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/ActionGroup/AdminCartPriceRuleActionGroup.xml new file mode 100644 index 0000000000000..24d881ee6081d --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/ActionGroup/AdminCartPriceRuleActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + <actionGroup name="selectNotLoggedInCustomerGroup"> + <!-- This actionGroup was created to be merged from B2B because B2B has a very different form control here --> + <selectOption selector="{{AdminCartPriceRulesFormSection.customerGroups}}" userInput="NOT LOGGED IN" stepKey="selectCustomerGroup"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Section/PriceRuleConditionsSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Section/PriceRuleConditionsSection.xml index 39c6dd6b31968..93ed408ce7a0e 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Section/PriceRuleConditionsSection.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Section/PriceRuleConditionsSection.xml @@ -16,5 +16,9 @@ <element name="treeRoot" type="text" selector=".x-tree-root-ct.x-tree-lines"/> <element name="lastTreeNode" type="text" selector=".x-tree-root-ct.x-tree-lines > div > li > ul > li:last-child div img.x-tree-elbow-end-plus"/> <element name="subcategory4level" type="text" selector=".x-tree-root-ct.x-tree-lines > div > li > ul > li > ul > li > ul > li > ul > li > div img.x-tree-elbow-end-plus"/> + + <element name="ruleParamLink" type="button" selector="//*[@id='conditions__{{var1}}__children']/li[{{var2}}]/span[{{var3}}]/a" parameterized="true"/> + <element name="operatorByIndex" type="input" selector="#conditions__{{var1}}--{{var2}}__operator" parameterized="true"/> + <element name="valueByIndex" type="input" selector="#conditions__{{var1}}--{{var2}}__value" parameterized="true"/> </section> </sections> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Test/StorefrontCartPriceRuleCountry.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Test/StorefrontCartPriceRuleCountry.xml new file mode 100644 index 0000000000000..a6a0d669f3435 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Test/StorefrontCartPriceRuleCountry.xml @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="StorefrontCartPriceRuleCountry"> + <annotations> + <features value="SalesRule"/> + <stories value="Create cart price rule"/> + <title value="Customer should only see cart price rule discount if condition shipping country"/> + <description value="Customer should only see cart price rule discount if condition shipping country"/> + <severity value="AVERAGE"/> + <testCaseId value="MC-241"/> + <group value="SalesRule"/> + </annotations> + + <before> + <createData entity="_defaultCategory" stepKey="createPreReqCategory"/> + <createData entity="_defaultProduct" stepKey="createPreReqProduct"> + <requiredEntity createDataKey="createPreReqCategory"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + + <after> + <actionGroup ref="DeleteCartPriceRuleByName" stepKey="cleanUpRule"> + <argument name="ruleName" value="{{SimpleSalesRule.name}}"/> + </actionGroup> + <deleteData createDataKey="createPreReqCategory" stepKey="deletePreReqCategory"/> + <deleteData createDataKey="createPreReqProduct" stepKey="deletePreReqProduct"/> + <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> + </after> + + <!-- Create the rule... --> + <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="amOnCartPriceList"/> + <waitForPageLoad stepKey="waitForRulesPage"/> + <click selector="{{AdminCartPriceRulesSection.addNewRuleButton}}" stepKey="clickAddNewRule"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{SimpleSalesRule.name}}" stepKey="fillRuleName"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="Main Website" stepKey="selectWebsites"/> + <actionGroup ref="selectNotLoggedInCustomerGroup" stepKey="selectNotLoggedInCustomerGroup"/> + <click selector="{{PriceRuleConditionsSection.conditionsTab}}" stepKey="expandConditions"/> + <!-- Scroll down to fix some flaky behavior... --> + <scrollTo selector="{{PriceRuleConditionsSection.conditionsTab}}" stepKey="scrollToConditionsTab"/> + <waitForElementVisible selector="{{PriceRuleConditionsSection.createNewRule}}" stepKey="waitForNewRule"/> + <click selector="{{PriceRuleConditionsSection.createNewRule}}" stepKey="clickNewRule"/> + <selectOption selector="{{PriceRuleConditionsSection.rulesDropdown}}" userInput="Shipping Country" stepKey="selectProductAttributes"/> + <waitForPageLoad stepKey="wait1"/> + <click selector="{{PriceRuleConditionsSection.ruleParamLink('1', '1', '2')}}" stepKey="startEditValue"/> + <waitForPageLoad stepKey="wait4"/> + <selectOption selector="{{PriceRuleConditionsSection.valueByIndex('1', '1')}}" userInput="Brazil" stepKey="fillValue"/> + <waitForPageLoad stepKey="wait5"/> + <click selector="{{AdminCartPriceRulesFormSection.actionsHeader}}" stepKey="clickToExpandActions"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.apply}}" userInput="Fixed amount discount for whole cart" stepKey="selectActionType"/> + <fillField selector="{{AdminCartPriceRulesFormSection.discountAmount}}" userInput="9.99" stepKey="fillDiscountAmount"/> + <click selector="{{AdminCartPriceRulesFormSection.save}}" stepKey="clickSaveButton"/> + <see selector="{{AdminCartPriceRulesSection.messages}}" userInput="You saved the rule." stepKey="seeSuccessMessage"/> + + <!-- Add the product we created to our cart --> + <amOnPage url="$$createPreReqProduct.name$$.html" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + <fillField selector="{{StorefrontProductActionSection.quantity}}" userInput="1" stepKey="fillQuantity"/> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> + <waitForPageLoad stepKey="waitForAddToCart"/> + + <!-- Should not see the discount yet because we have not set country --> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCartPage"/> + <waitForPageLoad stepKey="waitForCartPage"/> + <see selector="{{CheckoutCartSummarySection.subtotal}}" userInput="$123.00" stepKey="seeSubtotal"/> + <dontSeeElement selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="dontSeeDiscount"/> + + <!-- See discount if we use valid country --> + <click selector="{{CheckoutCartSummarySection.shippingHeading}}" stepKey="expandShipping"/> + <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="Brazil" stepKey="fillCountry"/> + <waitForPageLoad stepKey="waitForCountry1"/> + <waitForElementVisible selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="waitForDiscountElement"/> + <see selector="{{CheckoutCartSummarySection.discountAmount}}" userInput="-$9.99" stepKey="seeDiscountTotal"/> + + <!-- Do not see discount with other country --> + <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="United States" stepKey="fillCountry2"/> + <waitForPageLoad stepKey="waitForCountry2"/> + <dontSeeElement selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="dontSeeDiscount2"/> + </test> +</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Test/StorefrontCartPriceRulePostcode.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Test/StorefrontCartPriceRulePostcode.xml new file mode 100644 index 0000000000000..97936382e60e0 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Test/StorefrontCartPriceRulePostcode.xml @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="StorefrontCartPriceRulePostcode"> + <annotations> + <features value="SalesRule"/> + <stories value="Create cart price rule"/> + <title value="Customer should only see cart price rule discount if condition shipping postcode"/> + <description value="Customer should only see cart price rule discount if condition shipping postcode"/> + <severity value="AVERAGE"/> + <testCaseId value="MC-238"/> + <group value="SalesRule"/> + </annotations> + + <before> + <createData entity="_defaultCategory" stepKey="createPreReqCategory"/> + <createData entity="_defaultProduct" stepKey="createPreReqProduct"> + <requiredEntity createDataKey="createPreReqCategory"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + + <after> + <actionGroup ref="DeleteCartPriceRuleByName" stepKey="cleanUpRule"> + <argument name="ruleName" value="{{SimpleSalesRule.name}}"/> + </actionGroup> + <deleteData createDataKey="createPreReqCategory" stepKey="deletePreReqCategory"/> + <deleteData createDataKey="createPreReqProduct" stepKey="deletePreReqProduct"/> + <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> + </after> + + <!-- Create the rule... --> + <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="amOnCartPriceList"/> + <waitForPageLoad stepKey="waitForRulesPage"/> + <click selector="{{AdminCartPriceRulesSection.addNewRuleButton}}" stepKey="clickAddNewRule"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{SimpleSalesRule.name}}" stepKey="fillRuleName"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="Main Website" stepKey="selectWebsites"/> + <actionGroup ref="selectNotLoggedInCustomerGroup" stepKey="selectNotLoggedInCustomerGroup"/> + <click selector="{{PriceRuleConditionsSection.conditionsTab}}" stepKey="expandConditions"/> + <!-- Scroll down to fix some flaky behavior... --> + <scrollTo selector="{{PriceRuleConditionsSection.conditionsTab}}" stepKey="scrollToConditionsTab"/> + <waitForElementVisible selector="{{PriceRuleConditionsSection.createNewRule}}" stepKey="waitForNewRule"/> + <click selector="{{PriceRuleConditionsSection.createNewRule}}" stepKey="clickNewRule"/> + <selectOption selector="{{PriceRuleConditionsSection.rulesDropdown}}" userInput="Shipping Postcode" stepKey="selectProductAttributes"/> + <waitForPageLoad stepKey="wait1"/> + <click selector="{{PriceRuleConditionsSection.ruleParamLink('1', '1', '1')}}" stepKey="startEditOperator"/> + <waitForPageLoad stepKey="wait2"/> + <selectOption selector="{{PriceRuleConditionsSection.operatorByIndex('1', '1')}}" userInput="is one of" stepKey="fillOperator"/> + <waitForPageLoad stepKey="wait3"/> + <click selector="{{PriceRuleConditionsSection.ruleParamLink('1', '1', '2')}}" stepKey="startEditValue"/> + <waitForPageLoad stepKey="wait4"/> + <fillField selector="{{PriceRuleConditionsSection.valueByIndex('1', '1')}}" userInput="78613" stepKey="fillValue"/> + <waitForPageLoad stepKey="wait5"/> + <click selector="{{AdminCartPriceRulesFormSection.actionsHeader}}" stepKey="clickToExpandActions"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.apply}}" userInput="Fixed amount discount for whole cart" stepKey="selectActionType"/> + <fillField selector="{{AdminCartPriceRulesFormSection.discountAmount}}" userInput="9.99" stepKey="fillDiscountAmount"/> + <click selector="{{AdminCartPriceRulesFormSection.save}}" stepKey="clickSaveButton"/> + <see selector="{{AdminCartPriceRulesSection.messages}}" userInput="You saved the rule." stepKey="seeSuccessMessage"/> + + <!-- Add the product we created to our cart --> + <amOnPage url="$$createPreReqProduct.name$$.html" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + <fillField selector="{{StorefrontProductActionSection.quantity}}" userInput="1" stepKey="fillQuantity"/> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> + <waitForPageLoad stepKey="waitForAddToCart"/> + + <!-- Should not see the discount yet because we have not filled in postcode --> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCartPage"/> + <waitForPageLoad stepKey="waitForCartPage"/> + <see selector="{{CheckoutCartSummarySection.subtotal}}" userInput="$123.00" stepKey="seeSubtotal"/> + <dontSeeElement selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="dontSeeDiscount"/> + + <!-- See discount if we use valid postcode --> + <click selector="{{CheckoutCartSummarySection.shippingHeading}}" stepKey="expandShipping"/> + <fillField selector="{{CheckoutCartSummarySection.postcode}}" userInput="78613" stepKey="fillPostcode"/> + <waitForPageLoad stepKey="waitForPostcode1"/> + <waitForElementVisible selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="waitForDiscountElement"/> + <see selector="{{CheckoutCartSummarySection.discountAmount}}" userInput="-$9.99" stepKey="seeDiscountTotal"/> + + <!-- Do not see discount with other postcode --> + <fillField selector="{{CheckoutCartSummarySection.postcode}}" userInput="90210" stepKey="fillPostcode2"/> + <waitForPageLoad stepKey="waitForPostcode2"/> + <dontSeeElement selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="dontSeeDiscount2"/> + </test> +</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Test/StorefrontCartPriceRuleQuantity.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Test/StorefrontCartPriceRuleQuantity.xml new file mode 100644 index 0000000000000..f4342b5d480b4 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Test/StorefrontCartPriceRuleQuantity.xml @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="StorefrontCartPriceRuleQuantity"> + <annotations> + <features value="SalesRule"/> + <stories value="Create cart price rule"/> + <title value="Customer should only see cart price rule discount if condition total items quantity greater than"/> + <description value="Customer should only see cart price rule discount if condition total items quantity greater than"/> + <severity value="AVERAGE"/> + <testCaseId value="MC-236"/> + <group value="SalesRule"/> + </annotations> + + <before> + <createData entity="_defaultCategory" stepKey="createPreReqCategory"/> + <createData entity="_defaultProduct" stepKey="createPreReqProduct"> + <requiredEntity createDataKey="createPreReqCategory"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + + <after> + <actionGroup ref="DeleteCartPriceRuleByName" stepKey="cleanUpRule"> + <argument name="ruleName" value="{{SimpleSalesRule.name}}"/> + </actionGroup> + <deleteData createDataKey="createPreReqCategory" stepKey="deletePreReqCategory"/> + <deleteData createDataKey="createPreReqProduct" stepKey="deletePreReqProduct"/> + <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> + </after> + + <!-- Create the rule... --> + <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="amOnCartPriceList"/> + <waitForPageLoad stepKey="waitForRulesPage"/> + <click selector="{{AdminCartPriceRulesSection.addNewRuleButton}}" stepKey="clickAddNewRule"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{SimpleSalesRule.name}}" stepKey="fillRuleName"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="Main Website" stepKey="selectWebsites"/> + <actionGroup ref="selectNotLoggedInCustomerGroup" stepKey="selectNotLoggedInCustomerGroup"/> + <click selector="{{PriceRuleConditionsSection.conditionsTab}}" stepKey="expandConditions"/> + <!-- Scroll down to fix some flaky behavior... --> + <scrollTo selector="{{PriceRuleConditionsSection.conditionsTab}}" stepKey="scrollToConditionsTab"/> + <waitForElementVisible selector="{{PriceRuleConditionsSection.createNewRule}}" stepKey="waitForNewRule"/> + <click selector="{{PriceRuleConditionsSection.createNewRule}}" stepKey="clickNewRule"/> + <selectOption selector="{{PriceRuleConditionsSection.rulesDropdown}}" userInput="Total Items Quantity" stepKey="selectProductAttributes"/> + <waitForPageLoad stepKey="waitForConditions"/> + <click selector="{{PriceRuleConditionsSection.ruleParamLink('1', '1', '1')}}" stepKey="startEditOperator"/> + <selectOption selector="{{PriceRuleConditionsSection.operatorByIndex('1', '1')}}" userInput="greater than" stepKey="fillOperator"/> + <click selector="{{PriceRuleConditionsSection.ruleParamLink('1', '1', '2')}}" stepKey="startEditValue"/> + <fillField selector="{{PriceRuleConditionsSection.valueByIndex('1', '1')}}" userInput="1" stepKey="fillValue"/> + <click selector="{{AdminCartPriceRulesFormSection.actionsHeader}}" stepKey="clickToExpandActions"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.apply}}" userInput="Fixed amount discount for whole cart" stepKey="selectActionType"/> + <fillField selector="{{AdminCartPriceRulesFormSection.discountAmount}}" userInput="1.00" stepKey="fillDiscountAmount"/> + <click selector="{{AdminCartPriceRulesFormSection.save}}" stepKey="clickSaveButton"/> + <see selector="{{AdminCartPriceRulesSection.messages}}" userInput="You saved the rule." stepKey="seeSuccessMessage"/> + + <!-- Add 1 product to the cart --> + <amOnPage url="$$createPreReqProduct.name$$.html" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + <fillField selector="{{StorefrontProductActionSection.quantity}}" userInput="1" stepKey="fillQuantity"/> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> + <waitForPageLoad stepKey="waitForAddToCart"/> + + <!-- Should not see the discount yet because we have only 1 item in our cart --> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCartPage"/> + <waitForPageLoad stepKey="waitForCartPage"/> + <see selector="{{CheckoutCartSummarySection.subtotal}}" userInput="$123.00" stepKey="seeSubtotal"/> + <dontSeeElement selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="dontSeeDiscount"/> + + <!-- Add the same product to the cart again (2 total) --> + <amOnPage url="$$createPreReqProduct.name$$.html" stepKey="goToProductPage2"/> + <waitForPageLoad stepKey="waitForProductPageLoad2"/> + <fillField selector="{{StorefrontProductActionSection.quantity}}" userInput="1" stepKey="fillQuantity2"/> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart2"/> + <waitForPageLoad stepKey="waitForAddToCart2"/> + + <!-- Now we should see the discount because we have more than 1 item --> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCartPage2"/> + <waitForPageLoad stepKey="waitForCartPage2"/> + <see selector="{{CheckoutCartSummarySection.subtotal}}" userInput="$246.00" stepKey="seeSubtotal2"/> + <waitForElementVisible selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="waitForDiscountElement"/> + <see selector="{{CheckoutCartSummarySection.discountAmount}}" userInput="-$1.00" stepKey="seeDiscountTotal"/> + </test> +</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Test/StorefrontCartPriceRuleState.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Test/StorefrontCartPriceRuleState.xml new file mode 100644 index 0000000000000..e2de2117d78e7 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Test/StorefrontCartPriceRuleState.xml @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="StorefrontCartPriceRuleState"> + <annotations> + <features value="SalesRule"/> + <stories value="Create cart price rule"/> + <title value="Customer should only see cart price rule discount if condition shipping state/province"/> + <description value="Customer should only see cart price rule discount if condition shipping state/province"/> + <severity value="AVERAGE"/> + <testCaseId value="MC-239"/> + <group value="SalesRule"/> + </annotations> + + <before> + <createData entity="_defaultCategory" stepKey="createPreReqCategory"/> + <createData entity="_defaultProduct" stepKey="createPreReqProduct"> + <requiredEntity createDataKey="createPreReqCategory"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + + <after> + <actionGroup ref="DeleteCartPriceRuleByName" stepKey="cleanUpRule"> + <argument name="ruleName" value="{{SimpleSalesRule.name}}"/> + </actionGroup> + <deleteData createDataKey="createPreReqCategory" stepKey="deletePreReqCategory"/> + <deleteData createDataKey="createPreReqProduct" stepKey="deletePreReqProduct"/> + <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> + </after> + + <!-- Create the rule... --> + <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="amOnCartPriceList"/> + <waitForPageLoad stepKey="waitForRulesPage"/> + <click selector="{{AdminCartPriceRulesSection.addNewRuleButton}}" stepKey="clickAddNewRule"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{SimpleSalesRule.name}}" stepKey="fillRuleName"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="Main Website" stepKey="selectWebsites"/> + <actionGroup ref="selectNotLoggedInCustomerGroup" stepKey="selectNotLoggedInCustomerGroup"/> + <click selector="{{PriceRuleConditionsSection.conditionsTab}}" stepKey="expandConditions"/> + <!-- Scroll down to fix some flaky behavior... --> + <scrollTo selector="{{PriceRuleConditionsSection.conditionsTab}}" stepKey="scrollToConditionsTab"/> + <waitForElementVisible selector="{{PriceRuleConditionsSection.createNewRule}}" stepKey="waitForNewRule"/> + <click selector="{{PriceRuleConditionsSection.createNewRule}}" stepKey="clickNewRule"/> + <selectOption selector="{{PriceRuleConditionsSection.rulesDropdown}}" userInput="Shipping State/Province" stepKey="selectProductAttributes"/> + <waitForPageLoad stepKey="wait1"/> + <click selector="{{PriceRuleConditionsSection.ruleParamLink('1', '1', '2')}}" stepKey="startEditValue"/> + <waitForPageLoad stepKey="wait2"/> + <selectOption selector="{{PriceRuleConditionsSection.valueByIndex('1', '1')}}" userInput="Indiana" stepKey="fillValue"/> + <waitForPageLoad stepKey="wait3"/> + <click selector="{{AdminCartPriceRulesFormSection.actionsHeader}}" stepKey="clickToExpandActions"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.apply}}" userInput="Fixed amount discount for whole cart" stepKey="selectActionType"/> + <fillField selector="{{AdminCartPriceRulesFormSection.discountAmount}}" userInput="9.99" stepKey="fillDiscountAmount"/> + <click selector="{{AdminCartPriceRulesFormSection.save}}" stepKey="clickSaveButton"/> + <see selector="{{AdminCartPriceRulesSection.messages}}" userInput="You saved the rule." stepKey="seeSuccessMessage"/> + + <!-- Add the product we created to our cart --> + <amOnPage url="$$createPreReqProduct.name$$.html" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + <fillField selector="{{StorefrontProductActionSection.quantity}}" userInput="1" stepKey="fillQuantity"/> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> + <waitForPageLoad stepKey="waitForAddToCart"/> + + <!-- Should not see the discount yet because we have not filled in postcode --> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCartPage"/> + <waitForPageLoad stepKey="waitForCartPage"/> + <see selector="{{CheckoutCartSummarySection.subtotal}}" userInput="$123.00" stepKey="seeSubtotal"/> + <dontSeeElement selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="dontSeeDiscount"/> + + <!-- See discount if we use valid postcode --> + <click selector="{{CheckoutCartSummarySection.shippingHeading}}" stepKey="expandShipping"/> + <selectOption selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="Indiana" stepKey="fillState"/> + <waitForPageLoad stepKey="waitForPostcode1"/> + <waitForElementVisible selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="waitForDiscountElement"/> + <see selector="{{CheckoutCartSummarySection.discountAmount}}" userInput="-$9.99" stepKey="seeDiscountTotal"/> + + <!-- Do not see discount with other postcode --> + <selectOption selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="Texas" stepKey="fillState2"/> + <waitForPageLoad stepKey="waitForPostcode2"/> + <dontSeeElement selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="dontSeeDiscount2"/> + </test> +</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Test/StorefrontCartPriceRuleSubtotal.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Test/StorefrontCartPriceRuleSubtotal.xml new file mode 100644 index 0000000000000..6c49534ee43c1 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Test/StorefrontCartPriceRuleSubtotal.xml @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="StorefrontCartPriceRuleSubtotal"> + <annotations> + <features value="SalesRule"/> + <stories value="Create cart price rule"/> + <title value="Customer should only see cart price rule discount if condition subtotal equals or greater than"/> + <description value="Customer should only see cart price rule discount if condition subtotal equals or greater than"/> + <severity value="AVERAGE"/> + <testCaseId value="MC-235"/> + <group value="SalesRule"/> + </annotations> + + <before> + <createData entity="_defaultCategory" stepKey="createPreReqCategory"/> + <createData entity="_defaultProduct" stepKey="createPreReqProduct"> + <requiredEntity createDataKey="createPreReqCategory"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + + <after> + <actionGroup ref="DeleteCartPriceRuleByName" stepKey="cleanUpRule"> + <argument name="ruleName" value="{{SimpleSalesRule.name}}"/> + </actionGroup> + <deleteData createDataKey="createPreReqCategory" stepKey="deletePreReqCategory"/> + <deleteData createDataKey="createPreReqProduct" stepKey="deletePreReqProduct"/> + <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> + </after> + + <!-- Create the rule... --> + <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="amOnCartPriceList"/> + <waitForPageLoad stepKey="waitForRulesPage"/> + <click selector="{{AdminCartPriceRulesSection.addNewRuleButton}}" stepKey="clickAddNewRule"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{SimpleSalesRule.name}}" stepKey="fillRuleName"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="Main Website" stepKey="selectWebsites"/> + <actionGroup ref="selectNotLoggedInCustomerGroup" stepKey="selectNotLoggedInCustomerGroup"/> + <click selector="{{PriceRuleConditionsSection.conditionsTab}}" stepKey="expandConditions"/> + <!-- Scroll down to fix some flaky behavior... --> + <scrollTo selector="{{PriceRuleConditionsSection.conditionsTab}}" stepKey="scrollToConditionsTab"/> + <click selector="{{PriceRuleConditionsSection.createNewRule}}" stepKey="clickNewRule"/> + <selectOption selector="{{PriceRuleConditionsSection.rulesDropdown}}" userInput="Subtotal" stepKey="selectProductAttributes"/> + <waitForPageLoad stepKey="waitForConditions"/> + <click selector="{{PriceRuleConditionsSection.ruleParamLink('1', '1', '1')}}" stepKey="startEditOperator"/> + <selectOption selector="{{PriceRuleConditionsSection.operatorByIndex('1', '1')}}" userInput="equals or greater than" stepKey="fillOperator"/> + <click selector="{{PriceRuleConditionsSection.ruleParamLink('1', '1', '2')}}" stepKey="startEditValue"/> + <fillField selector="{{PriceRuleConditionsSection.valueByIndex('1', '1')}}" userInput="200" stepKey="fillValue"/> + <click selector="{{AdminCartPriceRulesFormSection.actionsHeader}}" stepKey="clickToExpandActions"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.apply}}" userInput="Fixed amount discount for whole cart" stepKey="selectActionType"/> + <fillField selector="{{AdminCartPriceRulesFormSection.discountAmount}}" userInput="0.01" stepKey="fillDiscountAmount"/> + <click selector="{{AdminCartPriceRulesFormSection.save}}" stepKey="clickSaveButton"/> + <see selector="{{AdminCartPriceRulesSection.messages}}" userInput="You saved the rule." stepKey="seeSuccessMessage"/> + + <!-- Add 1 product worth $123.00 to the cart --> + <amOnPage url="$$createPreReqProduct.name$$.html" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + <fillField selector="{{StorefrontProductActionSection.quantity}}" userInput="1" stepKey="fillQuantity"/> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> + <waitForPageLoad stepKey="waitForAddToCart"/> + + <!-- Should not see the discount yet because we have not exceeded $200 --> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCartPage"/> + <waitForPageLoad stepKey="waitForCartPage"/> + <see selector="{{CheckoutCartSummarySection.subtotal}}" userInput="$123.00" stepKey="seeSubtotal"/> + <dontSeeElement selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="dontSeeDiscount"/> + + <!-- Add the same product to the cart again ($246.00 subtotal) --> + <amOnPage url="$$createPreReqProduct.name$$.html" stepKey="goToProductPage2"/> + <waitForPageLoad stepKey="waitForProductPageLoad2"/> + <fillField selector="{{StorefrontProductActionSection.quantity}}" userInput="1" stepKey="fillQuantity2"/> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart2"/> + <waitForPageLoad stepKey="waitForAddToCart2"/> + + <!-- Now we should see the discount because we exceeded $200 --> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCartPage2"/> + <waitForPageLoad stepKey="waitForCartPage2"/> + <see selector="{{CheckoutCartSummarySection.subtotal}}" userInput="$246.00" stepKey="seeSubtotal2"/> + <waitForElementVisible selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="waitForDiscountElement"/> + <see selector="{{CheckoutCartSummarySection.discountAmount}}" userInput="-$0.01" stepKey="seeDiscountTotal"/> + </test> +</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/User/Section/AdminEditRoleInfoSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/User/Section/AdminEditRoleInfoSection.xml index 3e5b4ac6ecc88..69d1fdea9cb51 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/User/Section/AdminEditRoleInfoSection.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/User/Section/AdminEditRoleInfoSection.xml @@ -12,6 +12,10 @@ <element name="roleResourcesTab" type="button" selector="#role_info_tabs_account"/> <element name="backButton" type="button" selector="button[title='Back']"/> <element name="resetButton" type="button" selector="button[title='Reset']"/> + <element name="deleteButton" type="button" selector="button[title='Delete Role']"/> <element name="saveButton" type="button" selector="button[title='Save Role']"/> + <element name="message" type="text" selector=".modal-popup.confirm div.modal-content"/> + <element name="cancel" type="button" selector=".modal-popup.confirm button.action-dismiss"/> + <element name="ok" type="button" selector=".modal-popup.confirm button.action-accept" timeout="60"/> </section> </sections> diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentCreateTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentCreateTest.php index 8c8130d2b3949..d8b3c5cac52aa 100644 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentCreateTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentCreateTest.php @@ -43,13 +43,13 @@ public function testInvoke() 'qty' => $orderItem->getQtyOrdered(), 'additional_data' => null, 'description' => null, - 'entity_id' => null, + 'entity_id' => 1, 'name' => null, 'parent_id' => null, 'price' => null, 'product_id' => null, 'row_total' => null, - 'sku' => null, + 'sku' => 'simple', 'weight' => null, ], ]; diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentGetTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentGetTest.php index aaa7a5a6d4436..2b7e76aee0751 100644 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentGetTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentGetTest.php @@ -5,6 +5,8 @@ */ namespace Magento\Sales\Service\V1; +use Magento\Framework\Api\ExtensibleDataInterface; +use Magento\Framework\Api\SimpleDataObjectConverter; use Magento\TestFramework\TestCase\WebapiAbstract; /** @@ -57,7 +59,18 @@ public function testShipmentGet() unset($data['tracks']); foreach ($data as $key => $value) { if (!empty($value)) { - $this->assertEquals($shipment->getData($key), $value, $key); + if ($key === ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY) { + foreach ($value as $extensionAttributeKey => $extensionAttributeValue) { + $methodName = 'get' . + SimpleDataObjectConverter::snakeCaseToUpperCamelCase($extensionAttributeKey); + $this->assertEquals( + $shipment->getExtensionAttributes()->$methodName(), + $extensionAttributeValue + ); + } + } else { + $this->assertEquals($shipment->getData($key), $value, $key); + } } } $shipmentItem = $this->objectManager->get(\Magento\Sales\Model\Order\Shipment\Item::class); diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOrderBackendTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOrderBackendTest.xml index 8ffcab68cdc62..acf15c0e28252 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOrderBackendTest.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOrderBackendTest.xml @@ -27,8 +27,8 @@ <data name="creditCard/data/payment_code" xsi:type="string">braintree</data> <data name="configData" xsi:type="string">braintree</data> <data name="status" xsi:type="string">Processing</data> - <data name="orderButtonsAvailable" xsi:type="string">Back, Cancel, Send Email, Hold, Invoice, Ship, Reorder, Edit</data> - <constraint name="Magento\Sales\Test\Constraint\AssertOrderSuccessCreateMessage" /> + <data name="orderButtonsAvailable" xsi:type="string">Back, Cancel, Send Email, Invoice, Reorder, Edit</data> + <constraint name="Magento\Shipping\Test\Constraint\AssertShipmentSuccessCreateMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderButtonsAvailable" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> @@ -55,9 +55,9 @@ <data name="creditCard/dataset" xsi:type="string">visa_default</data> <data name="creditCard/data/payment_code" xsi:type="string">braintree</data> <data name="configData" xsi:type="string">braintree, braintree_sale</data> - <data name="status" xsi:type="string">Processing</data> - <data name="orderButtonsAvailable" xsi:type="string">Back, Send Email, Hold, Ship, Reorder</data> - <constraint name="Magento\Sales\Test\Constraint\AssertOrderSuccessCreateMessage" /> + <data name="status" xsi:type="string">Complete</data> + <data name="orderButtonsAvailable" xsi:type="string">Back, Send Email, Reorder</data> + <constraint name="Magento\Shipping\Test\Constraint\AssertShipmentSuccessCreateMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderButtonsAvailable" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> @@ -83,7 +83,7 @@ <data name="creditCard/dataset" xsi:type="string">visa_braintree_fraud_rejected</data> <data name="configData" xsi:type="string">braintree</data> <data name="status" xsi:type="string">Processing</data> - <constraint name="Magento\Sales\Test\Constraint\AssertOrderSuccessCreateMessage" /> + <constraint name="Magento\Shipping\Test\Constraint\AssertShipmentSuccessCreateMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGridOnFrontend" /> </variation> diff --git a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Page/Adminhtml/CatalogRuleNew.xml b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Page/Adminhtml/CatalogRuleNew.xml index 1823dd1e1bbe2..ba4ff8d42d951 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Page/Adminhtml/CatalogRuleNew.xml +++ b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Page/Adminhtml/CatalogRuleNew.xml @@ -9,6 +9,6 @@ <page name="CatalogRuleNew" area="Adminhtml" mca="catalog_rule/promo_catalog/new" module="Magento_CatalogRule"> <block name="formPageActions" class="Magento\CatalogRule\Test\Block\Adminhtml\FormPageActions" locator=".page-main-actions" strategy="css selector"/> <block name="editForm" class="Magento\CatalogRule\Test\Block\Adminhtml\Promo\Catalog\Edit\PromoForm" locator="[id='page:main-container']" strategy="css selector"/> - <block name="modalBlock" class="Magento\Ui\Test\Block\Adminhtml\Modal" locator="._show[data-role=modal][style='z-index: 900;']" strategy="css selector"/> + <block name="modalBlock" class="Magento\Ui\Test\Block\Adminhtml\Modal" locator="._show[data-role=modal][style='z-index: 902;']" strategy="css selector"/> </page> </config> diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Actions.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Actions.php index 573f28f09b78c..7a6903ef47aac 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Actions.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Actions.php @@ -137,6 +137,16 @@ class Actions extends Block */ protected $confirmModal = '.confirm._show[data-role=modal]'; + /** + * Is shipment can be created. + * + * @return bool + */ + public function canShip() + { + return $this->_rootElement->find($this->ship)->isVisible(); + } + /** * Ship order. * diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateOrderBackendTest.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateOrderBackendTest.xml index 439b8a4492a1a..c4e03b94d2ada 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateOrderBackendTest.xml +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateOrderBackendTest.xml @@ -19,7 +19,7 @@ </data> <data name="payment/method" xsi:type="string">cashondelivery</data> <data name="configData" xsi:type="string">cashondelivery</data> - <constraint name="Magento\Sales\Test\Constraint\AssertOrderSuccessCreateMessage" /> + <constraint name="Magento\Shipping\Test\Constraint\AssertShipmentSuccessCreateMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductsOutOfStock" /> </variation> @@ -35,7 +35,7 @@ </data> <data name="payment/method" xsi:type="string">cashondelivery</data> <data name="configData" xsi:type="string">cashondelivery</data> - <constraint name="Magento\Sales\Test\Constraint\AssertOrderSuccessCreateMessage" /> + <constraint name="Magento\Shipping\Test\Constraint\AssertShipmentSuccessCreateMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> <constraint name="Magento\Sales\Test\Constraint\AssertReorderButtonIsNotVisibleOnFrontend" /> </variation> diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/CreateShipmentStep.php b/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/CreateShipmentStep.php index 79abd438a1f2e..dcee66b44646e 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/CreateShipmentStep.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/CreateShipmentStep.php @@ -99,13 +99,21 @@ public function run() { $this->orderIndex->open(); $this->orderIndex->getSalesOrderGrid()->searchAndOpen(['id' => $this->order->getId()]); - $this->salesOrderView->getPageActions()->ship(); - if (!empty($this->data)) { - $this->orderShipmentNew->getFormBlock()->fillData($this->data, $this->order->getEntityId()['products']); + $shipmentIds = []; + /** + * As this step is used in general scenarios and not all test cases has shippable items(ex: virtual product) + * we need to check, if it possible to create shipment for given order. + */ + if ($this->salesOrderView->getPageActions()->canShip()) { + $this->salesOrderView->getPageActions()->ship(); + if (!empty($this->data)) { + $this->orderShipmentNew->getFormBlock()->fillData($this->data, $this->order->getEntityId()['products']); + } + $this->orderShipmentNew->getFormBlock()->submit(); + $shipmentIds = $this->getShipmentIds(); } - $this->orderShipmentNew->getFormBlock()->submit(); - return ['shipmentIds' => $this->getShipmentIds()]; + return ['shipmentIds' => $shipmentIds]; } /** diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/etc/testcase.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/etc/testcase.xml index c7c4a2276e4b7..28b7e3a69f67e 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/etc/testcase.xml +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/etc/testcase.xml @@ -36,7 +36,8 @@ <step name="fillShippingAddress" module="Magento_Sales" next="selectShippingMethodForOrder" /> <step name="selectShippingMethodForOrder" module="Magento_Sales" next="selectPaymentMethodForOrder" /> <step name="selectPaymentMethodForOrder" module="Magento_Sales" next="submitOrder" /> - <step name="submitOrder" module="Magento_Sales" /> + <step name="submitOrder" module="Magento_Sales" next="createShipment"/> + <step name="createShipment" module="Magento_Sales"/> </scenario> <scenario name="CreateOrderBackendPartOneTest" firstStep="setupConfiguration"> <step name="setupConfiguration" module="Magento_Config" next="createProducts" /> diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/quote_with_bundle_and_options.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/quote_with_bundle_and_options.php new file mode 100644 index 0000000000000..03949115ea62c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/quote_with_bundle_and_options.php @@ -0,0 +1,54 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . 'product_with_multiple_options.php'; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var $product \Magento\Catalog\Model\Product */ +$product = $objectManager->create(\Magento\Catalog\Model\Product::class); +$product->load(3); + +/** @var $typeInstance \Magento\Bundle\Model\Product\Type */ +$typeInstance = $product->getTypeInstance(); +$typeInstance->setStoreFilter($product->getStoreId(), $product); +$optionCollection = $typeInstance->getOptionsCollection($product); + +$bundleOptions = []; +$bundleOptionsQty = []; +foreach ($optionCollection as $option) { + /** @var $option \Magento\Bundle\Model\Option */ + $selectionsCollection = $typeInstance->getSelectionsCollection([$option->getId()], $product); + if ($option->isMultiSelection()) { + $bundleOptions[$option->getId()] = array_column($selectionsCollection->toArray(), 'selection_id'); + } else { + $bundleOptions[$option->getId()] = $selectionsCollection->getFirstItem()->getSelectionId(); + } + $bundleOptionsQty[$option->getId()] = 1; +} + +$requestInfo = new \Magento\Framework\DataObject( + [ + 'product' => $product->getId(), + 'bundle_option' => $bundleOptions, + 'bundle_option_qty' => $bundleOptionsQty, + 'qty' => 1, + ] +); + +/** @var $cart \Magento\Checkout\Model\Cart */ +$cart = Bootstrap::getObjectManager()->create(\Magento\Checkout\Model\Cart::class); +$cart->addProduct($product, $requestInfo); +$cart->getQuote()->setReservedOrderId('test_cart_with_bundle_and_options'); +$cart->save(); + +/** @var $objectManager \Magento\TestFramework\ObjectManager */ +$objectManager = Bootstrap::getObjectManager(); +$objectManager->removeSharedInstance(\Magento\Checkout\Model\Session::class); diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/quote_with_bundle_and_options_rollback.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/quote_with_bundle_and_options_rollback.php new file mode 100644 index 0000000000000..d32d6fab33319 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/quote_with_bundle_and_options_rollback.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var $objectManager \Magento\TestFramework\ObjectManager */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$quote = $objectManager->create(\Magento\Quote\Model\Quote::class); +$quote->load('test_cart_with_bundle_and_options', 'reserved_order_id'); +$quote->delete(); + +/** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ +$quoteIdMask = $objectManager->create(\Magento\Quote\Model\QuoteIdMask::class); +$quoteIdMask->delete($quote->getId()); + +require __DIR__ . 'product_with_multiple_options_rollback.php'; + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php index fdc85941b32e9..4261873cc8e6e 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php @@ -5,6 +5,8 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product; +use Magento\Framework\Exception\LocalizedException; + /** * @magentoAppArea adminhtml * @magentoDbIsolation enabled @@ -224,6 +226,109 @@ public function testSaveActionCleanAttributeLabelCache() $this->assertEquals('new string translation', $this->_translate('string to translate')); } + /** + * Get attribute data preset. + * + * @return array + */ + private function getLargeOptionsSetAttributeData() + { + return [ + 'frontend_label' => [ + 0 => 'testdrop1', + 1 => '', + 2 => '', + ], + 'frontend_input' => 'select', + 'is_required' => '0', + 'update_product_preview_image' => '0', + 'use_product_image_for_swatch' => '0', + 'visual_swatch_validation' => '', + 'visual_swatch_validation_unique' => '', + 'text_swatch_validation' => '', + 'text_swatch_validation_unique' => '', + 'attribute_code' => 'test_many_options', + 'is_global' => '0', + 'default_value_text' => '', + 'default_value_yesno' => '0', + 'default_value_date' => '', + 'default_value_textarea' => '', + 'is_unique' => '0', + 'is_used_in_grid' => '1', + 'is_visible_in_grid' => '1', + 'is_filterable_in_grid' => '1', + 'is_searchable' => '0', + 'is_comparable' => '0', + 'is_filterable' => '0', + 'is_filterable_in_search' => '0', + 'is_used_for_promo_rules' => '0', + 'is_html_allowed_on_front' => '1', + 'is_visible_on_front' => '0', + 'used_in_product_listing' => '0', + 'used_for_sort_by' => '0', + 'swatch_input_type' => 'dropdown', + ]; + } + + /** + * Test attribute saving with large amount of options exceeding maximum allowed by max_input_vars limit. + * @return void + */ + public function testLargeOptionsDataSet() + { + $maxInputVars = ini_get('max_input_vars'); + // Each option is at least 4 variables array (order, admin value, first store view value, delete flag). + // Set options count to exceed max_input_vars by 100 options (400 variables). + $optionsCount = floor($maxInputVars / 4) + 100; + $attributeData = $this->getLargeOptionsSetAttributeData(); + $optionsData = []; + $expectedOptionsLabels = []; + for ($i = 0; $i < $optionsCount; $i++) { + $order = $i + 1; + $expectedOptionLabelOnStoreView = "value_{$i}_store_1"; + $expectedOptionsLabels[$i+1] = $expectedOptionLabelOnStoreView; + $optionsData []= "option[order][option_{$i}]={$order}"; + $optionsData []= "option[value][option_{$i}][0]=value_{$i}_admin"; + $optionsData []= "option[value][option_{$i}][1]={$expectedOptionLabelOnStoreView}"; + $optionsData []= "option[delete][option_{$i}="; + } + $attributeData['serialized_options'] = json_encode($optionsData); + $this->getRequest()->setPostValue($attributeData); + $this->dispatch('backend/catalog/product_attribute/save'); + $entityTypeId = $this->_objectManager->create( + \Magento\Eav\Model\Entity::class + )->setType( + \Magento\Catalog\Model\Product::ENTITY + )->getTypeId(); + + /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ + $attribute = $this->_objectManager->create( + \Magento\Catalog\Model\ResourceModel\Eav\Attribute::class + )->setEntityTypeId( + $entityTypeId + ); + try { + $attribute->loadByCode($entityTypeId, 'test_many_options'); + $options = $attribute->getOptions(); + // assert that all options are saved without truncation + $this->assertEquals( + $optionsCount + 1, + count($options), + 'Expected options count does not match (regarding first empty option for non-required attribute)' + ); + + foreach ($expectedOptionsLabels as $optionOrderNum => $label) { + $this->assertEquals( + $label, + $options[$optionOrderNum]->getLabel(), + "Label for option #{$optionOrderNum} does not match expected." + ); + } + } catch (LocalizedException $e) { + $this->fail('Test failed with exception on attribute model load: ' . $e); + } + } + /** * Return translation for a string literal belonging to backend area * diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CategoriesTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CategoriesTest.php index fd0f6e98840c9..22bb76ca9222d 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CategoriesTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CategoriesTest.php @@ -6,6 +6,7 @@ namespace Magento\Catalog\Ui\DataProvider\Product\Form\Modifier; +use Magento\Catalog\Model\Product; use Magento\TestFramework\Helper\CacheCleaner; /** @@ -29,6 +30,8 @@ protected function setUp() $store = $objectManager->create(\Magento\Store\Model\Store::class); $store->load('admin'); $registry->register('current_store', $store); + $product = $objectManager->create(Product::class); + $registry->register('current_product', $product); $this->object = $objectManager->create(Categories::class); } diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Client/ElasticsearchTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Client/ElasticsearchTest.php index 0731871b4bd40..61add5f7d0ea7 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Client/ElasticsearchTest.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Client/ElasticsearchTest.php @@ -100,7 +100,7 @@ private function search($text) */ public function testSearchConfigurableProductBySimpleProductName() { - $this->assertProductWithSkuFound('configurable', $this->search('Configurable OptionOption')); + $this->assertProductWithSkuFound('configurable', $this->search('Configurable Option')); } /** diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Indexer/IndexHandlerTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Indexer/IndexHandlerTest.php index 4cdff533af737..014aaf7679bc9 100755 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Indexer/IndexHandlerTest.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Indexer/IndexHandlerTest.php @@ -5,13 +5,18 @@ */ namespace Magento\Elasticsearch\Model\Indexer; -use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Action as ProductAction; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\CatalogInventory\Api\StockRegistryInterface; +use Magento\CatalogInventory\Api\StockItemRepositoryInterface; +use Magento\CatalogSearch\Model\Indexer\Fulltext as CatalogSearchFulltextIndexer; use Magento\TestFramework\Helper\Bootstrap; use Magento\Store\Model\StoreManagerInterface; use Magento\Elasticsearch\SearchAdapter\ConnectionManager; use Magento\Elasticsearch\Model\Client\Elasticsearch as ElasticsearchClient; use Magento\Elasticsearch\Model\Config; use Magento\Elasticsearch\SearchAdapter\SearchIndexNameResolver; +use Magento\Indexer\Model\Indexer; /** * Important: Please make sure that each integration test file works with unique elastic search index. In order to @@ -20,107 +25,79 @@ * * @magentoDbIsolation disabled * @magentoDataFixture Magento/Elasticsearch/_files/indexer.php + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class IndexHandlerTest extends \PHPUnit\Framework\TestCase { /** - * @var ConnectionManager + * @var ProductRepositoryInterface */ - protected $connectionManager; + private $productRepository; /** * @var ElasticsearchClient */ - protected $client; + private $client; /** * @var StoreManagerInterface */ - protected $storeManager; + private $storeManager; /** * @var int[] */ - protected $storeIds; + private $storeIds; /** - * @var Config + * @var string */ - protected $clientConfig; + private $entityType; /** - * @var SearchIndexNameResolver - */ - protected $searchIndexNameResolver; - - /** - * @var Product - */ - protected $productApple; - - /** - * @var Product - */ - protected $productBanana; - - /** - * @var Product - */ - protected $productOrange; - - /** - * @var Product + * @var Indexer */ - protected $productPapaya; + private $indexer; /** - * @var Product + * @var SearchIndexNameResolver */ - protected $productCherry; + private $searchIndexNameResolver; /** - * Setup method + * {@inheritdoc} */ protected function setUp() { - $this->connectionManager = Bootstrap::getObjectManager()->create( - \Magento\Elasticsearch\SearchAdapter\ConnectionManager::class - ); - - $this->client = $this->connectionManager->getConnection(); + $connectionManager = Bootstrap::getObjectManager()->create(ConnectionManager::class); + $this->client = $connectionManager->getConnection(); - $this->storeManager = Bootstrap::getObjectManager()->create( - \Magento\Store\Model\StoreManagerInterface::class - ); + $this->storeManager = Bootstrap::getObjectManager()->create(StoreManagerInterface::class); $this->storeIds = array_keys($this->storeManager->getStores()); - $this->clientConfig = Bootstrap::getObjectManager()->create( - \Magento\Elasticsearch\Model\Config::class - ); + $clientConfig = Bootstrap::getObjectManager()->create(Config::class); + $this->entityType = $clientConfig->getEntityType(); - $this->searchIndexNameResolver = Bootstrap::getObjectManager()->create( - \Magento\Elasticsearch\SearchAdapter\SearchIndexNameResolver::class - ); + $this->indexer = Bootstrap::getObjectManager()->create(Indexer::class); + $this->indexer->load(CatalogSearchFulltextIndexer::INDEXER_ID); + $this->indexer->reindexAll(); - $this->productApple = $this->getProductBySku('fulltext-1'); - $this->productBanana = $this->getProductBySku('fulltext-2'); - $this->productOrange = $this->getProductBySku('fulltext-3'); - $this->productPapaya = $this->getProductBySku('fulltext-4'); - $this->productCherry = $this->getProductBySku('fulltext-5'); + $this->searchIndexNameResolver = Bootstrap::getObjectManager()->create(SearchIndexNameResolver::class); + $this->productRepository = Bootstrap::getObjectManager()->create(ProductRepositoryInterface::class); } /** - * Test reindex process * @magentoConfigFixture default/catalog/search/engine elasticsearch * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix indexerhandlertest + * @return void */ - public function testReindexAll() + public function testReindexAll(): void { - $this->reindexAll(); + $productApple = $this->productRepository->get('fulltext-1'); foreach ($this->storeIds as $storeId) { $products = $this->searchByName('Apple', $storeId); $this->assertCount(1, $products); - $this->assertEquals($this->productApple->getId(), $products[0]['_id']); + $this->assertEquals($productApple->getId(), $products[0]['_id']); $products = $this->searchByName('Simple Product', $storeId); $this->assertCount(5, $products); @@ -128,19 +105,17 @@ public function testReindexAll() } /** + * @magentoAppIsolation enabled * @magentoConfigFixture default/catalog/search/engine elasticsearch * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix indexerhandlertest + * @return void */ - public function testReindexRowAfterEdit() + public function testReindexRowAfterEdit(): void { - // The test executes fine locally. On bamboo there is some issue with parallel test execution or other - // test interaction. It is being marked as skipped until more time is available to investigate and - // fix the issue. - $this->markTestSkipped('MAGETWO-53851 - Ticket to investiage this test failure on Bamboo and fix it.'); - - $this->productApple->setData('name', 'Simple Product Cucumber'); - $this->productApple->save(); - $this->reindexAll(); + $this->storeManager->setCurrentStore('admin'); + $productApple = $this->productRepository->get('fulltext-1'); + $productApple->setName('Simple Product Cucumber'); + $this->productRepository->save($productApple); foreach ($this->storeIds as $storeId) { $products = $this->searchByName('Apple', $storeId); @@ -148,7 +123,7 @@ public function testReindexRowAfterEdit() $products = $this->searchByName('Cucumber', $storeId); $this->assertCount(1, $products); - $this->assertEquals($this->productApple->getId(), $products[0]['_id']); + $this->assertEquals($productApple->getId(), $products[0]['_id']); $products = $this->searchByName('Simple Product', $storeId); $this->assertCount(5, $products); @@ -158,22 +133,21 @@ public function testReindexRowAfterEdit() /** * @magentoConfigFixture default/catalog/search/engine elasticsearch * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix indexerhandlertest + * @return void */ - public function testReindexRowAfterMassAction() + public function testReindexRowAfterMassAction(): void { - $this->reindexAll(); + $productApple = $this->productRepository->get('fulltext-1'); + $productBanana = $this->productRepository->get('fulltext-2'); $productIds = [ - $this->productApple->getId(), - $this->productBanana->getId(), + $productApple->getId(), + $productBanana->getId(), ]; $attrData = [ 'name' => 'Simple Product Common', ]; - - /** @var \Magento\Catalog\Model\Product\Action $action */ - $action = Bootstrap::getObjectManager()->get( - \Magento\Catalog\Model\Product\Action::class - ); + /** @var ProductAction $action */ + $action = Bootstrap::getObjectManager()->get(ProductAction::class); foreach ($this->storeIds as $storeId) { $action->updateAttributes($productIds, $attrData, $storeId); @@ -199,30 +173,67 @@ public function testReindexRowAfterMassAction() * @magentoConfigFixture default/catalog/search/engine elasticsearch * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix indexerhandlertest * @magentoAppArea adminhtml + * @return void */ - public function testReindexRowAfterDelete() + public function testReindexRowAfterDelete(): void { - $this->reindexAll(); - $this->productBanana->delete(); + $productBanana = $this->productRepository->get('fulltext-2'); + $this->productRepository->delete($productBanana); foreach ($this->storeIds as $storeId) { + $products = $this->searchByName('Banana', $storeId); + $this->assertEmpty($products); + $products = $this->searchByName('Simple Product', $storeId); $this->assertCount(4, $products); } } /** - * Search docs in Elasticsearch by name + * @magentoDbIsolation enabled + * @magentoAppArea adminhtml + * @magentoConfigFixture default/catalog/search/engine elasticsearch + * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix indexerhandlertest + * @magentoDataFixture Magento/Elasticsearch/_files/configurable_products.php + * @return void + */ + public function testReindexRowAfterUpdateStockStatus(): void + { + foreach ($this->storeIds as $storeId) { + $products = $this->searchByName('ProductOption1', $storeId); + $this->assertNotEmpty($products); + } + $product = $this->productRepository->get('simple_10'); + /** @var StockRegistryInterface $stockRegistry */ + $stockRegistry = Bootstrap::getObjectManager()->create(StockRegistryInterface::class); + $stockItem = $stockRegistry->getStockItem($product->getId()); + $stockItem->setIsInStock(false); + /** @var StockItemRepositoryInterface $stockRepository */ + $stockRepository = Bootstrap::getObjectManager()->create(StockItemRepositoryInterface::class); + $stockRepository->save($stockItem); + + foreach ($this->storeIds as $storeId) { + $products = $this->searchByName('ProductOption1', $storeId); + $this->assertEmpty($products); + + $products = $this->searchByName('Configurable', $storeId); + $this->assertNotEmpty($products); + } + } + + /** + * Search docs in Elasticsearch by name. * * @param string $text * @param int $storeId * @return array */ - protected function searchByName($text, $storeId) + private function searchByName(string $text, int $storeId): array { + $index = $this->searchIndexNameResolver->getIndexName($storeId, $this->indexer->getId()); $searchQuery = [ - 'index' => $this->searchIndexNameResolver->getIndexName($storeId, 'catalogsearch_fulltext'), - 'type' => $this->clientConfig->getEntityType(), + 'index' => $index, + 'type' => $this->entityType, 'body' => [ 'query' => [ 'bool' => [ @@ -240,35 +251,7 @@ protected function searchByName($text, $storeId) ]; $queryResult = $this->client->query($searchQuery); $products = isset($queryResult['hits']['hits']) ? $queryResult['hits']['hits'] : []; - return $products; - } - - /** - * Return product by SKU - * - * @param string $sku - * @return Product - */ - protected function getProductBySku($sku) - { - /** @var Product $product */ - $product = Bootstrap::getObjectManager()->get( - \Magento\Catalog\Model\Product::class - ); - return $product->loadByAttribute('sku', $sku); - } - /** - * Perform full reindex - * - * @return void - */ - private function reindexAll() - { - $indexer = Bootstrap::getObjectManager()->create( - \Magento\Indexer\Model\Indexer::class - ); - $indexer->load('catalogsearch_fulltext'); - $indexer->reindexAll(); + return $products; } } diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/configurable_attribute.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/configurable_attribute.php new file mode 100644 index 0000000000000..7ec53d9099d35 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/configurable_attribute.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Eav\Api\AttributeRepositoryInterface; + +$eavConfig = Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); +$attribute = $eavConfig->getAttribute('catalog_product', 'test_configurable'); + +$eavConfig->clear(); + +/** @var $installer \Magento\Catalog\Setup\CategorySetup */ +$installer = Bootstrap::getObjectManager()->create(\Magento\Catalog\Setup\CategorySetup::class); + +if (!$attribute->getId()) { + /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ + $attribute = Bootstrap::getObjectManager()->create( + \Magento\Catalog\Model\ResourceModel\Eav\Attribute::class + ); + + /** @var AttributeRepositoryInterface $attributeRepository */ + $attributeRepository = Bootstrap::getObjectManager()->create(AttributeRepositoryInterface::class); + + $attribute->setData( + [ + 'attribute_code' => 'test_configurable', + 'entity_type_id' => $installer->getEntityTypeId('catalog_product'), + 'is_global' => 1, + 'is_user_defined' => 1, + 'frontend_input' => 'select', + 'is_unique' => 0, + 'is_required' => 0, + 'is_searchable' => 0, + 'is_visible_in_advanced_search' => 0, + 'is_comparable' => 0, + 'is_filterable' => 0, + 'is_filterable_in_search' => 0, + 'is_used_for_promo_rules' => 0, + 'is_html_allowed_on_front' => 1, + 'is_visible_on_front' => 0, + 'used_in_product_listing' => 0, + 'used_for_sort_by' => 0, + 'frontend_label' => ['Test Configurable'], + 'backend_type' => 'int', + 'option' => [ + 'value' => ['option_0' => ['Option 1'], 'option_1' => ['Option 2']], + 'order' => ['option_0' => 1, 'option_1' => 2], + ], + ] + ); + + $attributeRepository->save($attribute); + + /* Assign attribute to attribute set */ + $installer->addAttributeToGroup('catalog_product', 'Default', 'General', $attribute->getId()); +} + +$eavConfig->clear(); diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/configurable_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/configurable_attribute_rollback.php new file mode 100644 index 0000000000000..7bdfbc6d7f9b3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/configurable_attribute_rollback.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +$eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); +$attribute = $eavConfig->getAttribute('catalog_product', 'test_configurable'); +if ($attribute instanceof \Magento\Eav\Model\Entity\Attribute\AbstractAttribute + && $attribute->getId() +) { + $attribute->delete(); +} +$eavConfig->clear(); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/configurable_products.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/configurable_products.php index c2dd3c2f879e1..f8872b02ba246 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/configurable_products.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/configurable_products.php @@ -17,7 +17,7 @@ require __DIR__ . '/select_attribute.php'; require __DIR__ . '/multiselect_attribute.php'; -require __DIR__ . '/../../ConfigurableProduct/_files/configurable_attribute.php'; +require __DIR__ . '/configurable_attribute.php'; $objectManager = Bootstrap::getObjectManager(); @@ -45,7 +45,7 @@ ->setId($productId) ->setAttributeSetId($attributeSetId) ->setWebsiteIds([1]) - ->setName('Configurable Option' . $option->getLabel()) + ->setName('Configurable Option Product' . str_replace(' ', '', $option->getLabel())) ->setSku('simple_' . $productId) ->setPrice($productId) ->setTestConfigurable($option->getValue()) diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/configurable_products_rollback.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/configurable_products_rollback.php index 0d062c9d3f4e4..e73d2ab1b5906 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/configurable_products_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/configurable_products_rollback.php @@ -25,7 +25,7 @@ } } -require __DIR__ . '/../../ConfigurableProduct/_files/configurable_attribute_rollback.php'; +require __DIR__ . '/configurable_attribute_rollback.php'; require __DIR__ . '/select_attribute_rollback.php'; require __DIR__ . '/multiselect_attribute_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/indexer.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/indexer.php index 8ee3a40915028..b5c86a63fa47f 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/indexer.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/indexer.php @@ -52,7 +52,7 @@ ->setStockData(['use_config_manage_stock' => 0]) ->save(); -/** @var $productFirst \Magento\Catalog\Model\Product */ +/** @var $productSecond \Magento\Catalog\Model\Product */ $productSecond = $objectManager->create(\Magento\Catalog\Model\Product::class); $productSecond->setTypeId('simple') ->setAttributeSetId(4) @@ -68,7 +68,7 @@ ->setStockData(['use_config_manage_stock' => 0]) ->save(); -/** @var $productFirst \Magento\Catalog\Model\Product */ +/** @var $productThird \Magento\Catalog\Model\Product */ $productThird = $objectManager->create(\Magento\Catalog\Model\Product::class); $productThird->setTypeId('simple') ->setAttributeSetId(4) @@ -84,7 +84,7 @@ ->setStockData(['use_config_manage_stock' => 0]) ->save(); -/** @var $productFirst \Magento\Catalog\Model\Product */ +/** @var $productFourth \Magento\Catalog\Model\Product */ $productFourth = $objectManager->create(\Magento\Catalog\Model\Product::class); $productFourth->setTypeId('simple') ->setAttributeSetId(4) @@ -100,7 +100,7 @@ ->setStockData(['use_config_manage_stock' => 0]) ->save(); -/** @var $productFirst \Magento\Catalog\Model\Product */ +/** @var $productFifth \Magento\Catalog\Model\Product */ $productFifth = $objectManager->create(\Magento\Catalog\Model\Product::class); $productFifth->setTypeId('simple') ->setAttributeSetId(4) diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/requests.xml b/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/requests.xml index c40ac9e8b9b1c..0aaaf9b85857f 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/requests.xml +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/requests.xml @@ -394,13 +394,18 @@ <queries> <query xsi:type="boolQuery" name="filter_out_of_stock_child" boost="1"> <queryReference clause="must" ref="test_configurable"/> + <queryReference clause="must" ref="visibility"/> </query> <query xsi:type="filteredQuery" name="test_configurable"> <filterReference clause="must" ref="test_configurable_filter"/> </query> + <query xsi:type="filteredQuery" name="visibility"> + <filterReference clause="must" ref="visibility_filter"/> + </query> </queries> <filters> <filter xsi:type="termFilter" name="test_configurable_filter" field="test_configurable" value="$test_configurable$"/> + <filter xsi:type="termFilter" name="visibility_filter" field="visibility" value="$visibility$"/> </filters> <aggregations/> <from>0</from> diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/Adapter/Mysql/AdapterTest.php b/dev/tests/integration/testsuite/Magento/Framework/Search/Adapter/Mysql/AdapterTest.php index 581630e9626fa..ecbce25cd3377 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Search/Adapter/Mysql/AdapterTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Search/Adapter/Mysql/AdapterTest.php @@ -63,6 +63,10 @@ protected function setUp() ); $this->adapter = $this->createAdapter(); + + $indexer = $this->objectManager->create(\Magento\Indexer\Model\Indexer::class); + $indexer->load('catalogsearch_fulltext'); + $indexer->reindexAll(); } /** @@ -531,9 +535,15 @@ public function testAdvancedSearchCompositeProductWithOutOfStockOption() ->create(Collection::class) ->setAttributeFilter($attribute->getId()); + $visibility = [ + \Magento\Catalog\Model\Product\Visibility::VISIBILITY_IN_SEARCH, + \Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH, + ]; + $firstOption = $selectOptions->getFirstItem(); $firstOptionId = $firstOption->getId(); $this->requestBuilder->bind('test_configurable', $firstOptionId); + $this->requestBuilder->bind('visibility', $visibility); $this->requestBuilder->setRequestName('filter_out_of_stock_child'); $queryResponse = $this->executeQuery(); @@ -542,6 +552,7 @@ public function testAdvancedSearchCompositeProductWithOutOfStockOption() $secondOption = $selectOptions->getLastItem(); $secondOptionId = $secondOption->getId(); $this->requestBuilder->bind('test_configurable', $secondOptionId); + $this->requestBuilder->bind('visibility', $visibility); $this->requestBuilder->setRequestName('filter_out_of_stock_child'); $queryResponse = $this->executeQuery(); diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/product_configurable.php b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/product_configurable.php index 2f30cba3d8ee6..8a7c5246f373a 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/product_configurable.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/product_configurable.php @@ -15,8 +15,6 @@ use Magento\Eav\Api\Data\AttributeOptionInterface; use Magento\TestFramework\Helper\Bootstrap; -Bootstrap::getInstance()->reinitialize(); - require __DIR__ . '/configurable_attribute.php'; /** @var ProductRepositoryInterface $productRepository */ diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/requests.xml b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/requests.xml index 70f9ac75b07f3..dcf3cd582507c 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/requests.xml +++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/requests.xml @@ -394,13 +394,18 @@ <queries> <query xsi:type="boolQuery" name="filter_out_of_stock_child" boost="1"> <queryReference clause="must" ref="test_configurable"/> + <queryReference clause="must" ref="visibility"/> </query> <query xsi:type="filteredQuery" name="test_configurable"> <filterReference clause="must" ref="test_configurable_filter"/> </query> + <query xsi:type="filteredQuery" name="visibility"> + <filterReference clause="must" ref="visibility_filter"/> + </query> </queries> <filters> <filter xsi:type="termFilter" name="test_configurable_filter" field="test_configurable" value="$test_configurable$"/> + <filter xsi:type="termFilter" name="visibility_filter" field="visibility" value="$visibility$"/> </filters> <aggregations/> <from>0</from> diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/search_weight_products.php b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/search_weight_products.php index b672fbe9f8cec..3902e78d1fb55 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/search_weight_products.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/search_weight_products.php @@ -7,18 +7,8 @@ */ use Magento\Catalog\Api\ProductRepositoryInterface; -use Magento\Catalog\Model\Product; -use Magento\Catalog\Model\Product\Attribute\Source\Status; -use Magento\Catalog\Model\Product\Type; -use Magento\Catalog\Model\Product\Visibility; -use Magento\Catalog\Setup\CategorySetup; -use Magento\ConfigurableProduct\Helper\Product\Options\Factory; -use Magento\ConfigurableProduct\Model\Product\Type\Configurable; -use Magento\Eav\Api\Data\AttributeOptionInterface; use Magento\TestFramework\Helper\Bootstrap; -Bootstrap::getInstance()->reinitialize(); - $objectManager = Bootstrap::getObjectManager(); /** @var ProductRepositoryInterface $productRepository */ diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ProductTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ProductTest.php index 02a4ba4c282a0..98826adeb2147 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ProductTest.php @@ -58,6 +58,28 @@ public function testValidateCategorySalesRuleIncludesChildren($categoryId, $expe $this->assertEquals($expectedResult, $rule->validate($quote)); } + /** + * @magentoDbIsolation disabled + * @magentoDataFixture Magento/Bundle/_files/order_item_with_bundle_and_options.php + * @magentoDataFixture Magento/SalesRule/_files/rules_sku_exclude.php + * + * @return void + */ + public function testValidateSalesRuleExcludesBundleChildren(): void + { + // Load the quote that contains a child of a bundle product + /** @var \Magento\Quote\Model\Quote $quote */ + $quote = $this->objectManager->create(\Magento\Quote\Model\Quote::class) + ->load('test_cart_with_bundle_and_options', 'reserved_order_id'); + + // Load the SalesRule looking for excluding products with selected sku + /** @var $rule \Magento\SalesRule\Model\Rule */ + $rule = $this->objectManager->get(\Magento\Framework\Registry::class) + ->registry('_fixture/Magento_SalesRule_Sku_Exclude'); + + $this->assertEquals(false, $rule->validate($quote)); + } + /** * @return array */ diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/rules_sku_exclude.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/rules_sku_exclude.php new file mode 100644 index 0000000000000..9d88fe48ae111 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/rules_sku_exclude.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +/** @var \Magento\Eav\Api\AttributeRepositoryInterface $repository */ +$repository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Eav\Api\AttributeRepositoryInterface::class); + +/** @var \Magento\Eav\Api\Data\AttributeInterface $skuAttribute */ +$skuAttribute = $repository->get( + 'catalog_product', + 'sku' +); +$data = $skuAttribute->getData(); +$data['is_used_for_promo_rules'] = 1; +$skuAttribute->setData($data); +$skuAttribute->save(); + +/** @var \Magento\SalesRule\Model\Rule $rule */ +$salesRule = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\SalesRule\Model\Rule::class); +$salesRule->setData( + [ + 'name' => '20% Off', + 'is_active' => 1, + 'customer_group_ids' => [\Magento\Customer\Model\GroupManagement::NOT_LOGGED_IN_ID], + 'coupon_type' => \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON, + 'simple_action' => 'by_percent', + 'discount_amount' => 20, + 'discount_step' => 0, + 'stop_rules_processing' => 1, + 'website_ids' => [ + \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + \Magento\Store\Model\StoreManagerInterface::class + )->getWebsite()->getId(), + ], + ] +); + +$salesRule->getConditions()->loadArray([ + 'type' => \Magento\SalesRule\Model\Rule\Condition\Combine::class, + 'attribute' => null, + 'operator' => null, + 'value' => '1', + 'is_value_processed' => null, + 'aggregator' => 'all', + 'conditions' => + [ + [ + 'type' => \Magento\SalesRule\Model\Rule\Condition\Product\Found::class, + 'attribute' => null, + 'operator' => null, + 'value' => '1', + 'is_value_processed' => null, + 'aggregator' => 'all', + 'conditions' => + [ + [ + 'type' => \Magento\SalesRule\Model\Rule\Condition\Product::class, + 'attribute' => 'sku', + 'operator' => '!=', + 'value' => 'product-bundle', + 'is_value_processed' => false, + ], + ], + ], + ], +]); + +$salesRule->save(); + +/** @var Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + +$registry->unregister('_fixture/Magento_SalesRule_Sku_Exclude'); +$registry->register('_fixture/Magento_SalesRule_Sku_Exclude', $salesRule); diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/rules_sku_exclude_rollback.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/rules_sku_exclude_rollback.php new file mode 100644 index 0000000000000..79ec3259fd86a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/rules_sku_exclude_rollback.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +/** @var \Magento\Eav\Api\AttributeRepositoryInterface $repository */ +$repository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Eav\Api\AttributeRepositoryInterface::class); + +/** @var \Magento\Eav\Api\Data\AttributeInterface $skuAttribute */ +$skuAttribute = $repository->get( + 'catalog_product', + 'sku' +); +$data = $skuAttribute->getData(); +$data['is_used_for_promo_rules'] = 0; +$skuAttribute->setData($data); +$skuAttribute->save(); + +/** @var Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + +/** @var Magento\SalesRule\Model\Rule $rule */ +$rule = $registry->registry('_fixture/Magento_SalesRule_Sku_Exclude'); + +$rule->delete(); diff --git a/dev/tests/integration/testsuite/Magento/Signifyd/Observer/PlaceOrderTest.php b/dev/tests/integration/testsuite/Magento/Signifyd/Observer/PlaceOrderTest.php index d4204314453e5..e547187be5ed7 100644 --- a/dev/tests/integration/testsuite/Magento/Signifyd/Observer/PlaceOrderTest.php +++ b/dev/tests/integration/testsuite/Magento/Signifyd/Observer/PlaceOrderTest.php @@ -12,10 +12,17 @@ use Magento\Sales\Api\Data\OrderInterface; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Signifyd\Api\CaseCreationServiceInterface; +use Magento\Store\Api\StoreRepositoryInterface; +use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\MockObject_MockObject as MockObject; use Psr\Log\LoggerInterface; +/** + * Test for Magento\Signifyd\Observer\PlaceOrderTest class. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class PlaceOrderTest extends \PHPUnit\Framework\TestCase { /** @@ -105,7 +112,46 @@ public function testExecute() $event = $this->objectManager->create( Event::class, [ - 'data' => ['order' => $order] + 'data' => ['order' => $order], + ] + ); + + /** @var Observer $observer */ + $observer = $this->objectManager->get(Observer::class); + $observer->setEvent($event); + + $this->placeOrder->execute($observer); + } + + /** + * Signifyd is enabled for default store. + * Checks a test case when order placed with website where signifyd is disabled. + * + * @return void + * @covers \Magento\Signifyd\Observer\PlaceOrder::execute + * @magentoDataFixture Magento/Signifyd/_files/order_with_customer_and_two_simple_products.php + * @magentoDataFixture Magento/Signifyd/_files/website_configuration.php + */ + public function testExecuteWithWebsiteConfiguration(): void + { + /** @var StoreRepositoryInterface $storeRepository */ + $storeRepository = $this->objectManager->get(StoreRepositoryInterface::class); + $store = $storeRepository->get('test_second_store'); + + /** @var StoreManagerInterface $storeManager */ + $storeManager = $this->objectManager->get(StoreManagerInterface::class); + $storeManager->setCurrentStore($store->getId()); + + $order = $this->getOrder('100000001'); + $order->setStoreId($store->getId()); + + $this->creationService->expects(self::never()) + ->method('createForOrder'); + + $event = $this->objectManager->create( + Event::class, + [ + 'data' => ['order' => $order], ] ); diff --git a/dev/tests/integration/testsuite/Magento/Signifyd/_files/order_with_customer_and_two_simple_products.php b/dev/tests/integration/testsuite/Magento/Signifyd/_files/order_with_customer_and_two_simple_products.php index 8991825c9381e..49a0a2d33e236 100644 --- a/dev/tests/integration/testsuite/Magento/Signifyd/_files/order_with_customer_and_two_simple_products.php +++ b/dev/tests/integration/testsuite/Magento/Signifyd/_files/order_with_customer_and_two_simple_products.php @@ -10,7 +10,7 @@ use Magento\Sales\Api\OrderRepositoryInterface; use Magento\TestFramework\Helper\Bootstrap; -require __DIR__ . '/../../../Magento/Catalog/_files/multiple_products.php'; +require __DIR__ . '/../../../Magento/Catalog/_files/product_simple.php'; require __DIR__ . '/../../../Magento/Customer/_files/customer.php'; require __DIR__ . '/store.php'; @@ -36,33 +36,28 @@ ->setCcExpMonth('01') ->setCcExpYear('21'); -/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ -$productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); - -$product1 = $productRepository->get('simple1'); /** @var Item $orderItem */ $orderItem1 = $objectManager->create(Item::class); -$orderItem1->setProductId($product1->getId()) - ->setSku($product1->getSku()) - ->setName($product1->getName()) +$orderItem1->setProductId($product->getId()) + ->setSku($product->getSku()) + ->setName($product->getName()) ->setQtyOrdered(1) - ->setBasePrice($product1->getPrice()) - ->setPrice($product1->getPrice()) - ->setRowTotal($product1->getPrice()) - ->setProductType($product1->getTypeId()); + ->setBasePrice($product->getPrice()) + ->setPrice($product->getPrice()) + ->setRowTotal($product->getPrice()) + ->setProductType($product->getTypeId()); -$product2 = $productRepository->get('simple2'); /** @var Item $orderItem */ $orderItem2 = $objectManager->create(Item::class); -$orderItem2->setProductId($product2->getId()) - ->setSku($product2->getSku()) - ->setName($product2->getName()) - ->setPrice($product2->getPrice()) +$orderItem2->setProductId($product->getId()) + ->setSku('simple2') + ->setName('Simple product') + ->setPrice(100) ->setQtyOrdered(2) - ->setBasePrice($product2->getPrice()) - ->setPrice($product2->getPrice()) - ->setRowTotal($product2->getPrice()) - ->setProductType($product2->getTypeId()); + ->setBasePrice($product->getPrice()) + ->setPrice($product->getPrice()) + ->setRowTotal($product->getPrice()) + ->setProductType($product->getTypeId()); $orderAmount = 100; $customerEmail = $billingAddress->getEmail(); diff --git a/dev/tests/integration/testsuite/Magento/Signifyd/_files/website_configuration.php b/dev/tests/integration/testsuite/Magento/Signifyd/_files/website_configuration.php new file mode 100644 index 0000000000000..e53b0431503e7 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Signifyd/_files/website_configuration.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Config\Model\Config; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Store\Model\ResourceModel\Store as StoreResourceModel; +use Magento\Store\Model\ResourceModel\Website as WebsiteResourceModel; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Store\Model\Website; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var $website Website */ +$website = $objectManager->create(Website::class); +$website->setData(['code' => 'test_website', 'name' => 'Test Website', 'default_group_id' => '1', 'is_default' => '0']); +$websiteResourceModel = $objectManager->create(WebsiteResourceModel::class); +$websiteResourceModel->save($website); + +$websiteId = $website->getId(); +$store = $objectManager->create(Store::class); +$groupId = Bootstrap::getObjectManager()->get(StoreManagerInterface::class) + ->getWebsite() + ->getDefaultGroupId(); +$store->setCode('test_second_store') + ->setWebsiteId($websiteId) + ->setGroupId($groupId) + ->setName('Test Second Store') + ->setSortOrder(10) + ->setIsActive(1); +$storeResourceModel = $objectManager->create(StoreResourceModel::class); +$storeResourceModel->save($store); + +/* Refresh stores memory cache */ +$objectManager->get(StoreManagerInterface::class)->reinitStores(); + +$processConfigData = function (Config $config, array $data) { + foreach ($data as $key => $value) { + $config->setDataByPath($key, $value); + $config->save(); + } +}; + +// save signifyd configuration for the default scope +$configData = [ + 'fraud_protection/signifyd/active' => '1', +]; +/** @var Config $defConfig */ +$defConfig = $objectManager->create(Config::class); +$defConfig->setScope(ScopeConfigInterface::SCOPE_TYPE_DEFAULT); +$processConfigData($defConfig, $configData); + +// save signifyd website config data +$websiteConfigData = [ + 'fraud_protection/signifyd/active' => '0', +]; +/** @var Config $websiteConfig */ +$websiteConfig = $objectManager->create(Config::class); +$websiteConfig->setScope(ScopeInterface::SCOPE_WEBSITES); +$websiteConfig->setWebsite($websiteId); +$processConfigData($websiteConfig, $websiteConfigData); diff --git a/dev/tests/integration/testsuite/Magento/Signifyd/_files/website_configuration_rollback.php b/dev/tests/integration/testsuite/Magento/Signifyd/_files/website_configuration_rollback.php new file mode 100644 index 0000000000000..9b731813fea3b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Signifyd/_files/website_configuration_rollback.php @@ -0,0 +1,44 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\Config\Storage\WriterInterface; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\Store; +use Magento\Store\Model\Website; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); + +$deleteConfigData = function (WriterInterface $writer, $scope, $scopeId) { + $configData = [ + 'fraud_protection/signifyd/active', + ]; + foreach ($configData as $path) { + $writer->delete($path, $scope, $scopeId); + } +}; + +/** @var WriterInterface $configWriter */ +$configWriter = $objectManager->get(WriterInterface::class); +$deleteConfigData($configWriter, ScopeConfigInterface::SCOPE_TYPE_DEFAULT, null); + +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +$website = $websiteRepository->get('test_website'); +$deleteConfigData($configWriter, ScopeInterface::SCOPE_WEBSITES, $website->getId()); + +$website = $objectManager->create(Website::class); +/** @var $website Website */ +if ($website->load('test_website', 'code')->getId()) { + $website->delete(); +} +$store = $objectManager->create(Store::class); +if ($store->load('test_second_store', 'code')->getId()) { + $store->delete(); +} diff --git a/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/AttributeTest.php b/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/AttributeTest.php new file mode 100644 index 0000000000000..969d9530ae542 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/AttributeTest.php @@ -0,0 +1,213 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Swatches\Controller\Adminhtml\Product; + +use Magento\Framework\Exception\LocalizedException; + +/** + * Test for product attribute save controller. + * + * @magentoAppArea adminhtml + * @magentoDbIsolation enabled + */ +class AttributeTest extends \Magento\TestFramework\TestCase\AbstractBackendController +{ + /** + * Generate random hex color. + * + * @return string + */ + private function getRandomColor() : string + { + return '#' . str_pad(dechex(mt_rand(0, 0xFFFFFF)), 6, '0', STR_PAD_LEFT); + } + + /** + * Get visual swatches data set. + * + * @param int $optionsCount + * @return array + */ + private function getSwatchVisualDataSet(int $optionsCount) : array + { + $optionsData = []; + $expectedOptionsLabels = []; + for ($i = 0; $i < $optionsCount; $i++) { + $order = $i + 1; + $expectedOptionLabelOnStoreView = "value_{$i}_store_1"; + $expectedOptionsLabels[$i+1] = $expectedOptionLabelOnStoreView; + $optionsData []= "optionvisual[order][option_{$i}]={$order}"; + $optionsData []= "defaultvisual[]=option_{$i}"; + $optionsData []= "swatchvisual[value][option_{$i}]={$this->getRandomColor()}"; + $optionsData []= "optionvisual[value][option_{$i}][0]=value_{$i}_admin"; + $optionsData []= "optionvisual[value][option_{$i}][1]={$expectedOptionLabelOnStoreView}"; + $optionsData []= "optionvisual[delete][option_{$i}]="; + } + $optionsData []= "visual_swatch_validation="; + $optionsData []= "visual_swatch_validation_unique="; + return [ + 'attribute_data' => array_merge_recursive( + [ + 'serialized_swatch_values' => json_encode($optionsData), + ], + $this->getAttributePreset(), + [ + 'frontend_input' => 'swatch_visual' + ] + ), + 'expected_options_count' => $optionsCount + 1, + 'expected_store_labels' => $expectedOptionsLabels + ]; + } + + /** + * Get text swatches data set. + * + * @param int $optionsCount + * @return array + */ + private function getSwatchTextDataSet(int $optionsCount) : array + { + $optionsData = []; + $expectedOptionsLabels = []; + for ($i = 0; $i < $optionsCount; $i++) { + $order = $i + 1; + $expectedOptionLabelOnStoreView = "value_{$i}_store_1"; + $expectedOptionsLabels[$i+1] = $expectedOptionLabelOnStoreView; + $optionsData []= "optiontext[order][option_{$i}]={$order}"; + $optionsData []= "defaulttext[]=option_{$i}"; + $optionsData []= "swatchtext[value][option_{$i}]=x{$i}"; + $optionsData []= "optiontext[value][option_{$i}][0]=value_{$i}_admin"; + $optionsData []= "optiontext[value][option_{$i}][1]={$expectedOptionLabelOnStoreView}"; + $optionsData []= "optiontext[delete][option_{$i}]="; + } + $optionsData []= "text_swatch_validation="; + $optionsData []= "text_swatch_validation_unique="; + return [ + 'attribute_data' => array_merge_recursive( + [ + 'serialized_swatch_values' => json_encode($optionsData), + ], + $this->getAttributePreset(), + [ + 'frontend_input' => 'swatch_text' + ] + ), + 'expected_options_count' => $optionsCount + 1, + 'expected_store_labels' => $expectedOptionsLabels + ]; + } + + /** + * Get data preset for new attribute. + * + * @return array + */ + private function getAttributePreset() : array + { + return [ + 'serialized_options' => '[]', + 'form_key' => 'XxtpPYjm2YPYUlAt', + 'frontend_label' => [ + 0 => 'asdasd', + 1 => '', + 2 => '', + ], + 'is_required' => '0', + 'update_product_preview_image' => '0', + 'use_product_image_for_swatch' => '0', + 'is_global' => '0', + 'default_value_text' => '512', + 'default_value_yesno' => '1', + 'default_value_date' => '1/1/70', + 'default_value_textarea' => '512', + 'is_unique' => '0', + 'is_used_in_grid' => '1', + 'is_visible_in_grid' => '1', + 'is_filterable_in_grid' => '1', + 'is_searchable' => '0', + 'is_comparable' => '0', + 'is_filterable' => '0', + 'is_filterable_in_search' => '0', + 'position' => '0', + 'is_used_for_promo_rules' => '0', + 'is_html_allowed_on_front' => '1', + 'is_visible_on_front' => '0', + 'used_in_product_listing' => '0', + 'used_for_sort_by' => '0', + 'attribute_code' => 'test_many_swatches', + ]; + } + + /** + * Data provider for large swatches amount test. + * + * @return array + */ + public function getLargeSwatchesAmountAttributeData() : array + { + $maxInputVars = ini_get('max_input_vars'); + // Each option is at least 7 variables array for a visual swatch. + // Set options count to exceed max_input_vars by 20 options (140 variables). + $swatchVisualOptionsCount = (int)floor($maxInputVars / 7) + 20; + $swatchTextOptionsCount = (int)floor($maxInputVars / 4) + 80; + return [ + 'visual swatches' => $this->getSwatchVisualDataSet($swatchVisualOptionsCount), + 'text swatches' => $this->getSwatchTextDataSet($swatchTextOptionsCount) + ]; + } + + /** + * Test attribute saving with large amount of options exceeding maximum allowed by max_input_vars limit. + * @dataProvider getLargeSwatchesAmountAttributeData() + * @param array $attributeData + * @param int $expectedOptionsCount + * @param array $expectedLabels + * @return void + */ + public function testLargeOptionsDataSet( + array $attributeData, + int $expectedOptionsCount, + array $expectedLabels + ) : void { + $this->getRequest()->setPostValue($attributeData); + $this->dispatch('backend/catalog/product_attribute/save'); + $entityTypeId = $this->_objectManager->create( + \Magento\Eav\Model\Entity::class + )->setType( + \Magento\Catalog\Model\Product::ENTITY + )->getTypeId(); + + /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ + $attribute = $this->_objectManager->create( + \Magento\Catalog\Model\ResourceModel\Eav\Attribute::class + )->setEntityTypeId( + $entityTypeId + ); + try { + $attribute->loadByCode($entityTypeId, 'test_many_swatches'); + $options = $attribute->getOptions(); + // assert that all options are saved without truncation + $this->assertEquals( + $expectedOptionsCount, + count($options), + 'Expected options count does not match (regarding first empty option for non-required attribute)' + ); + + foreach ($expectedLabels as $optionOrderNum => $label) { + $this->assertEquals( + $label, + $options[$optionOrderNum]->getLabel(), + "Label for option #{$optionOrderNum} does not match expected." + ); + } + } catch (LocalizedException $e) { + $this->fail('Test failed with exception on attribute model load: ' . $e); + } + } +} diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Rule/view/adminhtml/web/conditions-data-normalizer.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Rule/view/adminhtml/web/conditions-data-normalizer.test.js index 21c04d098ae6c..ec820e537789a 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Rule/view/adminhtml/web/conditions-data-normalizer.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Rule/view/adminhtml/web/conditions-data-normalizer.test.js @@ -65,5 +65,17 @@ define([ expect(normal.foo['1'].value).toEqual(123); expect(normal.foo['1--1']).toEqual(321); }); + + it('Check keys containing a dot are normalized', function () { + var normal = normalizer.normalize({ + 'foo[1][name.foo]': 'bar', + 'foo[1][value.foo]': 123, + 'foo[1--1]': 321 + }); + + expect(normal.foo['1']['name.foo']).toEqual('bar'); + expect(normal.foo['1']['value.foo']).toEqual(123); + expect(normal.foo['1--1']).toEqual(321); + }); }); }); diff --git a/dev/tests/static/framework/Magento/TestFramework/Utility/ChangedFiles.php b/dev/tests/static/framework/Magento/TestFramework/Utility/ChangedFiles.php index 6470bc0c22086..3d7f5f2604d7a 100644 --- a/dev/tests/static/framework/Magento/TestFramework/Utility/ChangedFiles.php +++ b/dev/tests/static/framework/Magento/TestFramework/Utility/ChangedFiles.php @@ -32,7 +32,7 @@ public static function getPhpFiles($changedFilesList, $fileTypes = 0) $fileUtilities = Files::init(); if (isset($_ENV['INCREMENTAL_BUILD'])) { $phpFiles = []; - foreach (glob($changedFilesList) as $listFile) { + foreach (glob($changedFilesList, GLOB_NOSORT) as $listFile) { $phpFiles = array_merge($phpFiles, file($listFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)); } array_walk( diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/HhvmCompatibilityTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/HhvmCompatibilityTest.php index e33b771b3c645..c552e0daa97df 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/HhvmCompatibilityTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/HhvmCompatibilityTest.php @@ -47,6 +47,24 @@ class HhvmCompatibilityTest extends \PHPUnit\Framework\TestCase 'serialize_precision', ]; + /** + * Whitelist of variables allowed in files. + * + * @var array + */ + private $whitelistVarsInFiles = [ + 'max_input_vars' => [ + 'integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/AttributeTest.php', + 'integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php', + ] + ]; + + /** + * Test allowed directives. + * + * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ public function testAllowedIniGetSetDirectives() { $deniedDirectives = []; @@ -55,7 +73,19 @@ public function testAllowedIniGetSetDirectives() if ($fileDirectives) { $fileDeniedDirectives = array_diff($fileDirectives, $this->allowedDirectives); if ($fileDeniedDirectives) { - $deniedDirectives[$file] = array_unique($fileDeniedDirectives); + $deniedDirectivesInFile = array_unique($fileDeniedDirectives); + foreach ($deniedDirectivesInFile as $key => $deniedDirective) { + if (isset($this->whitelistVarsInFiles[$deniedDirective])) { + foreach ($this->whitelistVarsInFiles[$deniedDirective] as $whitelistFile) { + if (strpos($file, $whitelistFile) !== false) { + unset($deniedDirectivesInFile[$key]); + } + } + } + } + if ($deniedDirectivesInFile) { + $deniedDirectives[$file] = $deniedDirectivesInFile; + } } } } diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/Xml/SchemaTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/Xml/SchemaTest.php index 8048925a4e542..5877ee5cbcc48 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/Xml/SchemaTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/Xml/SchemaTest.php @@ -106,7 +106,9 @@ private function _filterSpecialCases(&$files) '#etc/countries.xml$#', '#conf/schema.xml$#', '#layout/swagger_index_index.xml$#', - '#Doc/etc/doc/vars.xml$#' + '#Doc/etc/doc/vars.xml$#', + '#phpunit.xml$#', + '#etc/db_schema.xml$#' ]; foreach ($list as $pattern) { foreach ($files as $key => $value) { diff --git a/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/FilterProcessor.php b/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/FilterProcessor.php index b663a3a2f733c..c9f10c183b50c 100644 --- a/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/FilterProcessor.php +++ b/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/FilterProcessor.php @@ -72,6 +72,15 @@ private function addFilterGroupToCollection( if (!$isApplied) { $condition = $filter->getConditionType() ? $filter->getConditionType() : 'eq'; $fields[] = $this->getFieldMapping($filter->getField()); + + if ($condition === 'fulltext') { + // NOTE: This is not a fulltext search, but the best way to search something when + // a SearchCriteria with "fulltext" condition is provided over a MySQL table + // (see https://github.com/magento-engcom/msi/issues/1221) + $condition = 'like'; + $filter->setValue('%' . $filter->getValue() . '%'); + } + $conditions[] = [$condition => $filter->getValue()]; } } diff --git a/lib/internal/Magento/Framework/App/Utility/Files.php b/lib/internal/Magento/Framework/App/Utility/Files.php index 7e8d79eead7e1..b0dfc50e9f972 100644 --- a/lib/internal/Magento/Framework/App/Utility/Files.php +++ b/lib/internal/Magento/Framework/App/Utility/Files.php @@ -392,7 +392,7 @@ public function getMainConfigFiles($asDataSet = true) $configXmlPaths = array_merge($globPaths, $configXmlPaths); $files = []; foreach ($configXmlPaths as $xmlPath) { - $files = array_merge($files, glob($xmlPath)); + $files = array_merge($files, glob($xmlPath, GLOB_NOSORT)); } self::$_cache[$cacheKey] = $files; } diff --git a/lib/internal/Magento/Framework/Archive/Tar.php b/lib/internal/Magento/Framework/Archive/Tar.php index ccf9838d6649f..e2a070503f61f 100644 --- a/lib/internal/Magento/Framework/Archive/Tar.php +++ b/lib/internal/Magento/Framework/Archive/Tar.php @@ -252,7 +252,7 @@ protected function _createTar($skipRoot = false, $finalize = false) $file = $this->_getCurrentFile(); if (is_dir($file)) { - $dirFiles = scandir($file); + $dirFiles = scandir($file, SCANDIR_SORT_NONE); if (false === $dirFiles) { throw new \Magento\Framework\Exception\LocalizedException( diff --git a/lib/internal/Magento/Framework/Backup/Test/Unit/_files/app_dirs_rollback.php b/lib/internal/Magento/Framework/Backup/Test/Unit/_files/app_dirs_rollback.php index 7a9814bcd6932..5d4f18037d023 100644 --- a/lib/internal/Magento/Framework/Backup/Test/Unit/_files/app_dirs_rollback.php +++ b/lib/internal/Magento/Framework/Backup/Test/Unit/_files/app_dirs_rollback.php @@ -15,7 +15,7 @@ } } -$files = glob(TESTS_TEMP_DIR . '/Magento/Backup/data/*'); +$files = glob(TESTS_TEMP_DIR . '/Magento/Backup/data/*', GLOB_NOSORT); foreach ($files as $file) { unlink($file); } diff --git a/lib/internal/Magento/Framework/Data/Collection/Filesystem.php b/lib/internal/Magento/Framework/Data/Collection/Filesystem.php index 67d2a020864dd..330ff4e975e8a 100644 --- a/lib/internal/Magento/Framework/Data/Collection/Filesystem.php +++ b/lib/internal/Magento/Framework/Data/Collection/Filesystem.php @@ -243,7 +243,7 @@ protected function _collectRecursive($dir) $dir = [$dir]; } foreach ($dir as $folder) { - if ($nodes = glob($folder . '/*')) { + if ($nodes = glob($folder . '/*', GLOB_NOSORT)) { foreach ($nodes as $node) { $collectedResult[] = $node; } diff --git a/lib/internal/Magento/Framework/Filesystem/Io/File.php b/lib/internal/Magento/Framework/Filesystem/Io/File.php index 7849fdf31031f..c1cfebc7a0ac1 100644 --- a/lib/internal/Magento/Framework/Filesystem/Io/File.php +++ b/lib/internal/Magento/Framework/Filesystem/Io/File.php @@ -366,7 +366,7 @@ protected static function _recursiveCallback($dir, array $fileCallback, array $d $dirCallback = $fileCallback; } if (is_dir($dir)) { - foreach (scandir($dir) as $item) { + foreach (scandir($dir, SCANDIR_SORT_NONE) as $item) { if (!strcmp($item, '.') || !strcmp($item, '..')) { continue; } diff --git a/lib/internal/Magento/Framework/Validator/Test/Unit/ConfigTest.php b/lib/internal/Magento/Framework/Validator/Test/Unit/ConfigTest.php index 213a71c0087ea..6717095376625 100644 --- a/lib/internal/Magento/Framework/Validator/Test/Unit/ConfigTest.php +++ b/lib/internal/Magento/Framework/Validator/Test/Unit/ConfigTest.php @@ -51,7 +51,7 @@ public function testConstructException() protected function _initConfig(array $files = null) { if (null === $files) { - $files = glob(__DIR__ . '/_files/validation/positive/*/validation.xml'); + $files = glob(__DIR__ . '/_files/validation/positive/*/validation.xml', GLOB_NOSORT); } $configFiles = []; foreach ($files as $path) { diff --git a/lib/web/css/source/lib/_buttons.less b/lib/web/css/source/lib/_buttons.less index 679c4b11fbaac..a92093b742902 100644 --- a/lib/web/css/source/lib/_buttons.less +++ b/lib/web/css/source/lib/_buttons.less @@ -276,6 +276,9 @@ // --------------------------------------------- .lib-button-primary( + @_button-font-family: @button-primary__font-family, + @_button-font-size: @button-primary__font-size, + @_button-font-weight: @button-primary__font-weight, @_button-line-height: @button-primary__line-height, @_button-width: @button-primary__width, @_button-margin: @button-primary__margin, @@ -299,6 +302,9 @@ @_button-gradient-direction: @button-primary__gradient-direction ) { .lib-button( + @_button-font-family: @_button-font-family, + @_button-font-size: @_button-font-size, + @_button-font-weight: @_button-font-weight, @_button-line-height: @_button-line-height, @_button-width: @_button-width, @_button-margin: @_button-margin, diff --git a/lib/web/css/source/lib/variables/_buttons.less b/lib/web/css/source/lib/variables/_buttons.less index 82f618faa1d0f..8c31c16143859 100644 --- a/lib/web/css/source/lib/variables/_buttons.less +++ b/lib/web/css/source/lib/variables/_buttons.less @@ -47,6 +47,9 @@ @button__active__gradient-color-end: false; // Primary button +@button-primary__font-family: @button__font-family; +@button-primary__font-size: @button__font-size; +@button-primary__font-weight: @button__font-weight; @button-primary__line-height: false; @button-primary__width: false; @button-primary__margin: false; diff --git a/lib/web/mage/adminhtml/form.js b/lib/web/mage/adminhtml/form.js index e136e8c0f7354..487c71484e4c5 100644 --- a/lib/web/mage/adminhtml/form.js +++ b/lib/web/mage/adminhtml/form.js @@ -389,7 +389,7 @@ define([ var idTo, idFrom, values, fromId, radioFrom; if (config) { - this._config = config; + this._config = jQuery.extend(this._config, config); } for (idTo in elementsMap) { //eslint-disable-line guard-for-in diff --git a/lib/web/mage/apply/scripts.js b/lib/web/mage/apply/scripts.js index f35e9a2140e67..bf211c38adba5 100644 --- a/lib/web/mage/apply/scripts.js +++ b/lib/web/mage/apply/scripts.js @@ -14,7 +14,7 @@ define([ virtuals = []; /** - * Adds components to the virtula list. + * Adds components to the virtual list. * * @param {Object} components */ diff --git a/setup/src/Magento/Setup/Fixtures/FixtureModel.php b/setup/src/Magento/Setup/Fixtures/FixtureModel.php index 104c9cb343216..99237e48748fb 100644 --- a/setup/src/Magento/Setup/Fixtures/FixtureModel.php +++ b/setup/src/Magento/Setup/Fixtures/FixtureModel.php @@ -105,7 +105,7 @@ public function reindex(OutputInterface $output) */ public function loadFixtures() { - $files = glob(__DIR__ . DIRECTORY_SEPARATOR . self::FIXTURE_PATTERN); + $files = glob(__DIR__ . DIRECTORY_SEPARATOR . self::FIXTURE_PATTERN, GLOB_NOSORT); foreach ($files as $file) { $file = basename($file, '.php');