diff --git a/.gitignore b/.gitignore index 68d38d9ca7817..8ec1104f25535 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,8 @@ atlassian* /pub/media/import/* !/pub/media/import/.htaccess /pub/media/logo/* +/pub/media/custom_options/* +!/pub/media/custom_options/.htaccess /pub/media/theme/* /pub/media/theme_customization/* !/pub/media/theme_customization/.htaccess diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php index 9890a10a4ceb0..891b2a3ada724 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php @@ -282,25 +282,23 @@ public function getGridIdsJson() if (!$this->getUseSelectAll()) { return ''; } - /** @var \Magento\Framework\Data\Collection $allIdsCollection */ - $allIdsCollection = clone $this->getParentBlock()->getCollection(); - if ($this->getMassactionIdField()) { - $massActionIdField = $this->getMassactionIdField(); + /** @var \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection $collection */ + $collection = clone $this->getParentBlock()->getCollection(); + + if ($collection instanceof AbstractDb) { + $idsSelect = clone $collection->getSelect(); + $idsSelect->reset(\Magento\Framework\DB\Select::ORDER); + $idsSelect->reset(\Magento\Framework\DB\Select::LIMIT_COUNT); + $idsSelect->reset(\Magento\Framework\DB\Select::LIMIT_OFFSET); + $idsSelect->reset(\Magento\Framework\DB\Select::COLUMNS); + $idsSelect->columns($this->getMassactionIdField(), 'main_table'); + $idList = $collection->getConnection()->fetchCol($idsSelect); } else { - $massActionIdField = $this->getParentBlock()->getMassactionIdField(); + $idList = $collection->setPageSize(0)->getColumnValues($this->getMassactionIdField()); } - if ($allIdsCollection instanceof AbstractDb) { - $allIdsCollection->getSelect()->limit(); - $allIdsCollection->clear(); - } - - $gridIds = $allIdsCollection->setPageSize(0)->getColumnValues($massActionIdField); - if (!empty($gridIds)) { - return join(",", $gridIds); - } - return ''; + return implode(',', $idList); } /** diff --git a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/MassactionTest.php b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/MassactionTest.php index e8143b5f6b43a..e62b73f39241d 100644 --- a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/MassactionTest.php +++ b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/MassactionTest.php @@ -269,62 +269,6 @@ public function testGetGridIdsJsonWithoutUseSelectAll() $this->assertEmpty($this->_block->getGridIdsJson()); } - /** - * @param array $items - * @param string $result - * - * @dataProvider dataProviderGetGridIdsJsonWithUseSelectAll - */ - public function testGetGridIdsJsonWithUseSelectAll(array $items, $result) - { - $this->_block->setUseSelectAll(true); - - if ($this->_block->getMassactionIdField()) { - $massActionIdField = $this->_block->getMassactionIdField(); - } else { - $massActionIdField = $this->_block->getParentBlock()->getMassactionIdField(); - } - - $collectionMock = $this->getMockBuilder(\Magento\Framework\Data\Collection::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->_gridMock->expects($this->once()) - ->method('getCollection') - ->willReturn($collectionMock); - $collectionMock->expects($this->once()) - ->method('setPageSize') - ->with(0) - ->willReturnSelf(); - $collectionMock->expects($this->once()) - ->method('getColumnValues') - ->with($massActionIdField) - ->willReturn($items); - - $this->assertEquals($result, $this->_block->getGridIdsJson()); - } - - /** - * @return array - */ - public function dataProviderGetGridIdsJsonWithUseSelectAll() - { - return [ - [ - [], - '', - ], - [ - [1], - '1', - ], - [ - [1, 2, 3], - '1,2,3', - ], - ]; - } - /** * @param string $itemId * @param array|\Magento\Framework\DataObject $item diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php index e84d9ff12906e..825d0ee032d6c 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php @@ -159,6 +159,7 @@ public function execute() if ($redirectBack === 'duplicate') { $product->unsetData('quantity_and_stock_status'); $newProduct = $this->productCopier->copy($product); + $this->checkUniqueAttributes($product); $this->messageManager->addSuccessMessage(__('You duplicated the product.')); } } catch (\Magento\Framework\Exception\LocalizedException $e) { @@ -343,4 +344,25 @@ private function persistMediaData(ProductInterface $product, array $data) return $data; } + + /** + * Check unique attributes and add error to message manager + * + * @param \Magento\Catalog\Model\Product $product + */ + private function checkUniqueAttributes(\Magento\Catalog\Model\Product $product) + { + $uniqueLabels = []; + foreach ($product->getAttributes() as $attribute) { + if ($attribute->getIsUnique() && $attribute->getIsUserDefined() + && $product->getData($attribute->getAttributeCode()) !== null + ) { + $uniqueLabels[] = $attribute->getDefaultFrontendLabel(); + } + } + if ($uniqueLabels) { + $uniqueLabels = implode('", "', $uniqueLabels); + $this->messageManager->addErrorMessage(__('The value of attribute(s) "%1" must be unique', $uniqueLabels)); + } + } } diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml index 09b49011938e8..a3f543e9cf32a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml @@ -16,6 +16,9 @@ + + + diff --git a/app/code/Magento/Catalog/etc/adminhtml/system.xml b/app/code/Magento/Catalog/etc/adminhtml/system.xml index 7a05601fcd666..1d563244f1432 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/system.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/system.xml @@ -59,7 +59,7 @@ Comma-separated. - validate-per-page-value-list + validate-per-page-value-list required-entry @@ -69,7 +69,7 @@ Comma-separated. - validate-per-page-value-list + validate-per-page-value-list required-entry diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Products/AdaptUrlRewritesToVisibilityAttribute.php b/app/code/Magento/CatalogUrlRewrite/Model/Products/AdaptUrlRewritesToVisibilityAttribute.php new file mode 100644 index 0000000000000..f5851cf1e11b1 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Model/Products/AdaptUrlRewritesToVisibilityAttribute.php @@ -0,0 +1,127 @@ +productCollectionFactory = $collectionFactory; + $this->urlRewriteGenerator = $urlRewriteGenerator; + $this->urlPersist = $urlPersist; + $this->urlPathGenerator = $urlPathGenerator; + } + + /** + * Process Url Rewrites according to the products visibility attribute + * + * @param array $productIds + * @param int $visibility + * @throws UrlAlreadyExistsException + */ + public function execute(array $productIds, int $visibility): void + { + $products = $this->getProductsByIds($productIds); + + /** @var Product $product */ + foreach ($products as $product) { + if ($visibility == Visibility::VISIBILITY_NOT_VISIBLE) { + $this->urlPersist->deleteByData( + [ + UrlRewrite::ENTITY_ID => $product->getId(), + UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE, + ] + ); + } elseif ($visibility !== Visibility::VISIBILITY_NOT_VISIBLE) { + $product->setVisibility($visibility); + $productUrlPath = $this->urlPathGenerator->getUrlPath($product); + $productUrlRewrite = $this->urlRewriteGenerator->generate($product); + $product->unsUrlPath(); + $product->setUrlPath($productUrlPath); + + try { + $this->urlPersist->replace($productUrlRewrite); + } catch (UrlAlreadyExistsException $e) { + throw new UrlAlreadyExistsException( + __( + 'Can not change the visibility of the product with SKU equals "%1". ' + . 'URL key "%2" for specified store already exists.', + $product->getSku(), + $product->getUrlKey() + ), + $e, + $e->getCode(), + $e->getUrls() + ); + } + } + } + } + + /** + * Get Product Models by Id's + * + * @param array $productIds + * @return array + */ + private function getProductsByIds(array $productIds): array + { + $productCollection = $this->productCollectionFactory->create(); + $productCollection->addAttributeToSelect(ProductInterface::VISIBILITY); + $productCollection->addAttributeToSelect('url_key'); + $productCollection->addFieldToFilter( + 'entity_id', + ['in' => array_unique($productIds)] + ); + + return $productCollection->getItems(); + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/ProcessUrlRewriteOnChangeProductVisibilityObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/ProcessUrlRewriteOnChangeProductVisibilityObserver.php new file mode 100644 index 0000000000000..2337bb3646bd7 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Observer/ProcessUrlRewriteOnChangeProductVisibilityObserver.php @@ -0,0 +1,54 @@ +adaptUrlRewritesToVisibility = $adaptUrlRewritesToVisibility; + } + + /** + * Generate urls for UrlRewrites and save it in storage + * + * @param Observer $observer + * @return void + * @throws UrlAlreadyExistsException + */ + public function execute(Observer $observer) + { + $event = $observer->getEvent(); + $attrData = $event->getAttributesData(); + $productIds = $event->getProductIds(); + $visibility = $attrData[ProductInterface::VISIBILITY] ?? 0; + + if (!$visibility || !$productIds) { + return; + } + + $this->adaptUrlRewritesToVisibility->execute($productIds, (int)$visibility); + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/etc/events.xml b/app/code/Magento/CatalogUrlRewrite/etc/events.xml index cc558fe81f16d..728442acf7a44 100644 --- a/app/code/Magento/CatalogUrlRewrite/etc/events.xml +++ b/app/code/Magento/CatalogUrlRewrite/etc/events.xml @@ -27,6 +27,9 @@ + + + diff --git a/app/code/Magento/CheckoutAgreements/view/frontend/layout/multishipping_checkout_overview.xml b/app/code/Magento/CheckoutAgreements/view/frontend/layout/multishipping_checkout_overview.xml index 3f742de0177da..122160f1a10cd 100644 --- a/app/code/Magento/CheckoutAgreements/view/frontend/layout/multishipping_checkout_overview.xml +++ b/app/code/Magento/CheckoutAgreements/view/frontend/layout/multishipping_checkout_overview.xml @@ -8,7 +8,7 @@ - + diff --git a/app/code/Magento/CheckoutAgreements/view/frontend/templates/multishipping_agreements.phtml b/app/code/Magento/CheckoutAgreements/view/frontend/templates/multishipping_agreements.phtml index 3400770f5cee8..33227f0cdce3c 100644 --- a/app/code/Magento/CheckoutAgreements/view/frontend/templates/multishipping_agreements.phtml +++ b/app/code/Magento/CheckoutAgreements/view/frontend/templates/multishipping_agreements.phtml @@ -4,6 +4,7 @@ * See COPYING.txt for license details. */ +// @deprecated // @codingStandardsIgnoreFile ?> diff --git a/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php b/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php index b2ef78bab9909..ca563bd9d8f61 100644 --- a/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php +++ b/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php @@ -270,7 +270,8 @@ public function getDirsCollection($path) $collection = $this->getCollection($path) ->setCollectDirs(true) ->setCollectFiles(false) - ->setCollectRecursively(false); + ->setCollectRecursively(false) + ->setOrder('basename', \Magento\Framework\Data\Collection\Filesystem::SORT_ORDER_ASC); $conditions = $this->getConditionsForExcludeDirs(); diff --git a/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php b/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php index 309f08a54aab6..7bec1e3601461 100644 --- a/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php +++ b/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php @@ -417,6 +417,10 @@ protected function generalTestGetDirsCollection($path, $collectionArray = [], $e ->method('setCollectRecursively') ->with(false) ->willReturnSelf(); + $storageCollectionMock->expects($this->once()) + ->method('setOrder') + ->with('basename', \Magento\Framework\Data\Collection\Filesystem::SORT_ORDER_ASC) + ->willReturnSelf(); $storageCollectionMock->expects($this->once()) ->method('getIterator') ->willReturn(new \ArrayIterator($collectionArray)); diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml index e79f7f75cce3f..93df31a7d89e5 100755 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml @@ -54,7 +54,7 @@ - + @@ -83,4 +83,120 @@ + + + + + + <description value="After selecting a simple product when adding Admin should be switch to configurable implicitly"/> + <severity value="CRITICAL"/> + <useCaseId value="MAGETWO-44165"/> + <testCaseId value="MAGETWO-29398"/> + <group value="catalog"/> + <group value="mtf_migrated"/> + <skip> + <issueId value="MSI-2110"/> + </skip> + </annotations> + <before> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + </before> + <after> + <deleteData stepKey="deleteAttribute" createDataKey="createConfigProductAttribute"/> + </after> + <actionGroup ref="GoToSpecifiedCreateProductPage" stepKey="openProductFillForm"> + <argument name="productType" value="simple"/> + </actionGroup> + <!-- Create configurable product from simple product page--> + <comment userInput="Create configurable product" stepKey="commentCreateProduct"/> + <actionGroup ref="fillMainProductForm" stepKey="fillProductForm"> + <argument name="product" value="_defaultProduct"/> + </actionGroup> + <comment before="createConfiguration" stepKey="beforeCreateConfiguration" userInput="Adding Configuration to Product"/> + <actionGroup ref="generateConfigurationsByAttributeCode" stepKey="createConfiguration" after="fillProductForm"> + <argument name="attributeCode" value="$$createConfigProductAttribute.attribute_code$$"/> + </actionGroup> + <actionGroup ref="saveConfiguredProduct" stepKey="saveProductForm"/> + <see selector="{{AdminProductGridSection.productGridCell('2', 'Type')}}" userInput="Configurable Product" stepKey="seeProductTypeInGrid"/> + <!-- Verify product on store front --> + <comment userInput="Verify product on store front" stepKey="commentVerifyProductGrid"/> + <actionGroup ref="VerifyOptionInProductStorefront" stepKey="verifyConfigurableOption" after="AssertProductInStorefrontProductPage"> + <argument name="attributeCode" value="$createConfigProductAttribute.default_frontend_label$"/> + <argument name="optionName" value="$createConfigProductAttributeOption1.option[store_labels][1][label]$"/> + </actionGroup> + </test> + <test name="AdminCreateDownloadableProductSwitchToConfigurableTest"> + <annotations> + <features value="Catalog"/> + <stories value="Product Type Switching"/> + <title value="Admin should be able to switch a new product from downloadable to configurable"/> + <description value="After selecting a downloadable product when adding Admin should be switch to configurable implicitly"/> + <severity value="CRITICAL"/> + <useCaseId value="MAGETWO-44165"/> + <testCaseId value="MAGETWO-29398"/> + <group value="catalog"/> + <group value="mtf_migrated"/> + <skip> + <issueId value="MSI-2110"/> + </skip> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createPreReqCategory"/> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + </before> + <after> + <actionGroup ref="GoToProductCatalogPage" stepKey="goToProductCatalogPage"/> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteConfigurableProduct"> + <argument name="product" value="_defaultProduct"/> + </actionGroup> + <actionGroup ref="resetProductGridToDefaultView" stepKey="resetSearch"/> + <deleteData createDataKey="createPreReqCategory" stepKey="deletePreReqCategory"/> + <deleteData stepKey="deleteAttribute" createDataKey="createConfigProductAttribute"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Create configurable product from downloadable product page--> + <comment userInput="Create configurable product" stepKey="commentCreateProduct"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> + <!-- Open Dropdown and select downloadable product option --> + <comment stepKey="beforeOpenProductFillForm" userInput="Selecting Product from the Add Product Dropdown"/> + <actionGroup ref="GoToSpecifiedCreateProductPage" stepKey="openProductFillForm"> + <argument name="productType" value="downloadable"/> + </actionGroup> + <scrollTo selector="{{AdminProductDownloadableSection.sectionHeader}}" stepKey="scrollToDownloadableInfo" /> + <uncheckOption selector="{{AdminProductDownloadableSection.isDownloadableProduct}}" stepKey="checkIsDownloadable"/> + <!-- Fill form for Downloadable Product Type --> + <comment stepKey="beforeFillProductForm" userInput="Filling Product Form"/> + <actionGroup ref="fillMainProductForm" stepKey="fillProductForm"> + <argument name="product" value="_defaultProduct"/> + </actionGroup> + <actionGroup ref="SetProductUrlKey" stepKey="setProductUrl"> + <argument name="product" value="_defaultProduct"/> + </actionGroup> + <comment before="createConfiguration" stepKey="beforeCreateConfiguration" userInput="Adding Configuration to Product"/> + <actionGroup ref="generateConfigurationsByAttributeCode" stepKey="createConfiguration"> + <argument name="attributeCode" value="$$createConfigProductAttribute.attribute_code$$"/> + </actionGroup> + <actionGroup ref="saveConfiguredProduct" stepKey="saveProductForm"/> + <!-- Check that product was added with implicit type change --> + <comment stepKey="beforeVerify" userInput="Verify Product Type Assigned Correctly"/> + <actionGroup ref="GoToProductCatalogPage" stepKey="goToProductCatalogPage"/> + <actionGroup ref="resetProductGridToDefaultView" stepKey="resetSearch"/> + <actionGroup ref="filterProductGridByName" stepKey="searchForProduct"> + <argument name="product" value="_defaultProduct"/> + </actionGroup> + <see selector="{{AdminProductGridSection.productGridCell('2', 'Type')}}" userInput="Configurable Product" stepKey="seeProductTypeInGrid"/> + <actionGroup ref="AssertProductInStorefrontProductPage" stepKey="assertProductInStorefrontProductPage"> + <argument name="product" value="_defaultProduct"/> + </actionGroup> + <actionGroup ref="VerifyOptionInProductStorefront" stepKey="verifyConfigurableOption"> + <argument name="attributeCode" value="$createConfigProductAttribute.default_frontend_label$"/> + <argument name="optionName" value="$createConfigProductAttributeOption1.option[store_labels][1][label]$"/> + </actionGroup> + </test> </tests> diff --git a/app/code/Magento/ConfigurableProduct/etc/di.xml b/app/code/Magento/ConfigurableProduct/etc/di.xml index 06134fcbf09d8..c3ffe988b00d7 100644 --- a/app/code/Magento/ConfigurableProduct/etc/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/di.xml @@ -248,4 +248,11 @@ <type name="Magento\Tax\Model\Sales\Total\Quote\CommonTaxCollector"> <plugin name="apply_tax_class_id" type="Magento\ConfigurableProduct\Plugin\Tax\Model\Sales\Total\Quote\CommonTaxCollector" /> </type> + <type name="Magento\Eav\Model\Entity\Attribute\Group"> + <arguments> + <argument name="reservedSystemNames" xsi:type="array"> + <item name="configurable" xsi:type="string">configurable</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls b/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls index 40ef6975fad8b..8da1920f9a444 100644 --- a/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls +++ b/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls @@ -10,8 +10,10 @@ type Query { type Currency { base_currency_code: String base_currency_symbol: String - default_display_currecy_code: String - default_display_currecy_symbol: String + default_display_currecy_code: String @deprecated(reason: "Symbol was missed. Use `default_display_currency_code`.") + default_display_currency_code: String + default_display_currecy_symbol: String @deprecated(reason: "Symbol was missed. Use `default_display_currency_symbol`.") + default_display_currency_symbol: String available_currency_codes: [String] exchange_rates: [ExchangeRate] } diff --git a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php index d0a5e8de53ae9..1fd71e446e6bb 100644 --- a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php +++ b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php @@ -1683,14 +1683,16 @@ public function saveAttribute(DataObject $object, $attributeCode) $connection->beginTransaction(); try { - $select = $connection->select()->from($table, 'value_id')->where($where); - $origValueId = $connection->fetchOne($select); + $select = $connection->select()->from($table, ['value_id', 'value'])->where($where); + $origRow = $connection->fetchRow($select); + $origValueId = $origRow['value_id'] ?? false; + $origValue = $origRow['value'] ?? null; if ($origValueId === false && $newValue !== null) { $this->_insertAttribute($object, $attribute, $newValue); } elseif ($origValueId !== false && $newValue !== null) { $this->_updateAttribute($object, $attribute, $origValueId, $newValue); - } elseif ($origValueId !== false && $newValue === null) { + } elseif ($origValueId !== false && $newValue === null && $origValue !== null) { $connection->delete($table, $where); } $this->_processAttributeValues(); diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/Group.php b/app/code/Magento/Eav/Model/Entity/Attribute/Group.php index 0b6ac2b998de7..2e55964560588 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/Group.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/Group.php @@ -3,11 +3,16 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Eav\Model\Entity\Attribute; +use Magento\Eav\Api\Data\AttributeGroupExtensionInterface; use Magento\Framework\Api\AttributeValueFactory; +use Magento\Framework\Exception\LocalizedException; /** + * Entity attribute group model + * * @api * @method int getSortOrder() * @method \Magento\Eav\Model\Entity\Attribute\Group setSortOrder(int $value) @@ -27,6 +32,11 @@ class Group extends \Magento\Framework\Model\AbstractExtensibleModel implements */ private $translitFilter; + /** + * @var array + */ + private $reservedSystemNames = []; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -35,7 +45,8 @@ class Group extends \Magento\Framework\Model\AbstractExtensibleModel implements * @param \Magento\Framework\Filter\Translit $translitFilter * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection - * @param array $data + * @param array $data (optional) + * @param array $reservedSystemNames (optional) */ public function __construct( \Magento\Framework\Model\Context $context, @@ -45,7 +56,8 @@ public function __construct( \Magento\Framework\Filter\Translit $translitFilter, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, - array $data = [] + array $data = [], + array $reservedSystemNames = [] ) { parent::__construct( $context, @@ -56,6 +68,7 @@ public function __construct( $resourceCollection, $data ); + $this->reservedSystemNames = $reservedSystemNames; $this->translitFilter = $translitFilter; } @@ -74,6 +87,7 @@ protected function _construct() * Checks if current attribute group exists * * @return bool + * @throws LocalizedException * @codeCoverageIgnore */ public function itemExists() @@ -85,6 +99,7 @@ public function itemExists() * Delete groups * * @return $this + * @throws LocalizedException * @codeCoverageIgnore */ public function deleteGroups() @@ -110,9 +125,10 @@ public function beforeSave() ), '-' ); - if (empty($attributeGroupCode)) { + $isReservedSystemName = in_array(strtolower($attributeGroupCode), $this->reservedSystemNames); + if (empty($attributeGroupCode) || $isReservedSystemName) { // in the following code md5 is not used for security purposes - $attributeGroupCode = md5($groupName); + $attributeGroupCode = md5(strtolower($groupName)); } $this->setAttributeGroupCode($attributeGroupCode); } @@ -121,7 +137,8 @@ public function beforeSave() } /** - * {@inheritdoc} + * @inheritdoc + * * @codeCoverageIgnoreStart */ public function getAttributeGroupId() @@ -130,7 +147,7 @@ public function getAttributeGroupId() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAttributeGroupName() { @@ -138,7 +155,7 @@ public function getAttributeGroupName() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAttributeSetId() { @@ -146,7 +163,7 @@ public function getAttributeSetId() } /** - * {@inheritdoc} + * @inheritdoc */ public function setAttributeGroupId($attributeGroupId) { @@ -154,7 +171,7 @@ public function setAttributeGroupId($attributeGroupId) } /** - * {@inheritdoc} + * @inheritdoc */ public function setAttributeGroupName($attributeGroupName) { @@ -162,7 +179,7 @@ public function setAttributeGroupName($attributeGroupName) } /** - * {@inheritdoc} + * @inheritdoc */ public function setAttributeSetId($attributeSetId) { @@ -170,9 +187,9 @@ public function setAttributeSetId($attributeSetId) } /** - * {@inheritdoc} + * @inheritdoc * - * @return \Magento\Eav\Api\Data\AttributeGroupExtensionInterface|null + * @return AttributeGroupExtensionInterface|null */ public function getExtensionAttributes() { @@ -180,14 +197,13 @@ public function getExtensionAttributes() } /** - * {@inheritdoc} + * @inheritdoc * - * @param \Magento\Eav\Api\Data\AttributeGroupExtensionInterface $extensionAttributes + * @param AttributeGroupExtensionInterface $extensionAttributes * @return $this */ - public function setExtensionAttributes( - \Magento\Eav\Api\Data\AttributeGroupExtensionInterface $extensionAttributes - ) { + public function setExtensionAttributes(AttributeGroupExtensionInterface $extensionAttributes) + { return $this->_setExtensionAttributes($extensionAttributes); } diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/GroupTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/GroupTest.php index d4c91e98d9608..1584b922abaa9 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/GroupTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/GroupTest.php @@ -40,6 +40,7 @@ protected function setUp() 'resource' => $this->resourceMock, 'translitFilter' => $translitFilter, 'context' => $contextMock, + 'reservedSystemNames' => ['configurable'], ]; $objectManager = new ObjectManager($this); $this->model = $objectManager->getObject( @@ -67,6 +68,8 @@ public function attributeGroupCodeDataProvider() { return [ ['General Group', 'general-group'], + ['configurable', md5('configurable')], + ['configurAble', md5('configurable')], ['///', md5('///')], ]; } diff --git a/app/code/Magento/Eav/etc/di.xml b/app/code/Magento/Eav/etc/di.xml index a4c89dcfab2af..db6f9b0a64f9f 100644 --- a/app/code/Magento/Eav/etc/di.xml +++ b/app/code/Magento/Eav/etc/di.xml @@ -210,4 +210,3 @@ </arguments> </type> </config> - diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder/Term.php b/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder/Term.php index 0c03a9df18dc8..eeb48f805bccf 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder/Term.php +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder/Term.php @@ -22,13 +22,15 @@ public function build( array $queryResult, DataProviderInterface $dataProvider ) { + $buckets = $queryResult['aggregations'][$bucket->getName()]['buckets'] ?? []; $values = []; - foreach ($queryResult['aggregations'][$bucket->getName()]['buckets'] as $resultBucket) { + foreach ($buckets as $resultBucket) { $values[$resultBucket['key']] = [ 'value' => $resultBucket['key'], 'count' => $resultBucket['doc_count'], ]; } + return $values; } } diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php index aaa9d8a88382f..afd383c13421f 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php @@ -5,6 +5,10 @@ */ namespace Magento\Elasticsearch\SearchAdapter\Query\Builder; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ResolverInterface as TypeResolver; +use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerPool; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Search\Request\Query\BoolExpression; use Magento\Framework\Search\Request\QueryInterface as RequestQueryInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; @@ -26,20 +30,49 @@ class Match implements QueryInterface private $fieldMapper; /** + * @deprecated + * @see \Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\TextTransformer * @var PreprocessorInterface[] */ protected $preprocessorContainer; + /** + * @var AttributeProvider + */ + private $attributeProvider; + + /** + * @var TypeResolver + */ + private $fieldTypeResolver; + + /** + * @var ValueTransformerPool + */ + private $valueTransformerPool; + /** * @param FieldMapperInterface $fieldMapper * @param PreprocessorInterface[] $preprocessorContainer + * @param AttributeProvider|null $attributeProvider + * @param TypeResolver|null $fieldTypeResolver + * @param ValueTransformerPool|null $valueTransformerPool */ public function __construct( FieldMapperInterface $fieldMapper, - array $preprocessorContainer + array $preprocessorContainer, + AttributeProvider $attributeProvider = null, + TypeResolver $fieldTypeResolver = null, + ValueTransformerPool $valueTransformerPool = null ) { $this->fieldMapper = $fieldMapper; $this->preprocessorContainer = $preprocessorContainer; + $this->attributeProvider = $attributeProvider ?? ObjectManager::getInstance() + ->get(AttributeProvider::class); + $this->fieldTypeResolver = $fieldTypeResolver ?? ObjectManager::getInstance() + ->get(TypeResolver::class); + $this->valueTransformerPool = $valueTransformerPool ?? ObjectManager::getInstance() + ->get(ValueTransformerPool::class); } /** @@ -72,10 +105,6 @@ public function build(array $selectQuery, RequestQueryInterface $requestQuery, $ */ protected function prepareQuery($queryValue, $conditionType) { - $queryValue = $this->escape($queryValue); - foreach ($this->preprocessorContainer as $preprocessor) { - $queryValue = $preprocessor->process($queryValue); - } $condition = $conditionType === BoolExpression::QUERY_CONDITION_NOT ? self::QUERY_CONDITION_MUST_NOT : $conditionType; return [ @@ -104,10 +133,24 @@ protected function buildQueries(array $matches, array $queryValue) // Checking for quoted phrase \"phrase test\", trim escaped surrounding quotes if found $count = 0; - $value = preg_replace('#^\\\\"(.*)\\\\"$#m', '$1', $queryValue['value'], -1, $count); + $value = preg_replace('#^"(.*)"$#m', '$1', $queryValue['value'], -1, $count); $condition = ($count) ? 'match_phrase' : 'match'; + $transformedTypes = []; foreach ($matches as $match) { + $attributeAdapter = $this->attributeProvider->getByAttributeCode($match['field']); + $fieldType = $this->fieldTypeResolver->getFieldType($attributeAdapter); + $valueTransformer = $this->valueTransformerPool->get($fieldType ?? 'text'); + $valueTransformerHash = \spl_object_hash($valueTransformer); + if (!isset($transformedTypes[$valueTransformerHash])) { + $transformedTypes[$valueTransformerHash] = $valueTransformer->transform($value); + } + $transformedValue = $transformedTypes[$valueTransformerHash]; + if (null === $transformedValue) { + //Value is incompatible with this field type. + continue; + } + $resolvedField = $this->fieldMapper->getFieldName( $match['field'], ['type' => FieldMapperInterface::TYPE_QUERY] @@ -117,8 +160,8 @@ protected function buildQueries(array $matches, array $queryValue) 'body' => [ $condition => [ $resolvedField => [ - 'query' => $value, - 'boost' => isset($match['boost']) ? $match['boost'] : 1, + 'query' => $transformedValue, + 'boost' => $match['boost'] ?? 1, ], ], ], @@ -131,16 +174,13 @@ protected function buildQueries(array $matches, array $queryValue) /** * Escape a value for special query characters such as ':', '(', ')', '*', '?', etc. * - * Cut trailing plus or minus sign, and @ symbol, using of which causes InnoDB to report a syntax error. - * https://dev.mysql.com/doc/refman/5.7/en/fulltext-boolean.html Fulltext-boolean search docs. - * + * @deprecated + * @see \Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\TextTransformer * @param string $value * @return string */ protected function escape($value) { - $value = preg_replace('/@+|[@+-]+$/', '', $value); - $pattern = '/(\+|-|&&|\|\||!|\(|\)|\{|}|\[|]|\^|"|~|\*|\?|:|\\\)/'; $replace = '\\\$1'; diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/DateTransformer.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/DateTransformer.php new file mode 100644 index 0000000000000..49eca6e9d82a6 --- /dev/null +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/DateTransformer.php @@ -0,0 +1,44 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer; + +use Magento\Elasticsearch\Model\Adapter\FieldType\Date; +use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerInterface; + +/** + * Value transformer for date type fields. + */ +class DateTransformer implements ValueTransformerInterface +{ + /** + * @var Date + */ + private $dateFieldType; + + /** + * @param Date $dateFieldType + */ + public function __construct(Date $dateFieldType) + { + $this->dateFieldType = $dateFieldType; + } + + /** + * @inheritdoc + */ + public function transform(string $value): ?string + { + try { + $formattedDate = $this->dateFieldType->formatDate(null, $value); + } catch (\Exception $e) { + $formattedDate = null; + } + + return $formattedDate; + } +} diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/FloatTransformer.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/FloatTransformer.php new file mode 100644 index 0000000000000..5e330076d3df7 --- /dev/null +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/FloatTransformer.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer; + +use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerInterface; + +/** + * Value transformer for float type fields. + */ +class FloatTransformer implements ValueTransformerInterface +{ + /** + * @inheritdoc + */ + public function transform(string $value): ?float + { + return \is_numeric($value) ? (float) $value : null; + } +} diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/IntegerTransformer.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/IntegerTransformer.php new file mode 100644 index 0000000000000..0846ff3a9bd86 --- /dev/null +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/IntegerTransformer.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer; + +use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerInterface; + +/** + * Value transformer for integer type fields. + */ +class IntegerTransformer implements ValueTransformerInterface +{ + /** + * @inheritdoc + */ + public function transform(string $value): ?int + { + return \is_numeric($value) ? (int) $value : null; + } +} diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/TextTransformer.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/TextTransformer.php new file mode 100644 index 0000000000000..68bec2580f621 --- /dev/null +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/TextTransformer.php @@ -0,0 +1,65 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer; + +use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerInterface; +use Magento\Framework\Search\Adapter\Preprocessor\PreprocessorInterface; + +/** + * Value transformer for fields with text types. + */ +class TextTransformer implements ValueTransformerInterface +{ + /** + * @var PreprocessorInterface[] + */ + private $preprocessors; + + /** + * @param PreprocessorInterface[] $preprocessors + */ + public function __construct(array $preprocessors = []) + { + foreach ($preprocessors as $preprocessor) { + if (!$preprocessor instanceof PreprocessorInterface) { + throw new \InvalidArgumentException( + \sprintf('"%s" is not a instance of ValueTransformerInterface.', get_class($preprocessor)) + ); + } + } + + $this->preprocessors = $preprocessors; + } + + /** + * @inheritdoc + */ + public function transform(string $value): string + { + $value = $this->escape($value); + foreach ($this->preprocessors as $preprocessor) { + $value = $preprocessor->process($value); + } + + return $value; + } + + /** + * Escape a value for special query characters such as ':', '(', ')', '*', '?', etc. + * + * @param string $value + * @return string + */ + private function escape(string $value): string + { + $pattern = '/(\+|-|&&|\|\||!|\(|\)|\{|}|\[|]|\^|"|~|\*|\?|:|\\\)/'; + $replace = '\\\$1'; + + return preg_replace($pattern, $replace, $value); + } +} diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformerInterface.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformerInterface.php new file mode 100644 index 0000000000000..c84ddc69cc7a8 --- /dev/null +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformerInterface.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch\SearchAdapter\Query; + +/** + * Value transformer of search term for matching with ES field types. + */ +interface ValueTransformerInterface +{ + /** + * Transform value according to field type. + * + * @param string $value + * @return mixed + */ + public function transform(string $value); +} diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformerPool.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformerPool.php new file mode 100644 index 0000000000000..11a35d79ce1fd --- /dev/null +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformerPool.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch\SearchAdapter\Query; + +/** + * Pool of value transformers. + */ +class ValueTransformerPool +{ + /** + * @var ValueTransformerInterface[] + */ + private $transformers; + + /** + * @param ValueTransformerInterface[] $valueTransformers + */ + public function __construct(array $valueTransformers = []) + { + foreach ($valueTransformers as $valueTransformer) { + if (!$valueTransformer instanceof ValueTransformerInterface) { + throw new \InvalidArgumentException( + \sprintf('"%s" is not a instance of ValueTransformerInterface.', get_class($valueTransformer)) + ); + } + } + + $this->transformers = $valueTransformers; + } + + /** + * Get value transformer related to field type. + * + * @param string $fieldType + * @return ValueTransformerInterface + */ + public function get(string $fieldType): ValueTransformerInterface + { + return $this->transformers[$fieldType] ?? $this->transformers['default']; + } +} diff --git a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchTest.php b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchTest.php index 8114feb09d35d..d0ffc6debcd8a 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchTest.php @@ -5,14 +5,29 @@ */ namespace Magento\Elasticsearch\Test\Unit\SearchAdapter\Query\Builder; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeAdapter; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ResolverInterface as TypeResolver; use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; use Magento\Elasticsearch\SearchAdapter\Query\Builder\Match as MatchQueryBuilder; +use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerInterface; +use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerPool; use Magento\Framework\Search\Request\Query\Match as MatchRequestQuery; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; -use PHPUnit_Framework_MockObject_MockObject as MockObject; +use PHPUnit\Framework\MockObject\MockObject as MockObject; class MatchTest extends \PHPUnit\Framework\TestCase { + /** + * @var AttributeProvider|MockObject + */ + private $attributeProvider; + + /** + * @var TypeResolver|MockObject + */ + private $fieldTypeResolver; + /** * @var MatchQueryBuilder */ @@ -23,46 +38,63 @@ class MatchTest extends \PHPUnit\Framework\TestCase */ protected function setUp() { + $this->attributeProvider = $this->createMock(AttributeProvider::class); + $this->fieldTypeResolver = $this->createMock(TypeResolver::class); + + $valueTransformerPoolMock = $this->createMock(ValueTransformerPool::class); + $valueTransformerMock = $this->createMock(ValueTransformerInterface::class); + $valueTransformerPoolMock->method('get') + ->willReturn($valueTransformerMock); + $valueTransformerMock->method('transform') + ->willReturnArgument(0); + $this->matchQueryBuilder = (new ObjectManager($this))->getObject( MatchQueryBuilder::class, [ 'fieldMapper' => $this->getFieldMapper(), 'preprocessorContainer' => [], + 'attributeProvider' => $this->attributeProvider, + 'fieldTypeResolver' => $this->fieldTypeResolver, + 'valueTransformerPool' => $valueTransformerPoolMock, ] ); } /** * Tests that method constructs a correct select query. - * @see MatchQueryBuilder::build - * - * @dataProvider queryValuesInvariantsProvider * - * @param string $rawQueryValue - * @param string $errorMessage + * @see MatchQueryBuilder::build */ - public function testBuild($rawQueryValue, $errorMessage) + public function testBuild() { - $this->assertSelectQuery( - $this->matchQueryBuilder->build([], $this->getMatchRequestQuery($rawQueryValue), 'not'), - $errorMessage - ); - } + $attributeAdapter = $this->createMock(AttributeAdapter::class); + $this->attributeProvider->expects($this->once()) + ->method('getByAttributeCode') + ->with('some_field') + ->willReturn($attributeAdapter); + $this->fieldTypeResolver->expects($this->once()) + ->method('getFieldType') + ->with($attributeAdapter) + ->willReturn('text'); + + $rawQueryValue = 'query_value'; + $selectQuery = $this->matchQueryBuilder->build([], $this->getMatchRequestQuery($rawQueryValue), 'not'); - /** - * @link https://dev.mysql.com/doc/refman/5.7/en/fulltext-boolean.html Fulltext-boolean search docs. - * - * @return array - */ - public function queryValuesInvariantsProvider() - { - return [ - ['query_value', 'Select query field must match simple raw query value.'], - ['query_value+', 'Specifying a trailing plus sign causes InnoDB to report a syntax error.'], - ['query_value-', 'Specifying a trailing minus sign causes InnoDB to report a syntax error.'], - ['query_@value', 'The @ symbol is reserved for use by the @distance proximity search operator.'], - ['query_value+@', 'The @ symbol is reserved for use by the @distance proximity search operator.'], + $expectedSelectQuery = [ + 'bool' => [ + 'must_not' => [ + [ + 'match' => [ + 'some_field' => [ + 'query' => $rawQueryValue, + 'boost' => 43, + ], + ], + ], + ], + ], ]; + $this->assertEquals($expectedSelectQuery, $selectQuery); } /** @@ -76,6 +108,16 @@ public function queryValuesInvariantsProvider() */ public function testBuildMatchQuery($rawQueryValue, $queryValue, $match) { + $attributeAdapter = $this->createMock(AttributeAdapter::class); + $this->attributeProvider->expects($this->once()) + ->method('getByAttributeCode') + ->with('some_field') + ->willReturn($attributeAdapter); + $this->fieldTypeResolver->expects($this->once()) + ->method('getFieldType') + ->with($attributeAdapter) + ->willReturn('text'); + $query = $this->matchQueryBuilder->build([], $this->getMatchRequestQuery($rawQueryValue), 'should'); $expectedSelectQuery = [ @@ -111,30 +153,6 @@ public function matchProvider() ]; } - /** - * @param array $selectQuery - * @param string $errorMessage - */ - private function assertSelectQuery($selectQuery, $errorMessage) - { - $expectedSelectQuery = [ - 'bool' => [ - 'must_not' => [ - [ - 'match' => [ - 'some_field' => [ - 'query' => 'query_value', - 'boost' => 43, - ], - ], - ], - ], - ], - ]; - - $this->assertEquals($expectedSelectQuery, $selectQuery, $errorMessage); - } - /** * Gets fieldMapper mock object. * diff --git a/app/code/Magento/Elasticsearch/etc/di.xml b/app/code/Magento/Elasticsearch/etc/di.xml index 4a354a2ea528f..9732ae8226431 100644 --- a/app/code/Magento/Elasticsearch/etc/di.xml +++ b/app/code/Magento/Elasticsearch/etc/di.xml @@ -540,4 +540,22 @@ </argument> </arguments> </type> + <type name="Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerPool"> + <arguments> + <argument name="valueTransformers" xsi:type="array"> + <item name="default" xsi:type="object">Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\TextTransformer</item> + <item name="date" xsi:type="object">Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\DateTransformer</item> + <item name="float" xsi:type="object">Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\FloatTransformer</item> + <item name="integer" xsi:type="object">Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\IntegerTransformer</item> + </argument> + </arguments> + </type> + <type name="Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\TextTransformer"> + <arguments> + <argument name="preprocessors" xsi:type="array"> + <item name="stopwordsPreprocessor" xsi:type="object">Magento\Elasticsearch\SearchAdapter\Query\Preprocessor\Stopwords</item> + <item name="synonymsPreprocessor" xsi:type="object">Magento\Search\Adapter\Query\Preprocessor\Synonyms</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Multishipping/view/frontend/web/js/overview.js b/app/code/Magento/Multishipping/view/frontend/web/js/overview.js index 9b867cd7217b1..3a6d73e304974 100644 --- a/app/code/Magento/Multishipping/view/frontend/web/js/overview.js +++ b/app/code/Magento/Multishipping/view/frontend/web/js/overview.js @@ -15,7 +15,7 @@ define([ opacity: 0.5, // CSS opacity for the 'Place Order' button when it's clicked and then disabled. pleaseWaitLoader: 'span.please-wait', // 'Submitting order information...' Ajax loader. placeOrderSubmit: 'button[type="submit"]', // The 'Place Order' button. - agreements: '#checkout-agreements' // Container for all of the checkout agreements and terms/conditions + agreements: '.checkout-agreements' // Container for all of the checkout agreements and terms/conditions }, /** diff --git a/app/code/Magento/Quote/Model/QuoteManagement.php b/app/code/Magento/Quote/Model/QuoteManagement.php index 8f216b64aa9b0..2fcfd2dfadabb 100644 --- a/app/code/Magento/Quote/Model/QuoteManagement.php +++ b/app/code/Magento/Quote/Model/QuoteManagement.php @@ -532,19 +532,7 @@ protected function submitQuote(QuoteEntity $quote, $orderData = []) ); $this->quoteRepository->save($quote); } catch (\Exception $e) { - if (!empty($this->addressesToSync)) { - foreach ($this->addressesToSync as $addressId) { - $this->addressRepository->deleteById($addressId); - } - } - $this->eventManager->dispatch( - 'sales_model_service_quote_submit_failure', - [ - 'order' => $order, - 'quote' => $quote, - 'exception' => $e - ] - ); + $this->rollbackAddresses($quote, $order, $e); throw $e; } return $order; @@ -611,4 +599,41 @@ protected function _prepareCustomerQuote($quote) $shipping->setIsDefaultBilling(true); } } + + /** + * Remove related to order and quote addresses and submit exception to further processing. + * + * @param Quote $quote + * @param \Magento\Sales\Api\Data\OrderInterface $order + * @param \Exception $e + * @throws \Exception + */ + private function rollbackAddresses( + QuoteEntity $quote, + \Magento\Sales\Api\Data\OrderInterface $order, + \Exception $e + ): void { + try { + if (!empty($this->addressesToSync)) { + foreach ($this->addressesToSync as $addressId) { + $this->addressRepository->deleteById($addressId); + } + } + $this->eventManager->dispatch( + 'sales_model_service_quote_submit_failure', + [ + 'order' => $order, + 'quote' => $quote, + 'exception' => $e, + ] + ); + } catch (\Exception $consecutiveException) { + $message = sprintf( + "An exception occurred on 'sales_model_service_quote_submit_failure' event: %s", + $consecutiveException->getMessage() + ); + + throw new \Exception($message, 0, $e); + } + } } diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php index 1b32866ed883c..6868ce3f7f1ff 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php @@ -67,6 +67,11 @@ public function execute(Quote $cart, array $cartItemData): void { $sku = $this->extractSku($cartItemData); $qty = $this->extractQty($cartItemData); + if ($qty <= 0) { + throw new GraphQlInputException( + __('Please enter a number greater than 0 in this field.') + ); + } $customizableOptions = $this->extractCustomizableOptions($cartItemData); try { diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php index a93c8032c996a..d1dcb4a48a76b 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php @@ -60,12 +60,12 @@ public function __construct( public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) { if (!isset($args['input']['cart_id']) || empty($args['input']['cart_id'])) { - throw new GraphQlInputException(__('Required parameter "cart_id" is missing')); + throw new GraphQlInputException(__('Required parameter "cart_id" is missing.')); } $maskedCartId = $args['input']['cart_id']; if (!isset($args['input']['payment_method']['code']) || empty($args['input']['payment_method']['code'])) { - throw new GraphQlInputException(__('Required parameter "payment_method" is missing')); + throw new GraphQlInputException(__('Required parameter "code" for "payment_method" is missing.')); } $paymentMethodCode = $args['input']['payment_method']['code']; diff --git a/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php b/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php index da7ab97a1b211..d89a118bff94b 100644 --- a/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php +++ b/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php @@ -769,11 +769,12 @@ public function addOrdersCount() */ public function addRevenueToSelect($convertCurrency = false) { - $expr = $this->getTotalsExpression( + $expr = $this->getTotalsExpressionWithDiscountRefunded( !$convertCurrency, $this->getConnection()->getIfNullSql('main_table.base_subtotal_refunded', 0), $this->getConnection()->getIfNullSql('main_table.base_subtotal_canceled', 0), - $this->getConnection()->getIfNullSql('main_table.base_discount_canceled', 0) + $this->getConnection()->getIfNullSql('ABS(main_table.base_discount_refunded)', 0), + $this->getConnection()->getIfNullSql('ABS(main_table.base_discount_canceled)', 0) ); $this->getSelect()->columns(['revenue' => $expr]); @@ -791,11 +792,12 @@ public function addSumAvgTotals($storeId = 0) /** * calculate average and total amount */ - $expr = $this->getTotalsExpression( + $expr = $this->getTotalsExpressionWithDiscountRefunded( $storeId, $this->getConnection()->getIfNullSql('main_table.base_subtotal_refunded', 0), $this->getConnection()->getIfNullSql('main_table.base_subtotal_canceled', 0), - $this->getConnection()->getIfNullSql('main_table.base_discount_canceled', 0) + $this->getConnection()->getIfNullSql('ABS(main_table.base_discount_refunded)', 0), + $this->getConnection()->getIfNullSql('ABS(main_table.base_discount_canceled)', 0) ); $this->getSelect()->columns( @@ -808,13 +810,15 @@ public function addSumAvgTotals($storeId = 0) } /** - * Get SQL expression for totals + * Get SQL expression for totals. * * @param int $storeId * @param string $baseSubtotalRefunded * @param string $baseSubtotalCanceled * @param string $baseDiscountCanceled * @return string + * @deprecated + * @see getTotalsExpressionWithDiscountRefunded */ protected function getTotalsExpression( $storeId, @@ -825,10 +829,40 @@ protected function getTotalsExpression( $template = ($storeId != 0) ? '(main_table.base_subtotal - %2$s - %1$s - ABS(main_table.base_discount_amount) - %3$s)' : '((main_table.base_subtotal - %1$s - %2$s - ABS(main_table.base_discount_amount) + %3$s) ' - . ' * main_table.base_to_global_rate)'; + . ' * main_table.base_to_global_rate)'; return sprintf($template, $baseSubtotalRefunded, $baseSubtotalCanceled, $baseDiscountCanceled); } + /** + * Get SQL expression for totals with discount refunded. + * + * @param int $storeId + * @param string $baseSubtotalRefunded + * @param string $baseSubtotalCanceled + * @param string $baseDiscountRefunded + * @param string $baseDiscountCanceled + * @return string + */ + private function getTotalsExpressionWithDiscountRefunded( + $storeId, + $baseSubtotalRefunded, + $baseSubtotalCanceled, + $baseDiscountRefunded, + $baseDiscountCanceled + ) { + $template = ($storeId != 0) + ? '(main_table.base_subtotal - %2$s - %1$s - (ABS(main_table.base_discount_amount) - %3$s - %4$s))' + : '((main_table.base_subtotal - %1$s - %2$s - (ABS(main_table.base_discount_amount) - %3$s - %4$s)) ' + . ' * main_table.base_to_global_rate)'; + return sprintf( + $template, + $baseSubtotalRefunded, + $baseSubtotalCanceled, + $baseDiscountRefunded, + $baseDiscountCanceled + ); + } + /** * Sort order by total amount * diff --git a/app/code/Magento/Review/etc/acl.xml b/app/code/Magento/Review/etc/acl.xml index 09b80750da14d..46fdb20dee4a1 100644 --- a/app/code/Magento/Review/etc/acl.xml +++ b/app/code/Magento/Review/etc/acl.xml @@ -16,8 +16,8 @@ </resource> <resource id="Magento_Backend::marketing"> <resource id="Magento_Backend::marketing_user_content"> - <resource id="Magento_Review::reviews_all" title="Reviews" translate="title" sortOrder="10"/> <resource id="Magento_Review::pending" title="Pending Reviews" translate="title" sortOrder="20"/> + <resource id="Magento_Review::reviews_all" title="All Reviews" translate="title" sortOrder="10"/> </resource> </resource> </resource> diff --git a/app/code/Magento/Review/etc/adminhtml/menu.xml b/app/code/Magento/Review/etc/adminhtml/menu.xml index 0a2e49450e0cf..7376329471921 100644 --- a/app/code/Magento/Review/etc/adminhtml/menu.xml +++ b/app/code/Magento/Review/etc/adminhtml/menu.xml @@ -8,8 +8,8 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd"> <menu> <add id="Magento_Review::catalog_reviews_ratings_ratings" title="Rating" translate="title" module="Magento_Review" sortOrder="60" parent="Magento_Backend::stores_attributes" action="review/rating/" resource="Magento_Review::ratings"/> - <add id="Magento_Review::catalog_reviews_ratings_reviews_all" title="Reviews" translate="title" module="Magento_Review" parent="Magento_Backend::marketing_user_content" sortOrder="10" action="review/product/index" resource="Magento_Review::reviews_all"/> <add id="Magento_Review::catalog_reviews_ratings_pending" title="Pending Reviews" translate="title" module="Magento_Review" parent="Magento_Backend::marketing_user_content" sortOrder="20" action="review/product/pending" resource="Magento_Review::pending"/> + <add id="Magento_Review::catalog_reviews_ratings_reviews_all" title="All Reviews" translate="title" module="Magento_Review" parent="Magento_Backend::marketing_user_content" sortOrder="10" action="review/product/index" resource="Magento_Review::reviews_all"/> <add id="Magento_Review::report_review" title="Reviews" translate="title" module="Magento_Reports" sortOrder="20" parent="Magento_Reports::report" resource="Magento_Reports::review"/> <add id="Magento_Review::report_review_customer" title="By Customers" translate="title" sortOrder="10" module="Magento_Review" parent="Magento_Review::report_review" action="reports/report_review/customer" resource="Magento_Reports::review_customer"/> <add id="Magento_Review::report_review_product" title="By Products" translate="title" sortOrder="20" module="Magento_Review" parent="Magento_Review::report_review" action="reports/report_review/product" resource="Magento_Reports::review_product"/> diff --git a/app/code/Magento/Review/view/frontend/web/js/process-reviews.js b/app/code/Magento/Review/view/frontend/web/js/process-reviews.js index d1c40959e3ec2..88c61fa38af34 100644 --- a/app/code/Magento/Review/view/frontend/web/js/process-reviews.js +++ b/app/code/Magento/Review/view/frontend/web/js/process-reviews.js @@ -20,7 +20,7 @@ define([ showLoader: false, loaderContext: $('.product.data.items') }).done(function (data) { - $('#product-review-container').html(data); + $('#product-review-container').html(data).trigger('contentUpdated'); $('[data-role="product-review"] .pages a').each(function (index, element) { $(element).click(function (event) { //eslint-disable-line max-nested-callbacks processReviews($(element).attr('href'), true); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/NewAction.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/NewAction.php index 3295b244f323e..ceb231248ef5e 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/NewAction.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/NewAction.php @@ -1,18 +1,21 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Sales\Controller\Adminhtml\Order\Invoice; -use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Backend\App\Action; use Magento\Framework\Registry; use Magento\Framework\View\Result\PageFactory; +use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Model\Service\InvoiceService; +/** + * Create new invoice action. + */ class NewAction extends \Magento\Backend\App\Action implements HttpGetActionInterface { /** @@ -37,22 +40,32 @@ class NewAction extends \Magento\Backend\App\Action implements HttpGetActionInte */ private $invoiceService; + /** + * @var OrderRepositoryInterface + */ + private $orderRepository; + /** * @param Action\Context $context * @param Registry $registry * @param PageFactory $resultPageFactory * @param InvoiceService $invoiceService + * @param OrderRepositoryInterface|null $orderRepository */ public function __construct( Action\Context $context, Registry $registry, PageFactory $resultPageFactory, - InvoiceService $invoiceService + InvoiceService $invoiceService, + OrderRepositoryInterface $orderRepository = null ) { + parent::__construct($context); + $this->registry = $registry; $this->resultPageFactory = $resultPageFactory; - parent::__construct($context); $this->invoiceService = $invoiceService; + $this->orderRepository = $orderRepository ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(OrderRepositoryInterface::class); } /** @@ -78,14 +91,11 @@ public function execute() { $orderId = $this->getRequest()->getParam('order_id'); $invoiceData = $this->getRequest()->getParam('invoice', []); - $invoiceItems = isset($invoiceData['items']) ? $invoiceData['items'] : []; + $invoiceItems = $invoiceData['items'] ?? []; try { /** @var \Magento\Sales\Model\Order $order */ - $order = $this->_objectManager->create(\Magento\Sales\Model\Order::class)->load($orderId); - if (!$order->getId()) { - throw new \Magento\Framework\Exception\LocalizedException(__('The order no longer exists.')); - } + $order = $this->orderRepository->get($orderId); if (!$order->canInvoice()) { throw new \Magento\Framework\Exception\LocalizedException( diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/NewActionTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/NewActionTest.php index 05c99c9f9ef98..87e27fdb2206b 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/NewActionTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/NewActionTest.php @@ -6,12 +6,14 @@ namespace Magento\Sales\Test\Unit\Controller\Adminhtml\Order\Invoice; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Sales\Api\OrderRepositoryInterface; /** * Class NewActionTest * @package Magento\Sales\Controller\Adminhtml\Order\Invoice * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.TooManyFields) */ class NewActionTest extends \PHPUnit\Framework\TestCase { @@ -90,6 +92,11 @@ class NewActionTest extends \PHPUnit\Framework\TestCase */ protected $invoiceServiceMock; + /** + * @var OrderRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $orderRepositoryMock; + protected function setUp() { $objectManager = new ObjectManager($this); @@ -215,12 +222,15 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); + $this->orderRepositoryMock = $this->createMock(OrderRepositoryInterface::class); + $this->controller = $objectManager->getObject( \Magento\Sales\Controller\Adminhtml\Order\Invoice\NewAction::class, [ 'context' => $contextMock, 'resultPageFactory' => $this->resultPageFactoryMock, - 'invoiceService' => $this->invoiceServiceMock + 'invoiceService' => $this->invoiceServiceMock, + 'orderRepository' => $this->orderRepositoryMock ] ); } @@ -250,19 +260,17 @@ public function testExecute() $orderMock = $this->getMockBuilder(\Magento\Sales\Model\Order::class) ->disableOriginalConstructor() - ->setMethods(['load', 'getId', 'canInvoice']) + ->setMethods(['load', 'canInvoice']) ->getMock(); - $orderMock->expects($this->once()) - ->method('load') - ->with($orderId) - ->willReturnSelf(); - $orderMock->expects($this->once()) - ->method('getId') - ->willReturn($orderId); $orderMock->expects($this->once()) ->method('canInvoice') ->willReturn(true); + $this->orderRepositoryMock->expects($this->once()) + ->method('get') + ->with($orderId) + ->willReturn($orderMock); + $this->invoiceServiceMock->expects($this->once()) ->method('prepareInvoice') ->with($orderMock, []) @@ -285,11 +293,7 @@ public function testExecute() ->with(true) ->will($this->returnValue($commentText)); - $this->objectManagerMock->expects($this->at(0)) - ->method('create') - ->with(\Magento\Sales\Model\Order::class) - ->willReturn($orderMock); - $this->objectManagerMock->expects($this->at(1)) + $this->objectManagerMock->expects($this->once()) ->method('get') ->with(\Magento\Backend\Model\Session::class) ->will($this->returnValue($this->sessionMock)); @@ -318,19 +322,12 @@ public function testExecuteNoOrder() $orderMock = $this->getMockBuilder(\Magento\Sales\Model\Order::class) ->disableOriginalConstructor() - ->setMethods(['load', 'getId', 'canInvoice']) + ->setMethods(['canInvoice']) ->getMock(); - $orderMock->expects($this->once()) - ->method('load') - ->with($orderId) - ->willReturnSelf(); - $orderMock->expects($this->once()) - ->method('getId') - ->willReturn(null); - $this->objectManagerMock->expects($this->at(0)) - ->method('create') - ->with(\Magento\Sales\Model\Order::class) + $this->orderRepositoryMock->expects($this->once()) + ->method('get') + ->with($orderId) ->willReturn($orderMock); $resultRedirect = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class) diff --git a/app/code/Magento/Search/Model/ResourceModel/SynonymReader.php b/app/code/Magento/Search/Model/ResourceModel/SynonymReader.php index 46e794a1954cf..45eee0a4001d1 100644 --- a/app/code/Magento/Search/Model/ResourceModel/SynonymReader.php +++ b/app/code/Magento/Search/Model/ResourceModel/SynonymReader.php @@ -87,7 +87,7 @@ private function queryByPhrase($phrase) { $matchQuery = $this->fullTextSelect->getMatchQuery( ['synonyms' => 'synonyms'], - $phrase, + $this->escapePhrase($phrase), Fulltext::FULLTEXT_MODE_BOOLEAN ); $query = $this->getConnection()->select()->from( @@ -97,6 +97,18 @@ private function queryByPhrase($phrase) return $this->getConnection()->fetchAll($query); } + /** + * Cut trailing plus or minus sign, and @ symbol, using of which causes InnoDB to report a syntax error. + * + * @see https://dev.mysql.com/doc/refman/5.7/en/fulltext-boolean.html + * @param string $phrase + * @return string + */ + private function escapePhrase(string $phrase): string + { + return preg_replace('/@+|[@+-]+$/', '', $phrase); + } + /** * A private helper function to retrieve matching synonym groups per scope * diff --git a/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/AddTrack.php b/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/AddTrack.php index fa83db490e380..c417a202321b2 100644 --- a/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/AddTrack.php +++ b/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/AddTrack.php @@ -1,14 +1,27 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Shipping\Controller\Adminhtml\Order\Shipment; use Magento\Backend\App\Action; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\Controller\ResultInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\Sales\Api\Data\ShipmentTrackInterfaceFactory; +use Magento\Sales\Api\ShipmentRepositoryInterface; +use Magento\Shipping\Controller\Adminhtml\Order\ShipmentLoader; -class AddTrack extends \Magento\Backend\App\Action +/** + * Add new tracking number to shipment controller. + */ +class AddTrack extends Action implements HttpPostActionInterface { /** * Authorization level of a basic admin session @@ -18,27 +31,54 @@ class AddTrack extends \Magento\Backend\App\Action const ADMIN_RESOURCE = 'Magento_Sales::shipment'; /** - * @var \Magento\Shipping\Controller\Adminhtml\Order\ShipmentLoader + * @var ShipmentLoader */ protected $shipmentLoader; + /** + * @var ShipmentRepositoryInterface + */ + private $shipmentRepository; + + /** + * @var ShipmentTrackInterfaceFactory + */ + private $trackFactory; + + /** + * @var SerializerInterface + */ + private $serializer; + /** * @param Action\Context $context - * @param \Magento\Shipping\Controller\Adminhtml\Order\ShipmentLoader $shipmentLoader + * @param ShipmentLoader $shipmentLoader + * @param ShipmentRepositoryInterface|null $shipmentRepository + * @param ShipmentTrackInterfaceFactory|null $trackFactory + * @param SerializerInterface|null $serializer */ public function __construct( Action\Context $context, - \Magento\Shipping\Controller\Adminhtml\Order\ShipmentLoader $shipmentLoader + ShipmentLoader $shipmentLoader, + ShipmentRepositoryInterface $shipmentRepository = null, + ShipmentTrackInterfaceFactory $trackFactory = null, + SerializerInterface $serializer = null ) { - $this->shipmentLoader = $shipmentLoader; parent::__construct($context); + + $this->shipmentLoader = $shipmentLoader; + $this->shipmentRepository = $shipmentRepository ?: ObjectManager::getInstance() + ->get(ShipmentRepositoryInterface::class); + $this->trackFactory = $trackFactory ?: ObjectManager::getInstance() + ->get(ShipmentTrackInterfaceFactory::class); + $this->serializer = $serializer ?: ObjectManager::getInstance() + ->get(SerializerInterface::class); } /** - * Add new tracking number action + * Add new tracking number action. * - * @return void - * @throws \Magento\Framework\Exception\LocalizedException + * @return ResultInterface */ public function execute() { @@ -46,28 +86,29 @@ public function execute() $carrier = $this->getRequest()->getPost('carrier'); $number = $this->getRequest()->getPost('number'); $title = $this->getRequest()->getPost('title'); + if (empty($carrier)) { - throw new \Magento\Framework\Exception\LocalizedException(__('Please specify a carrier.')); + throw new LocalizedException(__('Please specify a carrier.')); } if (empty($number)) { - throw new \Magento\Framework\Exception\LocalizedException(__('Please enter a tracking number.')); + throw new LocalizedException(__('Please enter a tracking number.')); } + $this->shipmentLoader->setOrderId($this->getRequest()->getParam('order_id')); $this->shipmentLoader->setShipmentId($this->getRequest()->getParam('shipment_id')); $this->shipmentLoader->setShipment($this->getRequest()->getParam('shipment')); $this->shipmentLoader->setTracking($this->getRequest()->getParam('tracking')); $shipment = $this->shipmentLoader->load(); if ($shipment) { - $track = $this->_objectManager->create( - \Magento\Sales\Model\Order\Shipment\Track::class - )->setNumber( + $track = $this->trackFactory->create()->setNumber( $number )->setCarrierCode( $carrier )->setTitle( $title ); - $shipment->addTrack($track)->save(); + $shipment->addTrack($track); + $this->shipmentRepository->save($shipment); $this->_view->loadLayout(); $this->_view->getPage()->getConfig()->getTitle()->prepend(__('Shipments')); @@ -78,16 +119,18 @@ public function execute() 'message' => __('We can\'t initialize shipment for adding tracking number.'), ]; } - } catch (\Magento\Framework\Exception\LocalizedException $e) { + } catch (LocalizedException $e) { $response = ['error' => true, 'message' => $e->getMessage()]; } catch (\Exception $e) { $response = ['error' => true, 'message' => __('Cannot add tracking number.')]; } - if (is_array($response)) { - $response = $this->_objectManager->get(\Magento\Framework\Json\Helper\Data::class)->jsonEncode($response); - $this->getResponse()->representJson($response); - } else { - $this->getResponse()->setBody($response); + + if (\is_array($response)) { + $response = $this->serializer->serialize($response); + + return $this->resultFactory->create(ResultFactory::TYPE_JSON)->setJsonData($response); } + + return $this->resultFactory->create(ResultFactory::TYPE_RAW)->setContents($response); } } diff --git a/app/code/Magento/Shipping/Test/Unit/Controller/Adminhtml/Order/Shipment/AddTrackTest.php b/app/code/Magento/Shipping/Test/Unit/Controller/Adminhtml/Order/Shipment/AddTrackTest.php index cf3bf68e10416..1c9cdb1fa7d5b 100644 --- a/app/code/Magento/Shipping/Test/Unit/Controller/Adminhtml/Order/Shipment/AddTrackTest.php +++ b/app/code/Magento/Shipping/Test/Unit/Controller/Adminhtml/Order/Shipment/AddTrackTest.php @@ -6,127 +6,162 @@ namespace Magento\Shipping\Test\Unit\Controller\Adminhtml\Order\Shipment; -use Magento\Backend\App\Action; +use Magento\Backend\App\Action\Context; +use Magento\Framework\App\Request\Http; +use Magento\Framework\App\ResponseInterface; +use Magento\Framework\App\ViewInterface; +use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\Controller\ResultInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Framework\View\Element\BlockInterface; +use Magento\Framework\View\LayoutInterface; +use Magento\Framework\View\Page\Config; +use Magento\Framework\View\Page\Title; +use Magento\Framework\View\Result\Page; +use Magento\Sales\Api\Data\ShipmentTrackInterfaceFactory; +use Magento\Sales\Model\Order\Shipment; +use Magento\Sales\Model\Order\Shipment\Track; +use Magento\Shipping\Controller\Adminhtml\Order\Shipment\AddTrack; +use Magento\Shipping\Controller\Adminhtml\Order\ShipmentLoader; /** - * Class AddTrackTest + * Class AddTrackTest covers AddTrack controller. * - * @package Magento\Shipping\Controller\Adminhtml\Order\Shipment * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class AddTrackTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\Shipping\Controller\Adminhtml\Order\ShipmentLoader|\PHPUnit_Framework_MockObject_MockObject + * @var ShipmentLoader|\PHPUnit_Framework_MockObject_MockObject */ - protected $shipmentLoader; + private $shipmentLoader; /** - * @var \Magento\Shipping\Controller\Adminhtml\Order\Shipment\AddTrack + * @var AddTrack */ - protected $controller; + private $controller; /** - * @var Action\Context|\PHPUnit_Framework_MockObject_MockObject + * @var Context|\PHPUnit_Framework_MockObject_MockObject */ - protected $context; + private $context; /** - * @var \Magento\Framework\App\Request\Http|\PHPUnit_Framework_MockObject_MockObject + * @var Http|\PHPUnit_Framework_MockObject_MockObject */ - protected $request; + private $request; /** - * @var \Magento\Framework\App\ResponseInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ResponseInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $response; + private $response; /** - * @var \Magento\Framework\ObjectManager\ObjectManager|\PHPUnit_Framework_MockObject_MockObject + * @var ViewInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $objectManager; + private $view; /** - * @var \Magento\Framework\App\ViewInterface|\PHPUnit_Framework_MockObject_MockObject + * @var Page|\PHPUnit_Framework_MockObject_MockObject */ - protected $view; + private $resultPageMock; /** - * @var \Magento\Framework\View\Result\Page|\PHPUnit_Framework_MockObject_MockObject + * @var Config|\PHPUnit_Framework_MockObject_MockObject */ - protected $resultPageMock; + private $pageConfigMock; /** - * @var \Magento\Framework\View\Page\Config|\PHPUnit_Framework_MockObject_MockObject + * @var Title|\PHPUnit_Framework_MockObject_MockObject */ - protected $pageConfigMock; + private $pageTitleMock; /** - * @var \Magento\Framework\View\Page\Title|\PHPUnit_Framework_MockObject_MockObject + * @var ShipmentTrackInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $pageTitleMock; + private $trackFactory; + /** + * @var ResultInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $rawResult; + + /** + * @inheritdoc + */ protected function setUp() { $objectManagerHelper = new ObjectManagerHelper($this); $this->shipmentLoader = $this->getMockBuilder( - \Magento\Shipping\Controller\Adminhtml\Order\ShipmentLoader::class + ShipmentLoader::class ) ->disableOriginalConstructor() ->setMethods(['setShipmentId', 'setOrderId', 'setShipment', 'setTracking', 'load']) ->getMock(); $this->context = $this->createPartialMock( - \Magento\Backend\App\Action\Context::class, + Context::class, [ 'getRequest', 'getResponse', 'getRedirect', 'getObjectManager', 'getTitle', - 'getView' + 'getView', + 'getResultFactory' ] ); $this->response = $this->createPartialMock( - \Magento\Framework\App\ResponseInterface::class, + ResponseInterface::class, ['setRedirect', 'sendResponse', 'setBody'] ); - $this->request = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) + $this->request = $this->getMockBuilder(Http::class) ->disableOriginalConstructor()->getMock(); - $this->objectManager = $this->createPartialMock( - \Magento\Framework\ObjectManager\ObjectManager::class, - ['create', 'get'] - ); - $this->view = $this->createMock(\Magento\Framework\App\ViewInterface::class); - $this->resultPageMock = $this->getMockBuilder(\Magento\Framework\View\Result\Page::class) + $this->view = $this->createMock(ViewInterface::class); + $this->resultPageMock = $this->getMockBuilder(Page::class) ->disableOriginalConstructor() ->getMock(); - $this->pageConfigMock = $this->getMockBuilder(\Magento\Framework\View\Page\Config::class) + $this->pageConfigMock = $this->getMockBuilder(Config::class) ->disableOriginalConstructor() ->getMock(); - $this->pageTitleMock = $this->getMockBuilder(\Magento\Framework\View\Page\Title::class) + $this->pageTitleMock = $this->getMockBuilder(Title::class) ->disableOriginalConstructor() ->getMock(); + $this->trackFactory = $this->getMockBuilder(ShipmentTrackInterfaceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMockForAbstractClass(); + $this->rawResult = $this->getMockBuilder(ResultInterface::class) + ->disableOriginalConstructor() + ->setMethods(['setContents']) + ->getMockForAbstractClass(); + $resultFactory = $this->getMockBuilder(ResultFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMockForAbstractClass(); $this->context->expects($this->once()) ->method('getRequest') ->will($this->returnValue($this->request)); $this->context->expects($this->once()) ->method('getResponse') ->will($this->returnValue($this->response)); - $this->context->expects($this->once()) - ->method('getObjectManager') - ->will($this->returnValue($this->objectManager)); $this->context->expects($this->once()) ->method('getView') ->will($this->returnValue($this->view)); + $resultFactory->expects($this->once()) + ->method('create') + ->willReturn($this->rawResult); + $this->context->expects($this->once()) + ->method('getResultFactory') + ->willReturn($resultFactory); $this->controller = $objectManagerHelper->getObject( - \Magento\Shipping\Controller\Adminhtml\Order\Shipment\AddTrack::class, + AddTrack::class, [ 'context' => $this->context, 'shipmentLoader' => $this->shipmentLoader, 'request' => $this->request, 'response' => $this->response, - 'view' => $this->view + 'view' => $this->view, + 'trackFactory' => $this->trackFactory, ] ); } @@ -144,7 +179,7 @@ public function testExecute() $tracking = []; $shipmentData = ['items' => [], 'send_email' => '']; $shipment = $this->createPartialMock( - \Magento\Sales\Model\Order\Shipment::class, + Shipment::class, ['addTrack', '__wakeup', 'save'] ); $this->request->expects($this->any()) @@ -152,8 +187,10 @@ public function testExecute() ->will( $this->returnValueMap( [ - ['order_id', null, $orderId], ['shipment_id', null, $shipmentId], - ['shipment', null, $shipmentData], ['tracking', null, $tracking], + ['order_id', null, $orderId], + ['shipment_id', null, $shipmentId], + ['shipment', null, $shipmentData], + ['tracking', null, $tracking], ] ) ); @@ -183,14 +220,13 @@ public function testExecute() $this->shipmentLoader->expects($this->once()) ->method('load') ->will($this->returnValue($shipment)); - $track = $this->getMockBuilder(\Magento\Sales\Model\Order\Shipment\Track::class) + $track = $this->getMockBuilder(Track::class) ->disableOriginalConstructor() ->setMethods(['__wakeup', 'setNumber', 'setCarrierCode', 'setTitle']) ->getMock(); - $this->objectManager->expects($this->atLeastOnce()) + $this->trackFactory->expects($this->once()) ->method('create') - ->with(\Magento\Sales\Model\Order\Shipment\Track::class) - ->will($this->returnValue($track)); + ->willReturn($track); $track->expects($this->once()) ->method('setNumber') ->with($number) @@ -206,8 +242,8 @@ public function testExecute() $this->view->expects($this->once()) ->method('loadLayout') ->will($this->returnSelf()); - $layout = $this->createMock(\Magento\Framework\View\LayoutInterface::class); - $menuBlock = $this->createPartialMock(\Magento\Framework\View\Element\BlockInterface::class, ['toHtml']); + $layout = $this->createMock(LayoutInterface::class); + $menuBlock = $this->createPartialMock(BlockInterface::class, ['toHtml']); $html = 'html string'; $this->view->expects($this->once()) ->method('getLayout') @@ -235,9 +271,10 @@ public function testExecute() $this->pageConfigMock->expects($this->any()) ->method('getTitle') ->willReturn($this->pageTitleMock); - $this->response->expects($this->once()) - ->method('setBody') - ->with($html); - $this->assertNull($this->controller->execute()); + $this->rawResult->expects($this->once()) + ->method('setContents') + ->with($html) + ->willReturnSelf(); + $this->assertInstanceOf(ResultInterface::class, $this->controller->execute()); } } diff --git a/app/code/Magento/Ui/view/base/web/js/lib/core/storage/local.js b/app/code/Magento/Ui/view/base/web/js/lib/core/storage/local.js index 87b5b2f5fe8fe..038907c21224d 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/core/storage/local.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/core/storage/local.js @@ -79,7 +79,7 @@ define([ /** * Extracts and parses data stored in localStorage by the - * key specified in 'root' varaible. + * key specified in 'root' variable. * * @returns {Object} */ diff --git a/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js b/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js index d765f842a0895..97b47f77beeab 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js @@ -920,12 +920,12 @@ define([ ], 'validate-per-page-value-list': [ function (value) { - var isValid = utils.isEmpty(value), + var isValid = true, values = value.split(','), i; - if (isValid) { - return true; + if (utils.isEmpty(value)) { + return isValid; } for (i = 0; i < values.length; i++) { diff --git a/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php b/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php index d8ceb16d71fdc..2ac1bdd712114 100644 --- a/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php +++ b/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php @@ -105,42 +105,18 @@ protected function doFindOneByData(array $data) $result = null; $requestPath = $data[UrlRewrite::REQUEST_PATH]; - - $data[UrlRewrite::REQUEST_PATH] = [ + $decodedRequestPath = urldecode($requestPath); + $data[UrlRewrite::REQUEST_PATH] = array_unique([ rtrim($requestPath, '/'), rtrim($requestPath, '/') . '/', - ]; + rtrim($decodedRequestPath, '/'), + rtrim($decodedRequestPath, '/') . '/', + ]); $resultsFromDb = $this->connection->fetchAll($this->prepareSelect($data)); - - if (count($resultsFromDb) === 1) { - $resultFromDb = current($resultsFromDb); - $redirectTypes = [OptionProvider::TEMPORARY, OptionProvider::PERMANENT]; - - // If request path matches the DB value or it's redirect - we can return result from DB - $canReturnResultFromDb = ($resultFromDb[UrlRewrite::REQUEST_PATH] === $requestPath - || in_array((int)$resultFromDb[UrlRewrite::REDIRECT_TYPE], $redirectTypes, true)); - - // Otherwise return 301 redirect to request path from DB results - $result = $canReturnResultFromDb ? $resultFromDb : [ - UrlRewrite::ENTITY_TYPE => 'custom', - UrlRewrite::ENTITY_ID => '0', - UrlRewrite::REQUEST_PATH => $requestPath, - UrlRewrite::TARGET_PATH => $resultFromDb[UrlRewrite::REQUEST_PATH], - UrlRewrite::REDIRECT_TYPE => OptionProvider::PERMANENT, - UrlRewrite::STORE_ID => $resultFromDb[UrlRewrite::STORE_ID], - UrlRewrite::DESCRIPTION => null, - UrlRewrite::IS_AUTOGENERATED => '0', - UrlRewrite::METADATA => null, - ]; - } else { - // If we have 2 results - return the row that matches request path - foreach ($resultsFromDb as $resultFromDb) { - if ($resultFromDb[UrlRewrite::REQUEST_PATH] === $requestPath) { - $result = $resultFromDb; - break; - } - } + if ($resultsFromDb) { + $urlRewrite = $this->extractMostRelevantUrlRewrite($requestPath, $resultsFromDb); + $result = $this->prepareUrlRewrite($requestPath, $urlRewrite); } return $result; @@ -149,6 +125,75 @@ protected function doFindOneByData(array $data) return $this->connection->fetchRow($this->prepareSelect($data)); } + /** + * Extract most relevant url rewrite from url rewrites list + * + * @param string $requestPath + * @param array $urlRewrites + * @return array|null + */ + private function extractMostRelevantUrlRewrite(string $requestPath, array $urlRewrites): ?array + { + $prioritizedUrlRewrites = []; + foreach ($urlRewrites as $urlRewrite) { + switch (true) { + case $urlRewrite[UrlRewrite::REQUEST_PATH] === $requestPath: + $priority = 1; + break; + case $urlRewrite[UrlRewrite::REQUEST_PATH] === urldecode($requestPath): + $priority = 2; + break; + case rtrim($urlRewrite[UrlRewrite::REQUEST_PATH], '/') === rtrim($requestPath, '/'): + $priority = 3; + break; + case rtrim($urlRewrite[UrlRewrite::REQUEST_PATH], '/') === rtrim(urldecode($requestPath), '/'): + $priority = 4; + break; + default: + $priority = 5; + break; + } + $prioritizedUrlRewrites[$priority] = $urlRewrite; + } + ksort($prioritizedUrlRewrites); + + return array_shift($prioritizedUrlRewrites); + } + + /** + * Prepare url rewrite + * + * If request path matches the DB value or it's redirect - we can return result from DB + * Otherwise return 301 redirect to request path from DB results + * + * @param string $requestPath + * @param array $urlRewrite + * @return array + */ + private function prepareUrlRewrite(string $requestPath, array $urlRewrite): array + { + $redirectTypes = [OptionProvider::TEMPORARY, OptionProvider::PERMANENT]; + $canReturnResultFromDb = ( + in_array($urlRewrite[UrlRewrite::REQUEST_PATH], [$requestPath, urldecode($requestPath)], true) + || in_array((int) $urlRewrite[UrlRewrite::REDIRECT_TYPE], $redirectTypes, true) + ); + if (!$canReturnResultFromDb) { + $urlRewrite = [ + UrlRewrite::ENTITY_TYPE => 'custom', + UrlRewrite::ENTITY_ID => '0', + UrlRewrite::REQUEST_PATH => $requestPath, + UrlRewrite::TARGET_PATH => $urlRewrite[UrlRewrite::REQUEST_PATH], + UrlRewrite::REDIRECT_TYPE => OptionProvider::PERMANENT, + UrlRewrite::STORE_ID => $urlRewrite[UrlRewrite::STORE_ID], + UrlRewrite::DESCRIPTION => null, + UrlRewrite::IS_AUTOGENERATED => '0', + UrlRewrite::METADATA => null, + ]; + } + + return $urlRewrite; + } + /** * Delete old URLs from DB. * diff --git a/app/code/Magento/Wishlist/ViewModel/AllowedQuantity.php b/app/code/Magento/Wishlist/ViewModel/AllowedQuantity.php new file mode 100644 index 0000000000000..5e4c6b39f3c36 --- /dev/null +++ b/app/code/Magento/Wishlist/ViewModel/AllowedQuantity.php @@ -0,0 +1,80 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Wishlist\ViewModel; + +use Magento\Catalog\Controller\Adminhtml\Product\Initialization\StockDataFilter; +use Magento\Catalog\Model\Product\Configuration\Item\ItemInterface; +use Magento\CatalogInventory\Model\StockRegistry; +use Magento\Framework\View\Element\Block\ArgumentInterface; + +/** + * ViewModel for Wishlist Cart Block + */ +class AllowedQuantity implements ArgumentInterface +{ + /** + * @var StockRegistry + */ + private $stockRegistry; + + /** + * @var ItemInterface + */ + private $item; + + /** + * @param StockRegistry $stockRegistry + */ + public function __construct(StockRegistry $stockRegistry) + { + $this->stockRegistry = $stockRegistry; + } + + /** + * Set product configuration item + * + * @param ItemInterface $item + * @return self + */ + public function setItem(ItemInterface $item): self + { + $this->item = $item; + return $this; + } + + /** + * Get product configuration item + * + * @return ItemInterface + */ + public function getItem(): ItemInterface + { + return $this->item; + } + + /** + * Get min and max qty for wishlist form. + * + * @return array + */ + public function getMinMaxQty(): array + { + $product = $this->getItem()->getProduct(); + $stockItem = $this->stockRegistry->getStockItem($product->getId(), $product->getStore()->getWebsiteId()); + $params = []; + + $params['minAllowed'] = (float)$stockItem->getMinSaleQty(); + if ($stockItem->getMaxSaleQty()) { + $params['maxAllowed'] = (float)$stockItem->getMaxSaleQty(); + } else { + $params['maxAllowed'] = (float)StockDataFilter::MAX_QTY_VALUE; + } + + return $params; + } +} diff --git a/app/code/Magento/Wishlist/view/frontend/layout/wishlist_index_index.xml b/app/code/Magento/Wishlist/view/frontend/layout/wishlist_index_index.xml index b20bc6a4e00ba..d4c3cc7fadd84 100644 --- a/app/code/Magento/Wishlist/view/frontend/layout/wishlist_index_index.xml +++ b/app/code/Magento/Wishlist/view/frontend/layout/wishlist_index_index.xml @@ -41,6 +41,7 @@ </block> <block class="Magento\Wishlist\Block\Customer\Wishlist\Item\Column\Cart" name="customer.wishlist.item.cart" template="Magento_Wishlist::item/column/cart.phtml" cacheable="false"> <arguments> + <argument name="allowedQuantityViewModel" xsi:type="object">Magento\Wishlist\ViewModel\AllowedQuantity</argument> <argument name="title" translate="true" xsi:type="string">Add to Cart</argument> </arguments> </block> diff --git a/app/code/Magento/Wishlist/view/frontend/templates/item/column/cart.phtml b/app/code/Magento/Wishlist/view/frontend/templates/item/column/cart.phtml index 9ea0d1a823235..6cb32d70ee1d8 100644 --- a/app/code/Magento/Wishlist/view/frontend/templates/item/column/cart.phtml +++ b/app/code/Magento/Wishlist/view/frontend/templates/item/column/cart.phtml @@ -11,6 +11,9 @@ /** @var \Magento\Wishlist\Model\Item $item */ $item = $block->getItem(); $product = $item->getProduct(); +/** @var \Magento\Wishlist\ViewModel\AllowedQuantity $viewModel */ +$viewModel = $block->getData('allowedQuantityViewModel'); +$allowedQty = $viewModel->setItem($item)->getMinMaxQty(); ?> <?php foreach ($block->getChildNames() as $childName): ?> <?= /* @noEscape */ $block->getLayout()->renderElement($childName, false) ?> @@ -21,7 +24,7 @@ $product = $item->getProduct(); <div class="field qty"> <label class="label" for="qty[<?= $block->escapeHtmlAttr($item->getId()) ?>]"><span><?= $block->escapeHtml(__('Qty')) ?></span></label> <div class="control"> - <input type="number" data-role="qty" id="qty[<?= $block->escapeHtmlAttr($item->getId()) ?>]" class="input-text qty" data-validate="{'required-number':true,'validate-greater-than-zero':true}" + <input type="number" data-role="qty" id="qty[<?= $block->escapeHtmlAttr($item->getId()) ?>]" class="input-text qty" data-validate="{'required-number':true,'validate-greater-than-zero':true, 'validate-item-quantity':{'minAllowed':<?= /* @noEscape */ $allowedQty['minAllowed'] ?>,'maxAllowed':<?= /* @noEscape */ $allowedQty['maxAllowed'] ?>}}" name="qty[<?= $block->escapeHtmlAttr($item->getId()) ?>]" value="<?= /* @noEscape */ (int)($block->getAddToCartQty($item) * 1) ?>"> </div> </div> diff --git a/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistResolver.php b/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistResolver.php index e3a788af2ea7e..792928ab61aaf 100644 --- a/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistResolver.php +++ b/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistResolver.php @@ -13,6 +13,7 @@ use Magento\Wishlist\Model\ResourceModel\Wishlist as WishlistResourceModel; use Magento\Wishlist\Model\Wishlist; use Magento\Wishlist\Model\WishlistFactory; +use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; /** * Fetches the Wishlist data according to the GraphQL schema @@ -51,6 +52,10 @@ public function resolve( ) { $customerId = $context->getUserId(); + /* Guest checking */ + if (!$customerId && 0 === $customerId) { + throw new GraphQlAuthorizationException(__('The current user cannot perform operations on wishlist')); + } /** @var Wishlist $wishlist */ $wishlist = $this->wishlistFactory->create(); $this->wishlistResource->load($wishlist, $customerId, 'customer_id'); diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less index d8fd1db5ced47..5698afdaac7ae 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less @@ -545,7 +545,6 @@ & > .admin__field-label { #mix-grid .column(@field-label-grid__column, @field-grid__columns); cursor: pointer; - background: @color-white; left: 0; position: absolute; top: 0; diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CurrencyTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CurrencyTest.php index 1ff0b53dda0bb..ad5d71cb08605 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CurrencyTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CurrencyTest.php @@ -21,8 +21,8 @@ public function testGetCurrency() currency { base_currency_code base_currency_symbol - default_display_currecy_code - default_display_currecy_symbol + default_display_currency_code + default_display_currency_symbol available_currency_codes exchange_rates { currency_to @@ -36,8 +36,8 @@ public function testGetCurrency() $this->assertArrayHasKey('currency', $result); $this->assertArrayHasKey('base_currency_code', $result['currency']); $this->assertArrayHasKey('base_currency_symbol', $result['currency']); - $this->assertArrayHasKey('default_display_currecy_code', $result['currency']); - $this->assertArrayHasKey('default_display_currecy_symbol', $result['currency']); + $this->assertArrayHasKey('default_display_currency_code', $result['currency']); + $this->assertArrayHasKey('default_display_currency_symbol', $result['currency']); $this->assertArrayHasKey('available_currency_codes', $result['currency']); $this->assertArrayHasKey('exchange_rates', $result['currency']); } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php index 1e92a2e497bed..d9ab8db62a195 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php @@ -59,6 +59,22 @@ public function testAddSimpleProductToCart() self::assertEquals($sku, $response['addSimpleProductsToCart']['cart']['items'][0]['product']['sku']); } + /** + * @magentoApiDataFixture Magento/Catalog/_files/products.php + * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php + * @expectedException \Exception + * @expectedExceptionMessage Please enter a number greater than 0 in this field. + */ + public function testAddSimpleProductToCartWithNegativeQty() + { + $sku = 'simple'; + $qty = -2; + $maskedQuoteId = $this->getMaskedQuoteId(); + + $query = $this->getAddSimpleProductQuery($maskedQuoteId, $sku, $qty); + $this->graphQlQuery($query); + } + /** * @return string */ diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailablePaymentMethodsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailablePaymentMethodsTest.php index 00627cb893103..ba640bc3402ba 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailablePaymentMethodsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailablePaymentMethodsTest.php @@ -7,10 +7,8 @@ namespace Magento\GraphQl\Quote\Customer; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; use Magento\Integration\Api\CustomerTokenServiceInterface; -use Magento\Quote\Model\QuoteFactory; -use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; -use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; @@ -25,19 +23,9 @@ class GetAvailablePaymentMethodsTest extends GraphQlAbstract private $customerTokenService; /** - * @var QuoteResource + * @var GetMaskedQuoteIdByReservedOrderId */ - private $quoteResource; - - /** - * @var QuoteFactory - */ - private $quoteFactory; - - /** - * @var QuoteIdToMaskedQuoteIdInterface - */ - private $quoteIdToMaskedId; + private $getMaskedQuoteIdByReservedOrderId; /** * @inheritdoc @@ -45,55 +33,60 @@ class GetAvailablePaymentMethodsTest extends GraphQlAbstract protected function setUp() { $objectManager = Bootstrap::getObjectManager(); - $this->quoteResource = $objectManager->get(QuoteResource::class); - $this->quoteFactory = $objectManager->get(QuoteFactory::class); - $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php */ public function testGetAvailablePaymentMethods() { - $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId('test_order_item_with_items'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId); $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('cart', $response); + self::assertArrayHasKey('available_payment_methods', $response['cart']); + self::assertEquals('checkmo', $response['cart']['available_payment_methods'][0]['code']); self::assertEquals('Check / Money order', $response['cart']['available_payment_methods'][0]['title']); - self::assertGreaterThan( - 0, - count($response['cart']['available_payment_methods']), - 'There are no available payment methods for customer cart!' - ); } /** + * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_saved.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php */ public function testGetAvailablePaymentMethodsFromGuestCart() { - $guestQuoteMaskedId = $this->getMaskedQuoteIdByReservedOrderId( - 'test_order_with_virtual_product_without_address' - ); - $query = $this->getQuery($guestQuoteMaskedId); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); $this->expectExceptionMessage( - "The current user cannot perform operations on cart \"$guestQuoteMaskedId\"" + "The current user cannot perform operations on cart \"$maskedQuoteId\"" ); $this->graphQlQuery($query, [], '', $this->getHeaderMap()); } /** + * _security * @magentoApiDataFixture Magento/Customer/_files/three_customers.php - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php */ public function testGetAvailablePaymentMethodsFromAnotherCustomerCart() { - $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId('test_order_item_with_items'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId); $this->expectExceptionMessage( @@ -103,20 +96,27 @@ public function testGetAvailablePaymentMethodsFromAnotherCustomerCart() } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php - * @magentoApiDataFixture Magento/Payment/_files/disable_all_active_payment_methods.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/disable_all_active_payment_methods.php */ - public function testGetAvailablePaymentMethodsIfAllPaymentsAreDisabled() + public function testGetAvailablePaymentMethodsIfPaymentsAreNotPresent() { - $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId('test_order_item_with_items'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId); $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); - self::assertEquals(0, count($response['cart']['available_payment_methods'])); + self::assertArrayHasKey('cart', $response); + self::assertArrayHasKey('available_payment_methods', $response['cart']); + self::assertEmpty($response['cart']['available_payment_methods']); } /** * @magentoApiDataFixture Magento/Customer/_files/customer.php + * * @expectedException \Exception * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id" */ @@ -132,9 +132,8 @@ public function testGetAvailablePaymentMethodsOfNonExistentCart() * @param string $maskedQuoteId * @return string */ - private function getQuery( - string $maskedQuoteId - ): string { + private function getQuery(string $maskedQuoteId): string + { return <<<QUERY { cart(cart_id: "$maskedQuoteId") { @@ -158,16 +157,4 @@ private function getHeaderMap(string $username = 'customer@example.com', string $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; return $headerMap; } - - /** - * @param string $reservedOrderId - * @return string - */ - private function getMaskedQuoteIdByReservedOrderId(string $reservedOrderId): string - { - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reservedOrderId, 'reserved_order_id'); - - return $this->quoteIdToMaskedId->execute((int)$quote->getId()); - } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php index 4b1509eef354e..eb62b8c92f310 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php @@ -7,10 +7,8 @@ namespace Magento\GraphQl\Quote\Customer; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; use Magento\Integration\Api\CustomerTokenServiceInterface; -use Magento\Quote\Model\QuoteFactory; -use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; -use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; @@ -20,19 +18,9 @@ class GetCartTest extends GraphQlAbstract { /** - * @var QuoteResource + * @var GetMaskedQuoteIdByReservedOrderId */ - private $quoteResource; - - /** - * @var QuoteFactory - */ - private $quoteFactory; - - /** - * @var QuoteIdToMaskedQuoteIdInterface - */ - private $quoteIdToMaskedId; + private $getMaskedQuoteIdByReservedOrderId; /** * @var CustomerTokenServiceInterface @@ -42,19 +30,22 @@ class GetCartTest extends GraphQlAbstract protected function setUp() { $objectManager = Bootstrap::getObjectManager(); - $this->quoteResource = $objectManager->get(QuoteResource::class); - $this->quoteFactory = $objectManager->get(QuoteFactory::class); - $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_virtual_product.php */ public function testGetCart() { - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_item_with_items'); - $query = $this->getCartQuery($maskedQuoteId); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); @@ -63,22 +54,23 @@ public function testGetCart() self::assertCount(2, $response['cart']['items']); self::assertNotEmpty($response['cart']['items'][0]['id']); - self::assertEquals($response['cart']['items'][0]['qty'], 2); - self::assertEquals($response['cart']['items'][0]['product']['sku'], 'simple'); + self::assertEquals(2, $response['cart']['items'][0]['qty']); + self::assertEquals('simple', $response['cart']['items'][0]['product']['sku']); self::assertNotEmpty($response['cart']['items'][1]['id']); - self::assertEquals($response['cart']['items'][1]['qty'], 1); - self::assertEquals($response['cart']['items'][1]['product']['sku'], 'simple_one'); + self::assertEquals(2, $response['cart']['items'][1]['qty']); + self::assertEquals('virtual-product', $response['cart']['items'][1]['product']['sku']); } /** + * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php */ public function testGetGuestCart() { - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_with_simple_product_without_address'); - $query = $this->getCartQuery($maskedQuoteId); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); $this->expectExceptionMessage( "The current user cannot perform operations on cart \"{$maskedQuoteId}\"" @@ -87,13 +79,14 @@ public function testGetGuestCart() } /** + * _security * @magentoApiDataFixture Magento/Customer/_files/three_customers.php - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php */ public function testGetAnotherCustomerCart() { - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_item_with_items'); - $query = $this->getCartQuery($maskedQuoteId); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); $this->expectExceptionMessage( "The current user cannot perform operations on cart \"{$maskedQuoteId}\"" @@ -103,33 +96,30 @@ public function testGetAnotherCustomerCart() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php + * * @expectedException \Exception * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id" */ public function testGetNonExistentCart() { $maskedQuoteId = 'non_existent_masked_id'; - $query = $this->getCartQuery($maskedQuoteId); + $query = $this->getQuery($maskedQuoteId); $this->graphQlQuery($query, [], '', $this->getHeaderMap()); } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/make_cart_inactive.php + * * @expectedException \Exception * @expectedExceptionMessage Current user does not have an active cart. */ public function testGetInactiveCart() { - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, 'test_order_with_simple_product_without_address', 'reserved_order_id'); - $quote->setCustomerId(1); - $quote->setIsActive(false); - $this->quoteResource->save($quote); - $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId()); - - $query = $this->getCartQuery($maskedQuoteId); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); $this->graphQlQuery($query, [], '', $this->getHeaderMap()); } @@ -138,12 +128,11 @@ public function testGetInactiveCart() * @param string $maskedQuoteId * @return string */ - private function getCartQuery( - string $maskedQuoteId - ) : string { + private function getQuery(string $maskedQuoteId): string + { return <<<QUERY { - cart(cart_id: "$maskedQuoteId") { + cart(cart_id: "{$maskedQuoteId}") { items { id qty @@ -156,18 +145,6 @@ private function getCartQuery( QUERY; } - /** - * @param string $reversedQuoteId - * @return string - */ - private function getMaskedQuoteIdByReversedQuoteId(string $reversedQuoteId): string - { - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id'); - - return $this->quoteIdToMaskedId->execute((int)$quote->getId()); - } - /** * @param string $username * @param string $password diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php index 67a086311d71a..129375debe068 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php @@ -7,6 +7,7 @@ namespace Magento\GraphQl\Quote\Customer; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; use Magento\Integration\Api\CustomerTokenServiceInterface; use Magento\Quote\Model\QuoteFactory; use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; @@ -19,6 +20,11 @@ */ class SetBillingAddressOnCartTest extends GraphQlAbstract { + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + /** * @var QuoteResource */ @@ -42,6 +48,7 @@ class SetBillingAddressOnCartTest extends GraphQlAbstract protected function setUp() { $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); $this->quoteResource = $objectManager->get(QuoteResource::class); $this->quoteFactory = $objectManager->get(QuoteFactory::class); $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); @@ -49,12 +56,14 @@ protected function setUp() } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ public function testSetNewBillingAddress() { - $maskedQuoteId = $this->assignQuoteToCustomer(); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = <<<QUERY mutation { @@ -106,12 +115,14 @@ public function testSetNewBillingAddress() } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ public function testSetNewBillingAddressWithUseForShippingParameter() { - $maskedQuoteId = $this->assignQuoteToCustomer(); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = <<<QUERY mutation { @@ -181,13 +192,15 @@ public function testSetNewBillingAddressWithUseForShippingParameter() } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ public function testSetBillingAddressFromAddressBook() { - $maskedQuoteId = $this->assignQuoteToCustomer(); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = <<<QUERY mutation { @@ -227,14 +240,17 @@ public function testSetBillingAddressFromAddressBook() } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * * @expectedException \Exception * @expectedExceptionMessage Could not find a address with ID "100" */ public function testSetNotExistedBillingAddressFromAddressBook() { - $maskedQuoteId = $this->assignQuoteToCustomer(); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = <<<QUERY mutation { @@ -258,13 +274,15 @@ public function testSetNotExistedBillingAddressFromAddressBook() } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ public function testSetNewBillingAddressAndFromAddressBookAtSameTime() { - $maskedQuoteId = $this->assignQuoteToCustomer(); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = <<<QUERY mutation { @@ -304,13 +322,16 @@ public function testSetNewBillingAddressAndFromAddressBookAtSameTime() } /** + * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Customer/_files/customer_address.php - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ public function testSetBillingAddressToGuestCart() { - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_with_simple_product_without_address'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = <<<QUERY mutation { @@ -338,6 +359,7 @@ public function testSetBillingAddressToGuestCart() } /** + * _security * @magentoApiDataFixture Magento/Customer/_files/three_customers.php * @magentoApiDataFixture Magento/Customer/_files/customer_address.php * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php @@ -372,9 +394,11 @@ public function testSetBillingAddressToAnotherCustomerCart() } /** + * _security * @magentoApiDataFixture Magento/Customer/_files/three_customers.php * @magentoApiDataFixture Magento/Customer/_files/customer_address.php * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * * @expectedException \Exception * @expectedExceptionMessage Current customer does not have permission to address with ID "1" */ @@ -423,6 +447,60 @@ public function testSetBillingAddressOnNonExistentCart() $this->graphQlQuery($query, [], '', $this->getHeaderMap()); } + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * + * @dataProvider dataProviderSetWithoutRequiredParameters + * @param string $input + * @param string $message + * @throws \Exception + */ + public function testSetBillingAddressWithoutRequiredParameters(string $input, string $message) + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $input = str_replace('cart_id_value', $maskedQuoteId, $input); + + $query = <<<QUERY +mutation { + setBillingAddressOnCart( + input: { + {$input} + } + ) { + cart { + billing_address { + city + } + } + } +} +QUERY; + + $this->expectExceptionMessage($message); + $this->graphQlQuery($query); + } + + /** + * @return array + */ + public function dataProviderSetWithoutRequiredParameters(): array + { + return [ + 'missed_billing_address' => [ + 'cart_id: "cart_id_value"', + 'Field SetBillingAddressOnCartInput.billing_address of required type BillingAddressInput!' + . ' was not provided.', + ], + 'missed_cart_id' => [ + 'billing_address: {}', + 'Required parameter "cart_id" is missing' + ] + ]; + } + /** * Verify the all the whitelisted fields for a New Address Object * @@ -480,28 +558,16 @@ private function getHeaderMap(string $username = 'customer@example.com', string } /** - * @param string $reversedQuoteId - * @return string - */ - private function getMaskedQuoteIdByReversedQuoteId(string $reversedQuoteId): string - { - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id'); - - return $this->quoteIdToMaskedId->execute((int)$quote->getId()); - } - - /** - * @param string $reversedQuoteId + * @param string $reversedOrderId * @param int $customerId * @return string */ private function assignQuoteToCustomer( - string $reversedQuoteId = 'test_order_with_simple_product_without_address', + string $reversedOrderId = 'test_order_with_simple_product_without_address', int $customerId = 1 ): string { $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id'); + $this->quoteResource->load($quote, $reversedOrderId, 'reserved_order_id'); $quote->setCustomerId($customerId); $this->quoteResource->save($quote); return $this->quoteIdToMaskedId->execute((int)$quote->getId()); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodOnCartTest.php index c7da2144adb9e..450a22dd6e9c7 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodOnCartTest.php @@ -7,11 +7,10 @@ namespace Magento\GraphQl\Quote\Customer; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\OfflinePayments\Model\Cashondelivery; use Magento\OfflinePayments\Model\Checkmo; -use Magento\Quote\Model\QuoteFactory; -use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; -use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; @@ -21,24 +20,14 @@ class SetPaymentMethodOnCartTest extends GraphQlAbstract { /** - * @var CustomerTokenServiceInterface - */ - private $customerTokenService; - - /** - * @var QuoteResource + * @var GetMaskedQuoteIdByReservedOrderId */ - private $quoteResource; + private $getMaskedQuoteIdByReservedOrderId; /** - * @var QuoteFactory - */ - private $quoteFactory; - - /** - * @var QuoteIdToMaskedQuoteIdInterface + * @var CustomerTokenServiceInterface */ - private $quoteIdToMaskedId; + private $customerTokenService; /** * @inheritdoc @@ -46,21 +35,23 @@ class SetPaymentMethodOnCartTest extends GraphQlAbstract protected function setUp() { $objectManager = Bootstrap::getObjectManager(); - $this->quoteResource = $objectManager->get(QuoteResource::class); - $this->quoteFactory = $objectManager->get(QuoteFactory::class); - $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_and_address.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php */ - public function testSetPaymentWithVirtualProduct() + public function testSetPaymentOnCartWithSimpleProduct() { $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE; - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_with_virtual_product'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); - $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode); + $query = $this->getQuery($maskedQuoteId, $methodCode); $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('setPaymentMethodOnCart', $response); @@ -70,14 +61,35 @@ public function testSetPaymentWithVirtualProduct() } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * + * @expectedException \Exception + * @expectedExceptionMessage The shipping address is missing. Set the address and try again. + */ + public function testSetPaymentOnCartWithSimpleProductAndWithoutAddress() + { + $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId, $methodCode); + $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_virtual_product.php */ - public function testSetPaymentWithSimpleProduct() + public function testSetPaymentOnCartWithVirtualProduct() { $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE; - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_1'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); - $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode); + $query = $this->getQuery($maskedQuoteId, $methodCode); $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('setPaymentMethodOnCart', $response); @@ -88,103 +100,158 @@ public function testSetPaymentWithSimpleProduct() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * * @expectedException \Exception - * @expectedExceptionMessage The shipping address is missing. Set the address and try again. + * @expectedExceptionMessage The requested Payment Method is not available. */ - public function testSetPaymentWithSimpleProductWithoutAddress() + public function testSetNonExistentPaymentMethod() { - $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE; - $maskedQuoteId = $this->assignQuoteToCustomer('test_order_with_simple_product_without_address', 1); + $methodCode = 'noway'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); - $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode); + $query = $this->getQuery($maskedQuoteId, $methodCode); $this->graphQlQuery($query, [], '', $this->getHeaderMap()); } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * * @expectedException \Exception - * @expectedExceptionMessage The requested Payment Method is not available. + * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id" */ - public function testSetNonExistingPaymentMethod() + public function testSetPaymentOnNonExistentCart() { - $methodCode = 'noway'; - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_1'); - - $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode); + $maskedQuoteId = 'non_existent_masked_id'; + $query = <<<QUERY +{ + cart(cart_id: "$maskedQuoteId") { + items { + id + } + } +} +QUERY; $this->graphQlQuery($query, [], '', $this->getHeaderMap()); } /** + * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ public function testSetPaymentMethodToGuestCart() { $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE; - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_with_simple_product_without_address'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); - $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode); + $query = $this->getQuery($maskedQuoteId, $methodCode); $this->expectExceptionMessage( "The current user cannot perform operations on cart \"$maskedQuoteId\"" ); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); } /** + * _security * @magentoApiDataFixture Magento/Customer/_files/three_customers.php - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ public function testSetPaymentMethodToAnotherCustomerCart() { $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE; - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_1'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); - $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode); + $query = $this->getQuery($maskedQuoteId, $methodCode); $this->expectExceptionMessage( "The current user cannot perform operations on cart \"$maskedQuoteId\"" ); - $this->graphQlQuery($query, [], '', $this->getHeaderMap('customer2@search.example.com')); } /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @expectedException \Exception - * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id" + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * + * @param string $input + * @param string $message + * @throws \Exception + * @dataProvider dataProviderSetPaymentMethodWithoutRequiredParameters */ - public function testPaymentMethodOnNonExistentCart() + public function testSetPaymentMethodWithoutRequiredParameters(string $input, string $message) { - $maskedQuoteId = 'non_existent_masked_id'; $query = <<<QUERY -{ - cart(cart_id: "$maskedQuoteId") { - items { - id +mutation { + setPaymentMethodOnCart( + input: { + {$input} + } + ) { + cart { + items { + qty + } } } } QUERY; + $this->expectExceptionMessage($message); $this->graphQlQuery($query, [], '', $this->getHeaderMap()); } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_payment_saved.php + * @return array + */ + public function dataProviderSetPaymentMethodWithoutRequiredParameters(): array + { + return [ + 'missed_cart_id' => [ + 'payment_method: {code: "' . Checkmo::PAYMENT_METHOD_CHECKMO_CODE . '"}', + 'Required parameter "cart_id" is missing.' + ], + 'missed_payment_method' => [ + 'cart_id: "test"', + 'Required parameter "code" for "payment_method" is missing.' + ], + 'missed_payment_method_code' => [ + 'cart_id: "test", payment_method: {code: ""}', + 'Required parameter "code" for "payment_method" is missing.' + ], + ]; + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_checkmo_payment_method.php */ public function testReSetPayment() { - /** @var \Magento\Quote\Model\Quote $quote */ - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_1_with_payment'); - $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE; - $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $methodCode = Cashondelivery::PAYMENT_METHOD_CASHONDELIVERY_CODE; + $query = $this->getQuery($maskedQuoteId, $methodCode); $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('setPaymentMethodOnCart', $response); self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']); self::assertArrayHasKey('selected_payment_method', $response['setPaymentMethodOnCart']['cart']); + self::assertArrayHasKey('code', $response['setPaymentMethodOnCart']['cart']['selected_payment_method']); self::assertEquals($methodCode, $response['setPaymentMethodOnCart']['cart']['selected_payment_method']['code']); } @@ -193,7 +260,7 @@ public function testReSetPayment() * @param string $methodCode * @return string */ - private function prepareMutationQuery( + private function getQuery( string $maskedQuoteId, string $methodCode ) : string { @@ -215,34 +282,6 @@ private function prepareMutationQuery( QUERY; } - /** - * @param string $reversedQuoteId - * @return string - */ - private function getMaskedQuoteIdByReversedQuoteId(string $reversedQuoteId): string - { - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id'); - - return $this->quoteIdToMaskedId->execute((int)$quote->getId()); - } - - /** - * @param string $reversedQuoteId - * @param int $customerId - * @return string - */ - private function assignQuoteToCustomer( - string $reversedQuoteId, - int $customerId - ): string { - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id'); - $quote->setCustomerId($customerId); - $this->quoteResource->save($quote); - return $this->quoteIdToMaskedId->execute((int)$quote->getId()); - } - /** * @param string $username * @param string $password diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php index ae184b8930c07..5ff29d20b34d7 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php @@ -7,6 +7,7 @@ namespace Magento\GraphQl\Quote\Customer; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; use Magento\Integration\Api\CustomerTokenServiceInterface; use Magento\Quote\Model\QuoteFactory; use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; @@ -34,6 +35,11 @@ class SetShippingAddressOnCartTest extends GraphQlAbstract */ private $quoteIdToMaskedId; + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + /** * @var CustomerTokenServiceInterface */ @@ -45,16 +51,19 @@ protected function setUp() $this->quoteResource = $objectManager->get(QuoteResource::class); $this->quoteFactory = $objectManager->get(QuoteFactory::class); $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); } /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ - public function testSetNewShippingAddress() + public function testSetNewShippingAddressOnCartWithSimpleProduct() { - $maskedQuoteId = $this->assignQuoteToCustomer(); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = <<<QUERY mutation { @@ -108,14 +117,17 @@ public function testSetNewShippingAddress() } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_saved.php * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_virtual_product.php + * * @expectedException \Exception * @expectedExceptionMessage The Cart includes virtual product(s) only, so a shipping address is not used. */ - public function testSetNewShippingAddressOnQuoteWithVirtualProducts() + public function testSetNewShippingAddressOnCartWithVirtualProduct() { - $maskedQuoteId = $this->assignQuoteToCustomer('test_order_with_virtual_product_without_address'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = <<<QUERY mutation { @@ -152,13 +164,15 @@ public function testSetNewShippingAddressOnQuoteWithVirtualProducts() } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ public function testSetShippingAddressFromAddressBook() { - $maskedQuoteId = $this->assignQuoteToCustomer(); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = <<<QUERY mutation { @@ -196,14 +210,17 @@ public function testSetShippingAddressFromAddressBook() } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * * @expectedException \Exception * @expectedExceptionMessage Could not find a address with ID "100" */ public function testSetNonExistentShippingAddressFromAddressBook() { - $maskedQuoteId = $this->assignQuoteToCustomer(); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = <<<QUERY mutation { @@ -229,13 +246,15 @@ public function testSetNonExistentShippingAddressFromAddressBook() } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ public function testSetNewShippingAddressAndFromAddressBookAtSameTime() { - $maskedQuoteId = $this->assignQuoteToCustomer(); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = <<<QUERY mutation { @@ -276,9 +295,11 @@ public function testSetNewShippingAddressAndFromAddressBookAtSameTime() } /** + * _security * @magentoApiDataFixture Magento/Customer/_files/three_customers.php * @magentoApiDataFixture Magento/Customer/_files/customer_address.php * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * * @expectedException \Exception * @expectedExceptionMessage Current customer does not have permission to address with ID "1" */ @@ -311,9 +332,11 @@ public function testSetShippingAddressIfCustomerIsNotOwnerOfAddress() } /** + * _security * @magentoApiDataFixture Magento/Customer/_files/three_customers.php * @magentoApiDataFixture Magento/Customer/_files/customer_address.php * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * * @expectedException \Exception */ public function testSetShippingAddressToAnotherCustomerCart() @@ -343,13 +366,15 @@ public function testSetShippingAddressToAnotherCustomerCart() $this->expectExceptionMessage( "The current user cannot perform operations on cart \"$maskedQuoteId\"" ); - $this->graphQlQuery($query, [], '', $this->getHeaderMap('customer2@search.example.com')); } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * * @dataProvider dataProviderUpdateWithMissedRequiredParameters * @param string $input * @param string $message @@ -357,7 +382,7 @@ public function testSetShippingAddressToAnotherCustomerCart() */ public function testSetNewShippingAddressWithMissedRequiredParameters(string $input, string $message) { - $maskedQuoteId = $this->assignQuoteToCustomer(); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = <<<QUERY mutation { @@ -386,7 +411,7 @@ public function testSetNewShippingAddressWithMissedRequiredParameters(string $in /** * @return array */ - public function dataProviderUpdateWithMissedRequiredParameters() + public function dataProviderUpdateWithMissedRequiredParameters(): array { return [ 'shipping_addresses' => [ @@ -402,13 +427,16 @@ public function dataProviderUpdateWithMissedRequiredParameters() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * * @expectedException \Exception * @expectedExceptionMessage You cannot specify multiple shipping addresses. */ public function testSetMultipleNewShippingAddresses() { - $maskedQuoteId = $this->assignQuoteToCustomer(); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = <<<QUERY mutation { @@ -513,16 +541,16 @@ private function getHeaderMap(string $username = 'customer@example.com', string } /** - * @param string $reversedQuoteId + * @param string $reversedOrderId * @param int $customerId * @return string */ private function assignQuoteToCustomer( - string $reversedQuoteId = 'test_order_with_simple_product_without_address', + string $reversedOrderId = 'test_order_with_simple_product_without_address', int $customerId = 1 ): string { $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id'); + $this->quoteResource->load($quote, $reversedOrderId, 'reserved_order_id'); $quote->setCustomerId($customerId); $this->quoteResource->save($quote); return $this->quoteIdToMaskedId->execute((int)$quote->getId()); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php index 736ea69440753..b5634f51cb366 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php @@ -162,30 +162,30 @@ private function prepareMutationQuery( } /** - * @param string $reversedQuoteId + * @param string $reversedOrderId * @return string * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ - private function getMaskedQuoteIdByReversedQuoteId(string $reversedQuoteId): string + private function getMaskedQuoteIdByReservedOrderId(string $reversedOrderId): string { $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id'); + $this->quoteResource->load($quote, $reversedOrderId, 'reserved_order_id'); return $this->quoteIdToMaskedId->execute((int)$quote->getId()); } /** - * @param string $reversedQuoteId + * @param string $reversedOrderId * @param int $customerId * @return string * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ private function assignQuoteToCustomer( - string $reversedQuoteId, + string $reversedOrderId, int $customerId ): string { $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id'); + $this->quoteResource->load($quote, $reversedOrderId, 'reserved_order_id'); $quote->setCustomerId($customerId); $this->quoteResource->save($quote); return $this->quoteIdToMaskedId->execute((int)$quote->getId()); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetMaskedQuoteIdByReservedOrderId.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetMaskedQuoteIdByReservedOrderId.php new file mode 100644 index 0000000000000..c5a4e8af02a58 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetMaskedQuoteIdByReservedOrderId.php @@ -0,0 +1,63 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote; + +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; +use Magento\Quote\Model\QuoteFactory; + +/** + * Get masked quote id by reserved order id + */ +class GetMaskedQuoteIdByReservedOrderId +{ + /** + * @var QuoteFactory + */ + private $quoteFactory; + + /** + * @var QuoteResource + */ + private $quoteResource; + + /** + * @var QuoteIdToMaskedQuoteIdInterface + */ + private $quoteIdToMaskedId; + + /** + * @param QuoteFactory $quoteFactory + * @param QuoteResource $quoteResource + * @param QuoteIdToMaskedQuoteIdInterface $quoteIdToMaskedId + */ + public function __construct( + QuoteFactory $quoteFactory, + QuoteResource $quoteResource, + QuoteIdToMaskedQuoteIdInterface $quoteIdToMaskedId + ) { + $this->quoteFactory = $quoteFactory; + $this->quoteResource = $quoteResource; + $this->quoteIdToMaskedId = $quoteIdToMaskedId; + } + + /** + * Get masked quote id by reserved order id + * + * @param string $reversedOrderId + * @return string + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function execute(string $reversedOrderId): string + { + $quote = $this->quoteFactory->create(); + $this->quoteResource->load($quote, $reversedOrderId, 'reserved_order_id'); + + return $this->quoteIdToMaskedId->execute((int)$quote->getId()); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailablePaymentMethodsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailablePaymentMethodsTest.php index d083e8a859e9f..8271a76d88f12 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailablePaymentMethodsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailablePaymentMethodsTest.php @@ -7,9 +7,7 @@ namespace Magento\GraphQl\Quote\Guest; -use Magento\Quote\Model\QuoteFactory; -use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; -use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; @@ -19,19 +17,9 @@ class GetAvailablePaymentMethodsTest extends GraphQlAbstract { /** - * @var QuoteResource + * @var GetMaskedQuoteIdByReservedOrderId */ - private $quoteResource; - - /** - * @var QuoteFactory - */ - private $quoteFactory; - - /** - * @var QuoteIdToMaskedQuoteIdInterface - */ - private $quoteIdToMaskedId; + private $getMaskedQuoteIdByReservedOrderId; /** * @inheritdoc @@ -39,43 +27,39 @@ class GetAvailablePaymentMethodsTest extends GraphQlAbstract protected function setUp() { $objectManager = Bootstrap::getObjectManager(); - $this->quoteResource = $objectManager->get(QuoteResource::class); - $this->quoteFactory = $objectManager->get(QuoteFactory::class); - $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php */ public function testGetAvailablePaymentMethods() { - $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId('test_order_with_simple_product_without_address'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId); $response = $this->graphQlQuery($query); self::assertArrayHasKey('cart', $response); + self::assertArrayHasKey('available_payment_methods', $response['cart']); self::assertEquals('checkmo', $response['cart']['available_payment_methods'][0]['code']); self::assertEquals('Check / Money order', $response['cart']['available_payment_methods'][0]['title']); - - self::assertEquals('free', $response['cart']['available_payment_methods'][1]['code']); - self::assertEquals( - 'No Payment Information Required', - $response['cart']['available_payment_methods'][1]['title'] - ); - self::assertGreaterThan( - 0, - count($response['cart']['available_payment_methods']), - 'There are no available payment methods for guest cart!' - ); } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php */ public function testGetAvailablePaymentMethodsFromCustomerCart() { - $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId('test_order_1'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId); $this->expectExceptionMessage( @@ -85,16 +69,21 @@ public function testGetAvailablePaymentMethodsFromCustomerCart() } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php - * @magentoApiDataFixture Magento/Payment/_files/disable_all_active_payment_methods.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/disable_all_active_payment_methods.php */ - public function testGetPaymentMethodsIfPaymentsAreDisabled() + public function testGetAvailablePaymentMethodsIfPaymentsAreNotPresent() { - $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId('test_order_with_simple_product_without_address'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId); $response = $this->graphQlQuery($query); - self::assertEquals(0, count($response['cart']['available_payment_methods'])); + self::assertArrayHasKey('cart', $response); + self::assertArrayHasKey('available_payment_methods', $response['cart']); + self::assertEmpty($response['cart']['available_payment_methods']); } /** @@ -112,9 +101,8 @@ public function testGetAvailablePaymentMethodsOfNonExistentCart() * @param string $maskedQuoteId * @return string */ - private function getQuery( - string $maskedQuoteId - ): string { + private function getQuery(string $maskedQuoteId): string + { return <<<QUERY { cart(cart_id: "$maskedQuoteId") { @@ -126,16 +114,4 @@ private function getQuery( } QUERY; } - - /** - * @param string $reservedOrderId - * @return string - */ - private function getMaskedQuoteIdByReservedOrderId(string $reservedOrderId): string - { - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reservedOrderId, 'reserved_order_id'); - - return $this->quoteIdToMaskedId->execute((int)$quote->getId()); - } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php index 916d5951b0ff2..6b1f540e4d47a 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php @@ -7,10 +7,7 @@ namespace Magento\GraphQl\Quote\Guest; -use Magento\Integration\Api\CustomerTokenServiceInterface; -use Magento\Quote\Model\QuoteFactory; -use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; -use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; @@ -20,41 +17,27 @@ class GetCartTest extends GraphQlAbstract { /** - * @var QuoteResource + * @var GetMaskedQuoteIdByReservedOrderId */ - private $quoteResource; - - /** - * @var QuoteFactory - */ - private $quoteFactory; - - /** - * @var QuoteIdToMaskedQuoteIdInterface - */ - private $quoteIdToMaskedId; - - /** - * @var CustomerTokenServiceInterface - */ - private $customerTokenService; + private $getMaskedQuoteIdByReservedOrderId; protected function setUp() { $objectManager = Bootstrap::getObjectManager(); - $this->quoteResource = $objectManager->get(QuoteResource::class); - $this->quoteFactory = $objectManager->get(QuoteFactory::class); - $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); - $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_virtual_product.php */ public function testGetCart() { - $maskedQuoteId = $this->unAssignCustomerFromQuote('test_order_item_with_items'); - $query = $this->getCartQuery($maskedQuoteId); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); $response = $this->graphQlQuery($query); @@ -63,21 +46,23 @@ public function testGetCart() self::assertCount(2, $response['cart']['items']); self::assertNotEmpty($response['cart']['items'][0]['id']); - self::assertEquals($response['cart']['items'][0]['qty'], 2); - self::assertEquals($response['cart']['items'][0]['product']['sku'], 'simple'); + self::assertEquals(2, $response['cart']['items'][0]['qty']); + self::assertEquals('simple', $response['cart']['items'][0]['product']['sku']); self::assertNotEmpty($response['cart']['items'][1]['id']); - self::assertEquals($response['cart']['items'][1]['qty'], 1); - self::assertEquals($response['cart']['items'][1]['product']['sku'], 'simple_one'); + self::assertEquals(2, $response['cart']['items'][1]['qty']); + self::assertEquals('virtual-product', $response['cart']['items'][1]['product']['sku']); } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php */ public function testGetCustomerCart() { - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_item_with_items'); - $query = $this->getCartQuery($maskedQuoteId); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); $this->expectExceptionMessage( "The current user cannot perform operations on cart \"{$maskedQuoteId}\"" @@ -92,25 +77,22 @@ public function testGetCustomerCart() public function testGetNonExistentCart() { $maskedQuoteId = 'non_existent_masked_id'; - $query = $this->getCartQuery($maskedQuoteId); + $query = $this->getQuery($maskedQuoteId); $this->graphQlQuery($query); } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/make_cart_inactive.php + * * @expectedException \Exception * @expectedExceptionMessage Current user does not have an active cart. */ public function testGetInactiveCart() { - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, 'test_order_with_simple_product_without_address', 'reserved_order_id'); - $quote->setIsActive(false); - $this->quoteResource->save($quote); - $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId()); - - $query = $this->getCartQuery($maskedQuoteId); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); $this->graphQlQuery($query); } @@ -119,12 +101,11 @@ public function testGetInactiveCart() * @param string $maskedQuoteId * @return string */ - private function getCartQuery( - string $maskedQuoteId - ) : string { + private function getQuery(string $maskedQuoteId): string + { return <<<QUERY { - cart(cart_id: "$maskedQuoteId") { + cart(cart_id: "{$maskedQuoteId}") { items { id qty @@ -136,31 +117,4 @@ private function getCartQuery( } QUERY; } - - /** - * @param string $reversedQuoteId - * @return string - */ - private function getMaskedQuoteIdByReversedQuoteId(string $reversedQuoteId): string - { - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id'); - - return $this->quoteIdToMaskedId->execute((int)$quote->getId()); - } - - /** - * @param string $reversedQuoteId - * @param int $customerId - * @return string - */ - private function unAssignCustomerFromQuote( - string $reversedQuoteId - ): string { - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id'); - $quote->setCustomerId(0); - $this->quoteResource->save($quote); - return $this->quoteIdToMaskedId->execute((int)$quote->getId()); - } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php index 27de0d12e413d..3808ce38b9d7b 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php @@ -7,9 +7,7 @@ namespace Magento\GraphQl\Quote\Guest; -use Magento\Quote\Model\QuoteFactory; -use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; -use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; @@ -19,34 +17,24 @@ class SetBillingAddressOnCartTest extends GraphQlAbstract { /** - * @var QuoteResource + * @var GetMaskedQuoteIdByReservedOrderId */ - private $quoteResource; - - /** - * @var QuoteFactory - */ - private $quoteFactory; - - /** - * @var QuoteIdToMaskedQuoteIdInterface - */ - private $quoteIdToMaskedId; + private $getMaskedQuoteIdByReservedOrderId; protected function setUp() { $objectManager = Bootstrap::getObjectManager(); - $this->quoteResource = $objectManager->get(QuoteResource::class); - $this->quoteFactory = $objectManager->get(QuoteFactory::class); - $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ public function testSetNewBillingAddress() { - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_with_simple_product_without_address'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = <<<QUERY mutation { @@ -98,11 +86,13 @@ public function testSetNewBillingAddress() } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ public function testSetNewBillingAddressWithUseForShippingParameter() { - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_with_simple_product_without_address'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = <<<QUERY mutation { @@ -172,11 +162,13 @@ public function testSetNewBillingAddressWithUseForShippingParameter() } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php */ public function testSetBillingAddressToCustomerCart() { - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_1'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = <<<QUERY mutation { @@ -215,13 +207,17 @@ public function testSetBillingAddressToCustomerCart() } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * _security + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * * @expectedException \Exception * @expectedExceptionMessage The current customer isn't authorized. */ public function testSetBillingAddressFromAddressBook() { - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_with_simple_product_without_address'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = <<<QUERY mutation { @@ -263,6 +259,59 @@ public function testSetBillingAddressOnNonExistentCart() $this->graphQlQuery($query); } + /** + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * + * @dataProvider dataProviderSetWithoutRequiredParameters + * @param string $input + * @param string $message + * @throws \Exception + */ + public function testSetBillingAddressWithoutRequiredParameters(string $input, string $message) + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $input = str_replace('cart_id_value', $maskedQuoteId, $input); + + $query = <<<QUERY +mutation { + setBillingAddressOnCart( + input: { + {$input} + } + ) { + cart { + billing_address { + city + } + } + } +} +QUERY; + + $this->expectExceptionMessage($message); + $this->graphQlQuery($query); + } + + /** + * @return array + */ + public function dataProviderSetWithoutRequiredParameters(): array + { + return [ + 'missed_billing_address' => [ + 'cart_id: "cart_id_value"', + 'Field SetBillingAddressOnCartInput.billing_address of required type BillingAddressInput!' + . ' was not provided.', + ], + 'missed_cart_id' => [ + 'billing_address: {}', + 'Required parameter "cart_id" is missing' + ] + ]; + } + /** * Verify the all the whitelisted fields for a New Address Object * @@ -285,16 +334,4 @@ private function assertNewAddressFields(array $addressResponse, string $addressT $this->assertResponseFields($addressResponse, $assertionMap); } - - /** - * @param string $reversedQuoteId - * @return string - */ - private function getMaskedQuoteIdByReversedQuoteId(string $reversedQuoteId): string - { - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id'); - - return $this->quoteIdToMaskedId->execute((int)$quote->getId()); - } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetPaymentMethodOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetPaymentMethodOnCartTest.php index 182bbaf618505..8f37f00c3db7f 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetPaymentMethodOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetPaymentMethodOnCartTest.php @@ -7,10 +7,9 @@ namespace Magento\GraphQl\Quote\Guest; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\OfflinePayments\Model\Cashondelivery; use Magento\OfflinePayments\Model\Checkmo; -use Magento\Quote\Model\QuoteFactory; -use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; -use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; @@ -20,19 +19,9 @@ class SetPaymentMethodOnCartTest extends GraphQlAbstract { /** - * @var QuoteResource + * @var GetMaskedQuoteIdByReservedOrderId */ - private $quoteResource; - - /** - * @var QuoteFactory - */ - private $quoteFactory; - - /** - * @var QuoteIdToMaskedQuoteIdInterface - */ - private $quoteIdToMaskedId; + private $getMaskedQuoteIdByReservedOrderId; /** * @inheritdoc @@ -40,21 +29,21 @@ class SetPaymentMethodOnCartTest extends GraphQlAbstract protected function setUp() { $objectManager = Bootstrap::getObjectManager(); - $this->quoteResource = $objectManager->get(QuoteResource::class); - $this->quoteFactory = $objectManager->get(QuoteFactory::class); - $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_and_address.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php */ - public function testSetPaymentWithVirtualProduct() + public function testSetPaymentOnCartWithSimpleProduct() { $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE; - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_with_virtual_product'); - $this->unAssignCustomerFromQuote('test_order_with_virtual_product'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); - $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode); + $query = $this->getQuery($maskedQuoteId, $methodCode); $response = $this->graphQlQuery($query); self::assertArrayHasKey('setPaymentMethodOnCart', $response); @@ -64,15 +53,33 @@ public function testSetPaymentWithVirtualProduct() } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * + * @expectedException \Exception + * @expectedExceptionMessage The shipping address is missing. Set the address and try again. */ - public function testSetPaymentWithSimpleProduct() + public function testSetPaymentOnCartWithSimpleProductAndWithoutAddress() { $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE; - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_1'); - $this->unAssignCustomerFromQuote('test_order_1'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); - $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode); + $query = $this->getQuery($maskedQuoteId, $methodCode); + $this->graphQlQuery($query); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_virtual_product.php + */ + public function testSetPaymentOnCartWithVirtualProduct() + { + $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId, $methodCode); $response = $this->graphQlQuery($query); self::assertArrayHasKey('setPaymentMethodOnCart', $response); @@ -82,43 +89,56 @@ public function testSetPaymentWithSimpleProduct() } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * * @expectedException \Exception - * @expectedExceptionMessage The shipping address is missing. Set the address and try again. + * @expectedExceptionMessage The requested Payment Method is not available. */ - public function testSetPaymentWithSimpleProductWithoutAddress() + public function testSetNonExistentPaymentMethod() { - $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE; - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_with_simple_product_without_address'); + $methodCode = 'noway'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); - $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode); + $query = $this->getQuery($maskedQuoteId, $methodCode); $this->graphQlQuery($query); } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php * @expectedException \Exception - * @expectedExceptionMessage The requested Payment Method is not available. + * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id" */ - public function testSetNonExistingPaymentMethod() + public function testSetPaymentOnNonExistentCart() { - $methodCode = 'noway'; - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_1'); - $this->unAssignCustomerFromQuote('test_order_1'); - - $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode); + $maskedQuoteId = 'non_existent_masked_id'; + $query = <<<QUERY +{ + cart(cart_id: "$maskedQuoteId") { + items { + id + } + } +} +QUERY; $this->graphQlQuery($query); } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php */ public function testSetPaymentMethodToCustomerCart() { $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE; - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_1'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); - $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode); + $query = $this->getQuery($maskedQuoteId, $methodCode); $this->expectExceptionMessage( "The current user cannot perform operations on cart \"$maskedQuoteId\"" @@ -127,39 +147,78 @@ public function testSetPaymentMethodToCustomerCart() } /** - * @expectedException \Exception - * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id" + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * + * @param string $input + * @param string $message + * @dataProvider dataProviderSetPaymentMethodWithoutRequiredParameters + * @throws \Exception */ - public function testSetPaymentOnNonExistentCart() + public function testSetPaymentMethodWithoutRequiredParameters(string $input, string $message) { - $maskedQuoteId = 'non_existent_masked_id'; $query = <<<QUERY -{ - cart(cart_id: "$maskedQuoteId") { - items { - id +mutation { + setPaymentMethodOnCart( + input: { + {$input} + } + ) { + cart { + items { + qty + } } } } QUERY; + $this->expectExceptionMessage($message); $this->graphQlQuery($query); } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_payment_saved.php + * @return array + */ + public function dataProviderSetPaymentMethodWithoutRequiredParameters(): array + { + return [ + 'missed_cart_id' => [ + 'payment_method: {code: "' . Checkmo::PAYMENT_METHOD_CHECKMO_CODE . '"}', + 'Required parameter "cart_id" is missing.' + ], + 'missed_payment_method' => [ + 'cart_id: "test"', + 'Required parameter "code" for "payment_method" is missing.' + ], + 'missed_payment_method_code' => [ + 'cart_id: "test", payment_method: {code: ""}', + 'Required parameter "code" for "payment_method" is missing.' + ], + ]; + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_checkmo_payment_method.php */ public function testReSetPayment() { - /** @var \Magento\Quote\Model\Quote $quote */ - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_1_with_payment'); - $this->unAssignCustomerFromQuote('test_order_1_with_payment'); - $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE; - $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $methodCode = Cashondelivery::PAYMENT_METHOD_CASHONDELIVERY_CODE; + $query = $this->getQuery($maskedQuoteId, $methodCode); $response = $this->graphQlQuery($query); self::assertArrayHasKey('setPaymentMethodOnCart', $response); self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']); self::assertArrayHasKey('selected_payment_method', $response['setPaymentMethodOnCart']['cart']); + self::assertArrayHasKey('code', $response['setPaymentMethodOnCart']['cart']['selected_payment_method']); self::assertEquals($methodCode, $response['setPaymentMethodOnCart']['cart']['selected_payment_method']['code']); } @@ -168,20 +227,18 @@ public function testReSetPayment() * @param string $methodCode * @return string */ - private function prepareMutationQuery( + private function getQuery( string $maskedQuoteId, string $methodCode ) : string { return <<<QUERY mutation { - setPaymentMethodOnCart(input: - { - cart_id: "{$maskedQuoteId}", - payment_method: { - code: "{$methodCode}" - } - }) { - + setPaymentMethodOnCart(input: { + cart_id: "{$maskedQuoteId}", + payment_method: { + code: "{$methodCode}" + } + }) { cart { selected_payment_method { code @@ -191,31 +248,4 @@ private function prepareMutationQuery( } QUERY; } - - /** - * @param string $reversedQuoteId - * @param int $customerId - * @return string - */ - private function unAssignCustomerFromQuote( - string $reversedQuoteId - ): string { - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id'); - $quote->setCustomerId(0); - $this->quoteResource->save($quote); - return $this->quoteIdToMaskedId->execute((int)$quote->getId()); - } - - /** - * @param string $reversedQuoteId - * @return string - */ - private function getMaskedQuoteIdByReversedQuoteId(string $reversedQuoteId): string - { - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id'); - - return $this->quoteIdToMaskedId->execute((int)$quote->getId()); - } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php index 33aeb2b902a8f..e21d9ed64d491 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php @@ -7,14 +7,9 @@ namespace Magento\GraphQl\Quote\Guest; -use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\Multishipping\Helper\Data; -use Magento\Quote\Model\QuoteFactory; -use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; -use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; -use Magento\TestFramework\ObjectManager; /** * Test for set shipping addresses on cart mutation @@ -22,34 +17,24 @@ class SetShippingAddressOnCartTest extends GraphQlAbstract { /** - * @var QuoteResource + * @var GetMaskedQuoteIdByReservedOrderId */ - private $quoteResource; - - /** - * @var QuoteFactory - */ - private $quoteFactory; - - /** - * @var QuoteIdToMaskedQuoteIdInterface - */ - private $quoteIdToMaskedId; + private $getMaskedQuoteIdByReservedOrderId; protected function setUp() { $objectManager = Bootstrap::getObjectManager(); - $this->quoteResource = $objectManager->get(QuoteResource::class); - $this->quoteFactory = $objectManager->get(QuoteFactory::class); - $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ - public function testSetNewShippingAddress() + public function testSetNewShippingAddressOnCartWithSimpleProduct() { - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_with_simple_product_without_address'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = <<<QUERY mutation { @@ -103,13 +88,16 @@ public function testSetNewShippingAddress() } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_saved.php + * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_virtual_product.php + * * @expectedException \Exception * @expectedExceptionMessage The Cart includes virtual product(s) only, so a shipping address is not used. */ - public function testSetNewShippingAddressOnQuoteWithVirtualProducts() + public function testSetNewShippingAddressOnCartWithVirtualProduct() { - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_with_virtual_product_without_address'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = <<<QUERY mutation { @@ -146,13 +134,17 @@ public function testSetNewShippingAddressOnQuoteWithVirtualProducts() } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * _security + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * * @expectedException \Exception * @expectedExceptionMessage The current customer isn't authorized. */ public function testSetShippingAddressFromAddressBook() { - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_with_simple_product_without_address'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = <<<QUERY mutation { @@ -178,13 +170,17 @@ public function testSetShippingAddressFromAddressBook() } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * * @expectedException \Exception */ public function testSetShippingAddressToCustomerCart() { - $maskedQuoteId = $this->assignQuoteToCustomer('test_order_with_simple_product_without_address', 1); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = <<<QUERY mutation { @@ -209,12 +205,14 @@ public function testSetShippingAddressToCustomerCart() $this->expectExceptionMessage( "The current user cannot perform operations on cart \"$maskedQuoteId\"" ); - $this->graphQlQuery($query); } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * * @dataProvider dataProviderUpdateWithMissedRequiredParameters * @param string $input * @param string $message @@ -222,7 +220,7 @@ public function testSetShippingAddressToCustomerCart() */ public function testSetNewShippingAddressWithMissedRequiredParameters(string $input, string $message) { - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_with_simple_product_without_address'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = <<<QUERY mutation { @@ -251,7 +249,7 @@ public function testSetNewShippingAddressWithMissedRequiredParameters(string $in /** * @return array */ - public function dataProviderUpdateWithMissedRequiredParameters() + public function dataProviderUpdateWithMissedRequiredParameters(): array { return [ 'shipping_addresses' => [ @@ -266,13 +264,16 @@ public function dataProviderUpdateWithMissedRequiredParameters() } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * * @expectedException \Exception * @expectedExceptionMessage You cannot specify multiple shipping addresses. */ public function testSetMultipleNewShippingAddresses() { - $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_with_simple_product_without_address'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = <<<QUERY mutation { @@ -343,32 +344,4 @@ private function assertNewShippingAddressFields(array $shippingAddressResponse): $this->assertResponseFields($shippingAddressResponse, $assertionMap); } - - /** - * @param string $reversedQuoteId - * @return string - */ - private function getMaskedQuoteIdByReversedQuoteId(string $reversedQuoteId): string - { - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id'); - - return $this->quoteIdToMaskedId->execute((int)$quote->getId()); - } - - /** - * @param string $reversedQuoteId - * @param int $customerId - * @return string - */ - private function assignQuoteToCustomer( - string $reversedQuoteId = 'test_order_with_simple_product_without_address', - int $customerId = 1 - ): string { - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id'); - $quote->setCustomerId($customerId); - $this->quoteResource->save($quote); - return $this->quoteIdToMaskedId->execute((int)$quote->getId()); - } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php index f159cb6f6151e..bf08ce8adce6c 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php @@ -153,14 +153,14 @@ private function prepareMutationQuery( } /** - * @param string $reversedQuoteId + * @param string $reversedOrderId * @return string * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ - private function getMaskedQuoteIdByReversedQuoteId(string $reversedQuoteId): string + private function getMaskedQuoteIdByReservedOrderId(string $reversedOrderId): string { $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id'); + $this->quoteResource->load($quote, $reversedOrderId, 'reserved_order_id'); return $this->quoteIdToMaskedId->execute((int)$quote->getId()); } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/OfflineShipping/SetOfflineShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetOfflineShippingMethodsOnCartTest.php similarity index 96% rename from dev/tests/api-functional/testsuite/Magento/GraphQl/OfflineShipping/SetOfflineShippingMethodsOnCartTest.php rename to dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetOfflineShippingMethodsOnCartTest.php index 80acbbdb64230..cf32003340a66 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/OfflineShipping/SetOfflineShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetOfflineShippingMethodsOnCartTest.php @@ -5,7 +5,7 @@ */ declare(strict_types=1); -namespace Magento\GraphQl\OfflineShipping; +namespace Magento\GraphQl\Quote; use Magento\Integration\Api\CustomerTokenServiceInterface; use Magento\Quote\Model\QuoteFactory; @@ -53,7 +53,7 @@ protected function setUp() /** * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php - * @magentoApiDataFixture Magento/OfflineShipping/_files/enable_offline_shipping_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php * @magentoApiDataFixture Magento/OfflineShipping/_files/tablerates_weight.php * * @param string $carrierCode @@ -103,7 +103,7 @@ public function offlineShippingMethodDataProvider() /** * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php - * @magentoApiDataFixture Magento/OfflineShipping/_files/enable_offline_shipping_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php */ public function testSetShippingMethodTwiceInOneRequest() { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Ups/SetUpsShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Ups/SetUpsShippingMethodsOnCartTest.php new file mode 100644 index 0000000000000..463f2c4af101f --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Ups/SetUpsShippingMethodsOnCartTest.php @@ -0,0 +1,147 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Ups; + +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for setting "UPS" shipping method on cart + */ +class SetUpsShippingMethodsOnCartTest extends GraphQlAbstract +{ + /** + * Defines carrier code for "UPS" shipping method + */ + const CARRIER_CODE = 'ups'; + + /** + * Defines method code for the "Ground" UPS shipping + */ + const CARRIER_METHOD_CODE_GROUND = 'GND'; + + /** + * @var QuoteFactory + */ + private $quoteFactory; + + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @var QuoteResource + */ + private $quoteResource; + + /** + * @var QuoteIdToMaskedQuoteIdInterface + */ + private $quoteIdToMaskedId; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->quoteResource = $objectManager->get(QuoteResource::class); + $this->quoteFactory = $objectManager->get(QuoteFactory::class); + $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); + $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * @magentoApiDataFixture Magento/Ups/_files/enable_ups_shipping_method.php + */ + public function testSetUpsShippingMethod() + { + $quote = $this->quoteFactory->create(); + $this->quoteResource->load($quote, 'test_order_1', 'reserved_order_id'); + $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId()); + $shippingAddressId = (int)$quote->getShippingAddress()->getId(); + + $query = $this->getAddUpsShippingMethodQuery( + $maskedQuoteId, + $shippingAddressId, + self::CARRIER_CODE, + self::CARRIER_METHOD_CODE_GROUND + ); + + $response = $this->sendRequestWithToken($query); + $addressesInformation = $response['setShippingMethodsOnCart']['cart']['shipping_addresses']; + $expectedResult = [ + 'carrier_code' => self::CARRIER_CODE, + 'method_code' => self::CARRIER_METHOD_CODE_GROUND, + 'label' => 'United Parcel Service - Ground', + ]; + self::assertEquals($addressesInformation[0]['selected_shipping_method'], $expectedResult); + } + + /** + * Generates query for setting the specified shipping method on cart + * + * @param int $shippingAddressId + * @param string $maskedQuoteId + * @param string $carrierCode + * @param string $methodCode + * @return string + */ + private function getAddUpsShippingMethodQuery( + string $maskedQuoteId, + int $shippingAddressId, + string $carrierCode, + string $methodCode + ): string { + return <<<QUERY +mutation { + setShippingMethodsOnCart(input: { + cart_id: "$maskedQuoteId" + shipping_methods: [ + { + cart_address_id: $shippingAddressId + carrier_code: "$carrierCode" + method_code: "$methodCode" + } + ] + }) { + cart { + shipping_addresses { + selected_shipping_method { + carrier_code + method_code + label + } + } + } + } +} +QUERY; + } + + /** + * Sends a GraphQL request with using a bearer token + * + * @param string $query + * @return array + * @throws \Magento\Framework\Exception\AuthenticationException + */ + private function sendRequestWithToken(string $query): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken('customer@example.com', 'password'); + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + + return $this->graphQlQuery($query, [], '', $headerMap); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/WishlistTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/WishlistTest.php index d570fc09b7714..4aac5d9445934 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/WishlistTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/WishlistTest.php @@ -93,6 +93,36 @@ public function testGetCustomerWishlist(): void $this->assertEquals($wishlistItemProduct->getName(), $response['wishlist']['items'][0]['product']['name']); } + /** + * @expectedException \Exception + * @expectedExceptionMessage The current user cannot perform operations on wishlist + */ + public function testGetGuestWishlist() + { + $query = + <<<QUERY +{ + wishlist { + items_count + name + sharing_code + updated_at + items { + id + qty + description + added_at + product { + sku + name + } + } + } +} +QUERY; + $this->graphQlQuery($query); + } + /** * @param string $email * @param string $password diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Category/Tree.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Category/Tree.php index 2035e7e83200f..30a323eebb736 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Category/Tree.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Category/Tree.php @@ -187,6 +187,8 @@ public function assignCategory($parentCategoryName, $childCategoryName) */ public function expandAllCategories() { + $this->getTemplateBlock()->waitLoader(); $this->_rootElement->find($this->expandAll)->click(); + $this->getTemplateBlock()->waitLoader(); } } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/ProductList/TopToolbar.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/ProductList/TopToolbar.php index 090042140a548..48769126d88ea 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/ProductList/TopToolbar.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/ProductList/TopToolbar.php @@ -48,7 +48,7 @@ public function getSelectSortType() public function getSortType() { $content = $this->_rootElement->find($this->sorter)->getText(); - return explode("\n", $content); + return array_values(array_filter(array_map('trim', explode("\n", $content)))); } /** diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/CreateCategoryEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/CreateCategoryEntityTest.xml index c6a66beac7c79..ea6808ee2a7f5 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/CreateCategoryEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/CreateCategoryEntityTest.xml @@ -96,6 +96,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertCategoryForAssignedProducts" /> </variation> <variation name="CreateCategoryEntityTestVariation5_Anchor_MostOfFields"> + <data name="tag" xsi:type="string">test_type:acceptance_test</data> <data name="description" xsi:type="string">Create anchor subcategory with all fields</data> <data name="addCategory" xsi:type="string">addSubcategory</data> <data name="category/data/parent_id/dataset" xsi:type="string">default_category</data> diff --git a/dev/tests/functional/tests/app/Magento/CheckoutAgreements/Test/Page/MultishippingCheckoutOverview.xml b/dev/tests/functional/tests/app/Magento/CheckoutAgreements/Test/Page/MultishippingCheckoutOverview.xml index d304d305a7265..a266b09278ddb 100644 --- a/dev/tests/functional/tests/app/Magento/CheckoutAgreements/Test/Page/MultishippingCheckoutOverview.xml +++ b/dev/tests/functional/tests/app/Magento/CheckoutAgreements/Test/Page/MultishippingCheckoutOverview.xml @@ -7,6 +7,6 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/pages.xsd"> <page name="MultishippingCheckoutOverview" mca="multishipping/checkout/overview" module="Magento_Checkout"> - <block name="agreementReview" class="Magento\CheckoutAgreements\Test\Block\Multishipping\MultishippingAgreementReview" locator="#checkout-agreements" strategy="css selector"/> + <block name="agreementReview" class="Magento\CheckoutAgreements\Test\Block\Multishipping\MultishippingAgreementReview" locator=".checkout-agreements" strategy="css selector"/> </page> </config> diff --git a/dev/tests/functional/tests/app/Magento/Review/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/Review/Test/TestCase/NavigateMenuTest.xml index 8445a21604cdd..4d96ebd2edbe3 100644 --- a/dev/tests/functional/tests/app/Magento/Review/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/Review/Test/TestCase/NavigateMenuTest.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest"> <variation name="NavigateMenuTest70"> - <data name="menuItem" xsi:type="string">Marketing > Reviews</data> + <data name="menuItem" xsi:type="string">Marketing > All Reviews</data> <data name="pageTitle" xsi:type="string">Reviews</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> diff --git a/dev/tests/integration/testsuite/Magento/Backend/Block/Widget/Grid/MassactionTest.php b/dev/tests/integration/testsuite/Magento/Backend/Block/Widget/Grid/MassactionTest.php index 8aeee9cf12494..e11c5ce5d9cf3 100644 --- a/dev/tests/integration/testsuite/Magento/Backend/Block/Widget/Grid/MassactionTest.php +++ b/dev/tests/integration/testsuite/Magento/Backend/Block/Widget/Grid/MassactionTest.php @@ -87,41 +87,6 @@ public function testMassactionDefaultValues() $this->assertFalse($blockEmpty->isAvailable()); } - public function testGetJavaScript() - { - $this->loadLayout(); - - $javascript = $this->_block->getJavaScript(); - - $expectedItemFirst = '#"option_id1":{"label":"Option One",' . - '"url":"http:\\\/\\\/localhost\\\/index\.php\\\/(?:key\\\/([\w\d]+)\\\/)?",' . - '"complete":"Test","id":"option_id1"}#'; - $this->assertRegExp($expectedItemFirst, $javascript); - - $expectedItemSecond = '#"option_id2":{"label":"Option Two",' . - '"url":"http:\\\/\\\/localhost\\\/index\.php\\\/(?:key\\\/([\w\d]+)\\\/)?",' . - '"confirm":"Are you sure\?","id":"option_id2"}#'; - $this->assertRegExp($expectedItemSecond, $javascript); - } - - public function testGetJavaScriptWithAddedItem() - { - $this->loadLayout(); - - $input = [ - 'id' => 'option_id3', - 'label' => 'Option Three', - 'url' => '*/*/option3', - 'block_name' => 'admin.test.grid.massaction.option3', - ]; - $expected = '#"option_id3":{"id":"option_id3","label":"Option Three",' . - '"url":"http:\\\/\\\/localhost\\\/index\.php\\\/(?:key\\\/([\w\d]+)\\\/)?",' . - '"block_name":"admin.test.grid.massaction.option3"}#'; - - $this->_block->addItem($input['id'], $input); - $this->assertRegExp($expected, $this->_block->getJavaScript()); - } - /** * @param string $mageMode * @param int $expectedCount @@ -213,21 +178,4 @@ public function getItemsDataProvider() ] ]; } - - public function testGridContainsMassactionColumn() - { - $this->loadLayout(); - $this->_layout->getBlock('admin.test.grid')->toHtml(); - - $gridMassactionColumn = $this->_layout->getBlock('admin.test.grid') - ->getColumnSet() - ->getChildBlock('massaction'); - - $this->assertNotNull($gridMassactionColumn, 'Massaction column does not exist in the grid column set'); - $this->assertInstanceOf( - \Magento\Backend\Block\Widget\Grid\Column::class, - $gridMassactionColumn, - 'Massaction column is not an instance of \Magento\Backend\Block\Widget\Column' - ); - } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php index 7954e2c36227f..476f01eb277df 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php @@ -12,6 +12,11 @@ class ProductTest extends TestCase { + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + /** * @var Product */ @@ -29,7 +34,8 @@ protected function setUp() { $this->objectManager = Bootstrap::getObjectManager(); - $this->model = $this->objectManager->get(Product::class); + $this->productRepository = $this->objectManager->create(ProductRepositoryInterface::class); + $this->model = $this->objectManager->create(Product::class); } /** @@ -42,11 +48,29 @@ public function testGetAttributeRawValue() $sku = 'simple'; $attribute = 'name'; - /** @var ProductRepositoryInterface $productRepository */ - $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); - $product = $productRepository->get($sku); - + $product = $this->productRepository->get($sku); $actual = $this->model->getAttributeRawValue($product->getId(), $attribute, null); self::assertEquals($product->getName(), $actual); } + + /** + * @magentoAppArea adminhtml + * @magentoDataFixture Magento/Catalog/_files/product_special_price.php + * @magentoAppIsolation enabled + * @magentoConfigFixture default_store catalog/price/scope 1 + */ + public function testUpdateStoreSpecificSpecialPrice() + { + /** @var \Magento\Catalog\Model\Product $product */ + $product = $this->productRepository->get('simple', true, 1); + $this->assertEquals(5.99, $product->getSpecialPrice()); + + $product->setSpecialPrice(''); + $this->model->save($product); + $product = $this->productRepository->get('simple', false, 1, true); + $this->assertEmpty($product->getSpecialPrice()); + + $product = $this->productRepository->get('simple', false, 0, true); + $this->assertEquals(5.99, $product->getSpecialPrice()); + } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/text_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_text_attribute_rollback.php similarity index 84% rename from dev/tests/integration/testsuite/Magento/Catalog/_files/text_attribute_rollback.php rename to dev/tests/integration/testsuite/Magento/Catalog/_files/product_text_attribute_rollback.php index cbc0476efd1b5..a9ab0e11312b2 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/text_attribute_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_text_attribute_rollback.php @@ -3,13 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + /* Delete attribute with text_attribute code */ -$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get('Magento\Framework\Registry'); +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); $registry->unregister('isSecureArea'); $registry->register('isSecureArea', true); + /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ $attribute = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - 'Magento\Catalog\Model\ResourceModel\Eav\Attribute' + \Magento\Catalog\Model\ResourceModel\Eav\Attribute::class ); $attribute->load('text_attribute', 'attribute_code'); $attribute->delete(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data_rollback.php index 168073bc6ab74..c57c7c3fd6a92 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data_rollback.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data_rollback.php @@ -5,10 +5,10 @@ */ /** Delete all products */ -require dirname(dirname(__DIR__)) . '/Catalog/_files/products_with_multiselect_attribute_rollback.php'; +include dirname(dirname(__DIR__)) . '/Catalog/_files/products_with_multiselect_attribute_rollback.php'; /** Delete text attribute */ -require dirname(dirname(__DIR__)) . '/Catalog/_files/text_attribute_rollback.php'; +include dirname(dirname(__DIR__)) . '/Catalog/_files/product_text_attribute_rollback.php'; -require dirname(dirname(__DIR__)) . '/Store/_files/second_store_rollback.php'; +include dirname(dirname(__DIR__)) . '/Store/_files/second_store_rollback.php'; -require dirname(dirname(__DIR__)) . '/Catalog/_files/category_rollback.php'; +include dirname(dirname(__DIR__)) . '/Catalog/_files/category_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data_special_chars_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data_special_chars_rollback.php index 168073bc6ab74..c57c7c3fd6a92 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data_special_chars_rollback.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data_special_chars_rollback.php @@ -5,10 +5,10 @@ */ /** Delete all products */ -require dirname(dirname(__DIR__)) . '/Catalog/_files/products_with_multiselect_attribute_rollback.php'; +include dirname(dirname(__DIR__)) . '/Catalog/_files/products_with_multiselect_attribute_rollback.php'; /** Delete text attribute */ -require dirname(dirname(__DIR__)) . '/Catalog/_files/text_attribute_rollback.php'; +include dirname(dirname(__DIR__)) . '/Catalog/_files/product_text_attribute_rollback.php'; -require dirname(dirname(__DIR__)) . '/Store/_files/second_store_rollback.php'; +include dirname(dirname(__DIR__)) . '/Store/_files/second_store_rollback.php'; -require dirname(dirname(__DIR__)) . '/Catalog/_files/category_rollback.php'; +include dirname(dirname(__DIR__)) . '/Catalog/_files/category_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/ProcessUrlRewriteOnChangeVisibilityObserverTest.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/ProcessUrlRewriteOnChangeVisibilityObserverTest.php new file mode 100644 index 0000000000000..d3f0e9fa2a5ab --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/ProcessUrlRewriteOnChangeVisibilityObserverTest.php @@ -0,0 +1,195 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogUrlRewrite\Observer; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Framework\Event\ManagerInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\UrlRewrite\Model\Exception\UrlAlreadyExistsException; +use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; + +/** + * @magentoAppArea adminhtml + * @magentoDbIsolation disabled + */ +class ProcessUrlRewriteOnChangeVisibilityObserverTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var ManagerInterface + */ + private $eventManager; + + /** + * Set up + */ + protected function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->productRepository = $this->objectManager->create(ProductRepositoryInterface::class); + $this->eventManager = $this->objectManager->create(ManagerInterface::class); + } + + /** + * @magentoDataFixture Magento/CatalogUrlRewrite/_files/product_rewrite_multistore.php + * @magentoAppIsolation enabled + */ + public function testMakeProductInvisibleViaMassAction() + { + /** @var \Magento\Catalog\Model\Product $product*/ + $product = $this->productRepository->get('product1'); + + /** @var StoreManagerInterface $storeManager */ + $storeManager = $this->objectManager->get(StoreManagerInterface::class); + $storeManager->setCurrentStore(0); + + $testStore = $storeManager->getStore('test'); + $productFilter = [ + UrlRewrite::ENTITY_TYPE => 'product', + ]; + + $expected = [ + [ + 'request_path' => "product-1.html", + 'target_path' => "catalog/product/view/id/" . $product->getId(), + 'is_auto_generated' => 1, + 'redirect_type' => 0, + 'store_id' => '1', + ], + [ + 'request_path' => "product-1.html", + 'target_path' => "catalog/product/view/id/" . $product->getId(), + 'is_auto_generated' => 1, + 'redirect_type' => 0, + 'store_id' => $testStore->getId(), + ] + ]; + + $actual = $this->getActualResults($productFilter); + foreach ($expected as $row) { + $this->assertContains($row, $actual); + } + + $this->eventManager->dispatch( + 'catalog_product_attribute_update_before', + [ + 'attributes_data' => [ ProductInterface::VISIBILITY => Visibility::VISIBILITY_NOT_VISIBLE ], + 'product_ids' => [$product->getId()] + ] + ); + + $actual = $this->getActualResults($productFilter); + $this->assertCount(0, $actual); + } + + /** + * @magentoDataFixture Magento/CatalogUrlRewrite/_files/product_invisible_multistore.php + * @magentoAppIsolation enabled + */ + public function testMakeProductVisibleViaMassAction() + { + /** @var \Magento\Catalog\Model\Product $product*/ + $product = $this->productRepository->get('product1'); + + /** @var StoreManagerInterface $storeManager */ + $storeManager = $this->objectManager->get(StoreManagerInterface::class); + $storeManager->setCurrentStore(0); + + $testStore = $storeManager->getStore('test'); + $productFilter = [ + UrlRewrite::ENTITY_TYPE => 'product', + ]; + + $actual = $this->getActualResults($productFilter); + $this->assertCount(0, $actual); + + $this->eventManager->dispatch( + 'catalog_product_attribute_update_before', + [ + 'attributes_data' => [ ProductInterface::VISIBILITY => Visibility::VISIBILITY_BOTH ], + 'product_ids' => [$product->getId()] + ] + ); + + $expected = [ + [ + 'request_path' => "product-1.html", + 'target_path' => "catalog/product/view/id/" . $product->getId(), + 'is_auto_generated' => 1, + 'redirect_type' => 0, + 'store_id' => '1', + ], + [ + 'request_path' => "product-1.html", + 'target_path' => "catalog/product/view/id/" . $product->getId(), + 'is_auto_generated' => 1, + 'redirect_type' => 0, + 'store_id' => $testStore->getId(), + ] + ]; + + $actual = $this->getActualResults($productFilter); + foreach ($expected as $row) { + $this->assertContains($row, $actual); + } + } + + /** + * @magentoDataFixture Magento/CatalogUrlRewrite/_files/products_invisible.php + * @magentoAppIsolation enabled + */ + public function testErrorOnDuplicatedUrlKey() + { + $skus = ['product1', 'product2']; + foreach ($skus as $sku) { + /** @var \Magento\Catalog\Model\Product $product */ + $productIds[] = $this->productRepository->get($sku)->getId(); + } + $this->expectException(UrlAlreadyExistsException::class); + $this->expectExceptionMessage('Can not change the visibility of the product with SKU equals "product2". ' + . 'URL key "product-1" for specified store already exists.'); + + $this->eventManager->dispatch( + 'catalog_product_attribute_update_before', + [ + 'attributes_data' => [ ProductInterface::VISIBILITY => Visibility::VISIBILITY_BOTH ], + 'product_ids' => $productIds + ] + ); + } + + /** + * @param array $filter + * @return array + */ + private function getActualResults(array $filter) + { + /** @var \Magento\UrlRewrite\Model\UrlFinderInterface $urlFinder */ + $urlFinder = $this->objectManager->get(\Magento\UrlRewrite\Model\UrlFinderInterface::class); + $actualResults = []; + foreach ($urlFinder->findAllByData($filter) as $url) { + $actualResults[] = [ + 'request_path' => $url->getRequestPath(), + 'target_path' => $url->getTargetPath(), + 'is_auto_generated' => (int)$url->getIsAutogenerated(), + 'redirect_type' => $url->getRedirectType(), + 'store_id' => $url->getStoreId() + ]; + } + return $actualResults; + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_invisible_multistore.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_invisible_multistore.php new file mode 100644 index 0000000000000..d50b29383b1ef --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_invisible_multistore.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Setup\CategorySetup; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; + +\Magento\TestFramework\Helper\Bootstrap::getInstance() + ->loadArea(\Magento\Backend\App\Area\FrontNameResolver::AREA_CODE); + +require __DIR__ . '/../../Store/_files/store.php'; + +/** @var $installer CategorySetup */ +$objectManager = Bootstrap::getObjectManager(); +$installer = $objectManager->create(CategorySetup::class); +$storeManager = $objectManager->get(StoreManagerInterface::class); +$storeManager->setCurrentStore(0); + +/** @var $product \Magento\Catalog\Model\Product */ +$product = $objectManager->create(\Magento\Catalog\Model\Product::class); +$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setAttributeSetId($installer->getAttributeSetId('catalog_product', 'Default')) + ->setStoreId(0) + ->setWebsiteIds([1]) + ->setName('Product1') + ->setSku('product1') + ->setPrice(10) + ->setWeight(18) + ->setStockData(['use_config_manage_stock' => 0]) + ->setUrlKey('product-1') + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_NOT_VISIBLE) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED); + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_invisible_multistore_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_invisible_multistore_rollback.php new file mode 100644 index 0000000000000..bcf399cb5e552 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_invisible_multistore_rollback.php @@ -0,0 +1,33 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +use Magento\Framework\Exception\NoSuchEntityException; + +\Magento\TestFramework\Helper\Bootstrap::getInstance()->getInstance()->reinitialize(); + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); +try { + $product = $productRepository->get('product1', true); + if ($product->getId()) { + $productRepository->delete($product); + } +} catch (NoSuchEntityException $e) { +} +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); + +require __DIR__ . '/../../Store/_files/store_rollback.php'; +require __DIR__ . '/../../Store/_files/second_store_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/products_invisible.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/products_invisible.php new file mode 100644 index 0000000000000..c72d9c8284db3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/products_invisible.php @@ -0,0 +1,38 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Setup\CategorySetup; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; + +\Magento\TestFramework\Helper\Bootstrap::getInstance() + ->loadArea(\Magento\Backend\App\Area\FrontNameResolver::AREA_CODE); + +/** @var $installer CategorySetup */ +$objectManager = Bootstrap::getObjectManager(); +$installer = $objectManager->create(CategorySetup::class); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); + +$skus = ['product1', 'product2']; +foreach ($skus as $sku) { + /** @var $product \Magento\Catalog\Model\Product */ + $product = $objectManager->create(\Magento\Catalog\Model\Product::class); + $product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setAttributeSetId($installer->getAttributeSetId('catalog_product', 'Default')) + ->setStoreId(0) + ->setWebsiteIds([1]) + ->setName('Product1') + ->setSku($sku) + ->setPrice(10) + ->setWeight(18) + ->setStockData(['use_config_manage_stock' => 0]) + ->setUrlKey('product-1') + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_NOT_VISIBLE) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED); + $productRepository->save($product); +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/products_invisible_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/products_invisible_rollback.php new file mode 100644 index 0000000000000..d3d17542aa6ab --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/products_invisible_rollback.php @@ -0,0 +1,37 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +use Magento\Framework\Exception\NoSuchEntityException; + +\Magento\TestFramework\Helper\Bootstrap::getInstance()->getInstance()->reinitialize(); + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); + +$skus = ['product1', 'product2']; +foreach ($skus as $sku) { + try { + $product = $productRepository->get($sku, true); + if ($product->getId()) { + $productRepository->delete($product); + } + } catch (NoSuchEntityException $e) { + } +} +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); + +require __DIR__ . '/../../Store/_files/store_rollback.php'; +require __DIR__ . '/../../Store/_files/second_store_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/SearchAdapter/AdapterTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/SearchAdapter/AdapterTest.php index 6bb7d6ac568fc..a3da32e0d6c40 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/SearchAdapter/AdapterTest.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/SearchAdapter/AdapterTest.php @@ -367,4 +367,18 @@ public function dateDataProvider() [['from' => '2000-02-01T00:00:00Z', 'to' => ''], 0], ]; } + + public function filterByAttributeValuesDataProvider() + { + $variations = parent::filterByAttributeValuesDataProvider(); + + $variations['quick search by date'] = [ + 'quick_search_container', + [ + 'search_term' => '2000-10-30', + ], + ]; + + return $variations; + } } diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attributes.php b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attributes.php index b09af48b5f943..f4f3337a253c0 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attributes.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attributes.php @@ -20,6 +20,10 @@ CategorySetup::class, ['resourceName' => 'catalog_setup'] ); +$productEntityTypeId = $installer->getEntityTypeId( + \Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE +); + $selectOptions = []; $selectAttributes = []; foreach (range(1, 2) as $index) { @@ -30,7 +34,7 @@ $selectAttribute->setData( [ 'attribute_code' => 'select_attribute_' . $index, - 'entity_type_id' => $installer->getEntityTypeId('catalog_product'), + 'entity_type_id' => $productEntityTypeId, 'is_global' => 1, 'is_user_defined' => 1, 'frontend_input' => 'select', @@ -56,7 +60,8 @@ ); $selectAttribute->save(); /* Assign attribute to attribute set */ - $installer->addAttributeToGroup('catalog_product', 'Default', 'General', $selectAttribute->getId()); + $installer->addAttributeToGroup($productEntityTypeId, 'Default', 'General', $selectAttribute->getId()); + /** @var $selectOptions Collection */ $selectOption = Bootstrap::getObjectManager()->create( Collection::class @@ -65,6 +70,26 @@ $selectAttributes[$index] = $selectAttribute; $selectOptions[$index] = $selectOption; } + +$dateAttribute = Bootstrap::getObjectManager()->create(Attribute::class); +$dateAttribute->setData( + [ + 'attribute_code' => 'date_attribute', + 'entity_type_id' => $productEntityTypeId, + 'is_global' => 1, + 'is_filterable' => 1, + 'backend_type' => 'datetime', + 'frontend_input' => 'date', + 'frontend_label' => 'Test Date', + 'is_searchable' => 1, + 'is_filterable_in_search' => 1, + ] +); +$dateAttribute->save(); +/* Assign attribute to attribute set */ +$installer->addAttributeToGroup($productEntityTypeId, 'Default', 'General', $dateAttribute->getId()); + +$productAttributeSetId = $installer->getAttributeSetId($productEntityTypeId, 'Default'); /* Create simple products per each first attribute option */ foreach ($selectOptions[1] as $option) { /** @var $product Product */ @@ -74,7 +99,7 @@ $product->setTypeId( Type::TYPE_SIMPLE )->setAttributeSetId( - $installer->getAttributeSetId('catalog_product', 'Default') + $productAttributeSetId )->setWebsiteIds( [1] )->setName( @@ -92,6 +117,7 @@ )->setStockData( ['use_config_manage_stock' => 1, 'qty' => 5, 'is_in_stock' => 1] )->save(); + Bootstrap::getObjectManager()->get( Action::class )->updateAttributes( @@ -99,6 +125,7 @@ [ $selectAttributes[1]->getAttributeCode() => $option->getId(), $selectAttributes[2]->getAttributeCode() => $selectOptions[2]->getLastItem()->getId(), + $dateAttribute->getAttributeCode() => '10/30/2000', ], $product->getStoreId() ); diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attributes_rollback.php b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attributes_rollback.php index 18a5372d06d98..fd413726b2637 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attributes_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attributes_rollback.php @@ -13,6 +13,7 @@ $registry = Bootstrap::getObjectManager()->get(Registry::class); $registry->unregister('isSecureArea'); $registry->register('isSecureArea', true); + /** @var $productCollection \Magento\Catalog\Model\ResourceModel\Product\Collection */ $productCollection = Bootstrap::getObjectManager() ->create(Product::class) @@ -20,17 +21,26 @@ foreach ($productCollection as $product) { $product->delete(); } + /** @var $attribute Attribute */ $attribute = Bootstrap::getObjectManager()->create( Attribute::class ); /** @var $installer CategorySetup */ $installer = Bootstrap::getObjectManager()->create(CategorySetup::class); +$productEntityTypeId = $installer->getEntityTypeId( + \Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE +); foreach (range(1, 2) as $index) { - $attribute->loadByCode($installer->getEntityTypeId('catalog_product'), 'select_attribute_' . $index); + $attribute->loadByCode($productEntityTypeId, 'select_attribute_' . $index); if ($attribute->getId()) { $attribute->delete(); } } +$attribute->loadByCode($productEntityTypeId, 'date_attribute'); +if ($attribute->getId()) { + $attribute->delete(); +} + $registry->unregister('isSecureArea'); $registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_simple_product.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_simple_product.php new file mode 100644 index 0000000000000..d23381e33d436 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_simple_product.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); +/** @var QuoteFactory $quoteFactory */ +$quoteFactory = Bootstrap::getObjectManager()->get(QuoteFactory::class); +/** @var QuoteResource $quoteResource */ +$quoteResource = Bootstrap::getObjectManager()->get(QuoteResource::class); +/** @var CartRepositoryInterface $cartRepository */ +$cartRepository = Bootstrap::getObjectManager()->get(CartRepositoryInterface::class); + +$product = $productRepository->get('simple'); + +$quote = $quoteFactory->create(); +$quoteResource->load($quote, 'test_quote', 'reserved_order_id'); +$quote->addProduct($product, 2); +$cartRepository->save($quote); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_virtual_product.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_virtual_product.php new file mode 100644 index 0000000000000..f597d0bffa7ce --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_virtual_product.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); +/** @var QuoteFactory $quoteFactory */ +$quoteFactory = Bootstrap::getObjectManager()->get(QuoteFactory::class); +/** @var QuoteResource $quoteResource */ +$quoteResource = Bootstrap::getObjectManager()->get(QuoteResource::class); +/** @var CartRepositoryInterface $cartRepository */ +$cartRepository = Bootstrap::getObjectManager()->get(CartRepositoryInterface::class); + +$product = $productRepository->get('virtual-product'); + +$quote = $quoteFactory->create(); +$quoteResource->load($quote, 'test_quote', 'reserved_order_id'); +$quote->addProduct($product, 2); +$cartRepository->save($quote); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/customer/create_empty_cart.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/customer/create_empty_cart.php new file mode 100644 index 0000000000000..86174a7753f4e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/customer/create_empty_cart.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Quote\Api\CartManagementInterface; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Model\QuoteIdMask; +use Magento\Quote\Model\QuoteIdMaskFactory; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var CartManagementInterface $cartManagement */ +$cartManagement = Bootstrap::getObjectManager()->get(CartManagementInterface::class); +/** @var CartRepositoryInterface $cartRepository */ +$cartRepository = Bootstrap::getObjectManager()->get(CartRepositoryInterface::class); +/** @var QuoteIdMaskFactory $quoteIdMaskFactory */ +$quoteIdMaskFactory = Bootstrap::getObjectManager()->get(QuoteIdMaskFactory::class); + +$cartId = $cartManagement->createEmptyCartForCustomer(1); +$cart = $cartRepository->get($cartId); +$cart->setReservedOrderId('test_quote'); +$cartRepository->save($cart); + +/** @var QuoteIdMask $quoteIdMask */ +$quoteIdMask = $quoteIdMaskFactory->create(); +$quoteIdMask->setQuoteId($cartId) + ->save(); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/customer/create_empty_cart_rollback.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/customer/create_empty_cart_rollback.php new file mode 100644 index 0000000000000..0d5d5f067e35a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/customer/create_empty_cart_rollback.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\Quote\Model\QuoteIdMask; +use Magento\Quote\Model\QuoteIdMaskFactory; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var QuoteFactory $quoteFactory */ +$quoteFactory = Bootstrap::getObjectManager()->get(QuoteFactory::class); +/** @var QuoteResource $quoteResource */ +$quoteResource = Bootstrap::getObjectManager()->get(QuoteResource::class); +/** @var QuoteIdMaskFactory $quoteIdMaskFactory */ +$quoteIdMaskFactory = Bootstrap::getObjectManager()->get(QuoteIdMaskFactory::class); + +$quote = $quoteFactory->create(); +$quoteResource->load($quote, 'test_quote', 'reserved_order_id'); +$quoteResource->delete($quote); + +/** @var QuoteIdMask $quoteIdMask */ +$quoteIdMask = $quoteIdMaskFactory->create(); +$quoteIdMask->setQuoteId($quote->getId()) + ->delete(); diff --git a/dev/tests/integration/testsuite/Magento/Payment/_files/disable_all_active_payment_methods.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/disable_all_active_payment_methods.php similarity index 93% rename from dev/tests/integration/testsuite/Magento/Payment/_files/disable_all_active_payment_methods.php rename to dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/disable_all_active_payment_methods.php index ac811e67c580a..8e6d4b8f74b86 100644 --- a/dev/tests/integration/testsuite/Magento/Payment/_files/disable_all_active_payment_methods.php +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/disable_all_active_payment_methods.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +// TODO: Should be removed in scope of https://github.com/magento/graphql-ce/issues/167 declare(strict_types=1); use Magento\Config\Model\Config; diff --git a/dev/tests/integration/testsuite/Magento/Payment/_files/disable_all_active_payment_methods_rollback.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/disable_all_active_payment_methods_rollback.php similarity index 92% rename from dev/tests/integration/testsuite/Magento/Payment/_files/disable_all_active_payment_methods_rollback.php rename to dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/disable_all_active_payment_methods_rollback.php index 4a56888058e4d..092826d1fd3f7 100644 --- a/dev/tests/integration/testsuite/Magento/Payment/_files/disable_all_active_payment_methods_rollback.php +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/disable_all_active_payment_methods_rollback.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +// TODO: Should be removed in scope of https://github.com/magento/graphql-ce/issues/167 declare(strict_types=1); use Magento\Framework\App\Config\ScopeConfigInterface; diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php new file mode 100644 index 0000000000000..9c15589ba82e5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +// TODO: Should be removed in scope of https://github.com/magento/graphql-ce/issues/167 +declare(strict_types=1); + +use Magento\Framework\App\Config\Storage\Writer; +use Magento\Framework\App\Config\Storage\WriterInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\App\Config\ScopeConfigInterface; + +$objectManager = Bootstrap::getObjectManager(); +/** @var Writer $configWriter */ +$configWriter = $objectManager->get(WriterInterface::class); + +$configWriter->save('payment/banktransfer/active', 1); +$configWriter->save('payment/cashondelivery/active', 1); +$configWriter->save('payment/checkmo/active', 1); +$configWriter->save('payment/purchaseorder/active', 1); + +$scopeConfig = $objectManager->get(ScopeConfigInterface::class); +$scopeConfig->clean(); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/enable_offline_payment_methods_rollback.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/enable_offline_payment_methods_rollback.php new file mode 100644 index 0000000000000..61b7ed9737ff9 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/enable_offline_payment_methods_rollback.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +// TODO: Should be removed in scope of https://github.com/magento/graphql-ce/issues/167 +declare(strict_types=1); + +use Magento\Framework\App\Config\Storage\Writer; +use Magento\Framework\App\Config\Storage\WriterInterface; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var Writer $configWriter */ +$configWriter = $objectManager->create(WriterInterface::class); + +$configWriter->delete('payment/banktransfer/active'); +$configWriter->delete('payment/cashondelivery/active'); +$configWriter->delete('payment/checkmo/active'); +$configWriter->delete('payment/purchaseorder/active'); diff --git a/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/enable_offline_shipping_methods.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php similarity index 89% rename from dev/tests/integration/testsuite/Magento/OfflineShipping/_files/enable_offline_shipping_methods.php rename to dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php index 08694bf8c7d0b..ebc41da9b1b3c 100644 --- a/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/enable_offline_shipping_methods.php +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +// TODO: Should be removed in scope of https://github.com/magento/graphql-ce/issues/167 declare(strict_types=1); use Magento\Framework\App\Config\Storage\Writer; diff --git a/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/enable_offline_shipping_methods_rollback.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/enable_offline_shipping_methods_rollback.php similarity index 86% rename from dev/tests/integration/testsuite/Magento/OfflineShipping/_files/enable_offline_shipping_methods_rollback.php rename to dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/enable_offline_shipping_methods_rollback.php index 31a16f42adfce..384ffbdf51f3c 100644 --- a/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/enable_offline_shipping_methods_rollback.php +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/enable_offline_shipping_methods_rollback.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +// TODO: Should be removed in scope of https://github.com/magento/graphql-ce/issues/167 declare(strict_types=1); use Magento\Framework\App\Config\Storage\Writer; diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/guest/create_empty_cart.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/guest/create_empty_cart.php new file mode 100644 index 0000000000000..6a9ed898c3161 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/guest/create_empty_cart.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Api\GuestCartManagementInterface; +use Magento\Quote\Model\MaskedQuoteIdToQuoteIdInterface; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var GuestCartManagementInterface $guestCartManagement */ +$guestCartManagement = Bootstrap::getObjectManager()->get(GuestCartManagementInterface::class); +/** @var CartRepositoryInterface $cartRepository */ +$cartRepository = Bootstrap::getObjectManager()->get(CartRepositoryInterface::class); +/** @var MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId */ +$maskedQuoteIdToQuoteId = Bootstrap::getObjectManager()->get(MaskedQuoteIdToQuoteIdInterface::class); + +$cartHash = $guestCartManagement->createEmptyCart(); +$cartId = $maskedQuoteIdToQuoteId->execute($cartHash); +$cart = $cartRepository->get($cartId); +$cart->setReservedOrderId('test_quote'); +$cartRepository->save($cart); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/guest/create_empty_cart_rollback.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/guest/create_empty_cart_rollback.php new file mode 100644 index 0000000000000..0d5d5f067e35a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/guest/create_empty_cart_rollback.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\Quote\Model\QuoteIdMask; +use Magento\Quote\Model\QuoteIdMaskFactory; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var QuoteFactory $quoteFactory */ +$quoteFactory = Bootstrap::getObjectManager()->get(QuoteFactory::class); +/** @var QuoteResource $quoteResource */ +$quoteResource = Bootstrap::getObjectManager()->get(QuoteResource::class); +/** @var QuoteIdMaskFactory $quoteIdMaskFactory */ +$quoteIdMaskFactory = Bootstrap::getObjectManager()->get(QuoteIdMaskFactory::class); + +$quote = $quoteFactory->create(); +$quoteResource->load($quote, 'test_quote', 'reserved_order_id'); +$quoteResource->delete($quote); + +/** @var QuoteIdMask $quoteIdMask */ +$quoteIdMask = $quoteIdMaskFactory->create(); +$quoteIdMask->setQuoteId($quote->getId()) + ->delete(); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/make_cart_inactive.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/make_cart_inactive.php new file mode 100644 index 0000000000000..b5704f82879b2 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/make_cart_inactive.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var QuoteFactory $quoteFactory */ +$quoteFactory = Bootstrap::getObjectManager()->get(QuoteFactory::class); +/** @var QuoteResource $quoteResource */ +$quoteResource = Bootstrap::getObjectManager()->get(QuoteResource::class); +/** @var CartRepositoryInterface $cartRepository */ +$cartRepository = Bootstrap::getObjectManager()->get(CartRepositoryInterface::class); + +$quote = $quoteFactory->create(); +$quoteResource->load($quote, 'test_quote', 'reserved_order_id'); +$quote->setIsActive(false); +$cartRepository->save($quote); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/set_checkmo_payment_method.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/set_checkmo_payment_method.php new file mode 100644 index 0000000000000..a973a982b1150 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/set_checkmo_payment_method.php @@ -0,0 +1,33 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\OfflinePayments\Model\Checkmo; +use Magento\Quote\Api\Data\PaymentInterface; +use Magento\Quote\Api\Data\PaymentInterfaceFactory; +use Magento\Quote\Api\PaymentMethodManagementInterface; +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var QuoteFactory $quoteFactory */ +$quoteFactory = Bootstrap::getObjectManager()->get(QuoteFactory::class); +/** @var QuoteResource $quoteResource */ +$quoteResource = Bootstrap::getObjectManager()->get(QuoteResource::class); +/** @var PaymentInterfaceFactory $paymentFactory */ +$paymentFactory = Bootstrap::getObjectManager()->get(PaymentInterfaceFactory::class); +/** @var PaymentMethodManagementInterface $paymentMethodManagement */ +$paymentMethodManagement = Bootstrap::getObjectManager()->get(PaymentMethodManagementInterface::class); + +$quote = $quoteFactory->create(); +$quoteResource->load($quote, 'test_quote', 'reserved_order_id'); + +$payment = $paymentFactory->create([ + 'data' => [ + PaymentInterface::KEY_METHOD => Checkmo::PAYMENT_METHOD_CHECKMO_CODE, + ] +]); +$paymentMethodManagement->set($quote->getId(), $payment); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/set_new_shipping_address.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/set_new_shipping_address.php new file mode 100644 index 0000000000000..e17b9e61f82db --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Api\DataObjectHelper; +use Magento\Quote\Api\Data\AddressInterface; +use Magento\Quote\Api\Data\AddressInterfaceFactory; +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\Quote\Model\ShippingAddressManagementInterface; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var QuoteFactory $quoteFactory */ +$quoteFactory = Bootstrap::getObjectManager()->get(QuoteFactory::class); +/** @var QuoteResource $quoteResource */ +$quoteResource = Bootstrap::getObjectManager()->get(QuoteResource::class); +/** @var AddressInterfaceFactory $quoteAddressFactory */ +$quoteAddressFactory = Bootstrap::getObjectManager()->get(AddressInterfaceFactory::class); +/** @var DataObjectHelper $dataObjectHelper */ +$dataObjectHelper = Bootstrap::getObjectManager()->get(DataObjectHelper::class); +/** @var ShippingAddressManagementInterface $shippingAddressManagement */ +$shippingAddressManagement = Bootstrap::getObjectManager()->get(ShippingAddressManagementInterface::class); + +$quoteAddressData = [ + AddressInterface::KEY_TELEPHONE => 3468676, + AddressInterface::KEY_POSTCODE => 75477, + AddressInterface::KEY_COUNTRY_ID => 'US', + AddressInterface::KEY_CITY => 'CityM', + AddressInterface::KEY_COMPANY => 'CompanyName', + AddressInterface::KEY_STREET => 'Green str, 67', + AddressInterface::KEY_LASTNAME => 'Smith', + AddressInterface::KEY_FIRSTNAME => 'John', + AddressInterface::KEY_REGION_ID => 1, +]; +$quoteAddress = $quoteAddressFactory->create(); +$dataObjectHelper->populateWithArray($quoteAddress, $quoteAddressData, AddressInterfaceFactory::class); + +$quote = $quoteFactory->create(); +$quoteResource->load($quote, 'test_quote', 'reserved_order_id'); +$shippingAddressManagement->assign($quote->getId(), $quoteAddress); diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Block/Adminhtml/Subscriber/GridTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Block/Adminhtml/Subscriber/GridTest.php new file mode 100644 index 0000000000000..48d3356525f49 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Newsletter/Block/Adminhtml/Subscriber/GridTest.php @@ -0,0 +1,105 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Newsletter\Block\Adminhtml\Subscriber; + +/** + * @magentoAppArea adminhtml + * @magentoDbIsolation enabled + * + * @see \Magento\Newsletter\Block\Adminhtml\Subscriber\Grid + */ +class GridTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var null|\Magento\Framework\ObjectManagerInterface + */ + private $objectManager = null; + /** + * @var null|\Magento\Framework\View\LayoutInterface + */ + private $layout = null; + + /** + * Set up layout. + */ + protected function setUp() + { + parent::setUp(); + + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + + $this->layout = $this->objectManager->create(\Magento\Framework\View\LayoutInterface::class); + $this->layout->getUpdate()->load('newsletter_subscriber_grid'); + $this->layout->generateXml(); + $this->layout->generateElements(); + } + + /** + * Check if mass action block exists. + */ + public function testMassActionBlockExists() + { + $this->assertNotFalse( + $this->getMassActionBlock(), + 'Mass action block does not exist in the grid, or it name was changed.' + ); + } + + /** + * Check if mass action id field is correct. + */ + public function testMassActionFieldIdIsCorrect() + { + $this->assertEquals( + 'subscriber_id', + $this->getMassActionBlock()->getMassactionIdField(), + 'Mass action id field is incorrect.' + ); + } + + /** + * Check if function returns correct result. + * + * @magentoDataFixture Magento/Newsletter/_files/subscribers.php + */ + public function testMassActionBlockContainsCorrectIdList() + { + $this->assertEquals( + implode(',', $this->getAllSubscriberIdList()), + $this->getMassActionBlock()->getGridIdsJson(), + 'Function returns incorrect result.' + ); + } + + /** + * Retrieve mass action block. + * + * @return bool|\Magento\Backend\Block\Widget\Grid\Massaction + */ + private function getMassActionBlock() + { + return $this->layout->getBlock('adminhtml.newslettrer.subscriber.grid.massaction'); + } + + /** + * Retrieve list of id of all subscribers. + * + * @return array + */ + private function getAllSubscriberIdList() + { + /** @var \Magento\Framework\App\ResourceConnection $resourceConnection */ + $resourceConnection = $this->objectManager->get(\Magento\Framework\App\ResourceConnection::class); + $select = $resourceConnection->getConnection() + ->select() + ->from($resourceConnection->getTableName('newsletter_subscriber')) + ->columns(['subscriber_id' => 'subscriber_id']); + + return $resourceConnection->getConnection()->fetchCol($select); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Search/Model/SynonymReaderTest.php b/dev/tests/integration/testsuite/Magento/Search/Model/SynonymReaderTest.php index b9ba89ba53144..2d0020ba22680 100644 --- a/dev/tests/integration/testsuite/Magento/Search/Model/SynonymReaderTest.php +++ b/dev/tests/integration/testsuite/Magento/Search/Model/SynonymReaderTest.php @@ -48,7 +48,22 @@ public static function loadByPhraseDataProvider() ['synonyms' => 'queen,monarch', 'store_id' => 1, 'website_id' => 0], ['synonyms' => 'british,english', 'store_id' => 1, 'website_id' => 0] ] - ] + ], + [ + 'query_value', [] + ], + [ + 'query_value+', [] + ], + [ + 'query_value-', [] + ], + [ + 'query_@value', [] + ], + [ + 'query_value+@', [] + ], ]; } diff --git a/dev/tests/integration/testsuite/Magento/Ups/_files/enable_ups_shipping_method.php b/dev/tests/integration/testsuite/Magento/Ups/_files/enable_ups_shipping_method.php new file mode 100644 index 0000000000000..5c6c60866fafb --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Ups/_files/enable_ups_shipping_method.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\App\Config\Storage\Writer; +use Magento\Framework\App\Config\Storage\WriterInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\App\Config\ScopeConfigInterface; + +$objectManager = Bootstrap::getObjectManager(); +/** @var Writer $configWriter */ +$configWriter = $objectManager->get(WriterInterface::class); + +$configWriter->save('carriers/ups/active', 1); + +$scopeConfig = $objectManager->get(ScopeConfigInterface::class); +$scopeConfig->clean(); diff --git a/dev/tests/integration/testsuite/Magento/Ups/_files/enable_ups_shipping_method_rollback.php b/dev/tests/integration/testsuite/Magento/Ups/_files/enable_ups_shipping_method_rollback.php new file mode 100644 index 0000000000000..6d7894879f97b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Ups/_files/enable_ups_shipping_method_rollback.php @@ -0,0 +1,16 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\App\Config\Storage\Writer; +use Magento\Framework\App\Config\Storage\WriterInterface; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var Writer $configWriter */ +$configWriter = $objectManager->create(WriterInterface::class); + +$configWriter->delete('carriers/ups/active'); diff --git a/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/UrlFinderInterfaceTest.php b/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/UrlFinderInterfaceTest.php new file mode 100644 index 0000000000000..b6055f14e79d2 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/UrlFinderInterfaceTest.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\UrlRewrite\Model; + +use Magento\TestFramework\Helper\Bootstrap; +use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; + +/** + * @magentoDataFixture Magento/UrlRewrite/_files/url_rewrites.php + */ +class UrlFinderInterfaceTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var UrlFinderInterface + */ + private $urlFinder; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->urlFinder = Bootstrap::getObjectManager()->create(UrlFinderInterface::class); + } + + /** + * @dataProvider findOneDataProvider + * @param string $requestPath + * @param string $targetPath + * @param int $redirectType + */ + public function testFindOneByData(string $requestPath, string $targetPath, int $redirectType) + { + $data = [ + UrlRewrite::REQUEST_PATH => $requestPath, + ]; + $urlRewrite = $this->urlFinder->findOneByData($data); + $this->assertEquals($targetPath, $urlRewrite->getTargetPath()); + $this->assertEquals($redirectType, $urlRewrite->getRedirectType()); + } + + /** + * @return array + */ + public function findOneDataProvider(): array + { + return [ + ['string', 'test_page1', 0], + ['string/', 'string', 301], + ['string_permanent', 'test_page1', 301], + ['string_permanent/', 'test_page1', 301], + ['string_temporary', 'test_page1', 302], + ['string_temporary/', 'test_page1', 302], + ['строка', 'test_page1', 0], + ['строка/', 'строка', 301], + [urlencode('строка'), 'test_page2', 0], + [urlencode('строка') . '/', urlencode('строка'), 301], + ['другая_строка', 'test_page1', 302], + ['другая_строка/', 'test_page1', 302], + [urlencode('другая_строка'), 'test_page1', 302], + [urlencode('другая_строка') . '/', 'test_page1', 302], + ['السلسلة', 'test_page1', 0], + [urlencode('السلسلة'), 'test_page1', 0], + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrites.php b/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrites.php new file mode 100644 index 0000000000000..9edc6507308ee --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrites.php @@ -0,0 +1,42 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +$rewritesData = [ + [ + 'string', 'test_page1', 0 + ], + [ + 'string_permanent', 'test_page1', \Magento\UrlRewrite\Model\OptionProvider::PERMANENT + ], + [ + 'string_temporary', 'test_page1', \Magento\UrlRewrite\Model\OptionProvider::TEMPORARY + ], + [ + 'строка', 'test_page1', 0 + ], + [ + urlencode('строка'), 'test_page2', 0 + ], + [ + 'другая_строка', 'test_page1', \Magento\UrlRewrite\Model\OptionProvider::TEMPORARY + ], + [ + 'السلسلة', 'test_page1', 0 + ], +]; + +$rewriteResource = $objectManager->create(\Magento\UrlRewrite\Model\ResourceModel\UrlRewrite::class); +foreach ($rewritesData as $rewriteData) { + list ($requestPath, $targetPath, $redirectType) = $rewriteData; + $rewrite = $objectManager->create(\Magento\UrlRewrite\Model\UrlRewrite::class); + $rewrite->setEntityType('custom') + ->setRequestPath($requestPath) + ->setTargetPath($targetPath) + ->setRedirectType($redirectType); + $rewriteResource->save($rewrite); +} diff --git a/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrites_rollback.php b/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrites_rollback.php new file mode 100644 index 0000000000000..a98f947d614e0 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrites_rollback.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +$registry = $objectManager->get(\Magento\Framework\Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +$urlRewriteCollection = $objectManager->create(\Magento\UrlRewrite\Model\ResourceModel\UrlRewriteCollection::class); +$collection = $urlRewriteCollection + ->addFieldToFilter('target_path', ['test_page1', 'test_page2']) + ->load() + ->walk('delete'); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/lib/internal/Magento/Framework/Locale/Test/Unit/FormatTest.php b/lib/internal/Magento/Framework/Locale/Test/Unit/FormatTest.php index 1141f451c13a5..73a029a5a1411 100644 --- a/lib/internal/Magento/Framework/Locale/Test/Unit/FormatTest.php +++ b/lib/internal/Magento/Framework/Locale/Test/Unit/FormatTest.php @@ -68,15 +68,17 @@ protected function setUp() /** * @param string $localeCode + * @param string $currencyCode * @param array $expectedResult * @dataProvider getPriceFormatDataProvider */ - public function testGetPriceFormat($localeCode, array $expectedResult): void + public function testGetPriceFormat($localeCode, $currencyCode, array $expectedResult): void { $this->scope->expects($this->once()) ->method('getCurrentCurrency') ->willReturn($this->currency); + $this->currency->method('getCode')->willReturn($currencyCode); $result = $this->formatModel->getPriceFormat($localeCode); $intersection = array_intersect_assoc($result, $expectedResult); $this->assertCount(count($expectedResult), $intersection); @@ -88,18 +90,19 @@ public function testGetPriceFormat($localeCode, array $expectedResult): void */ public function getPriceFormatDataProvider(): array { + $swissGroupSymbol = INTL_ICU_VERSION >= 59.1 ? '’' : '\''; return [ - ['en_US', ['decimalSymbol' => '.', 'groupSymbol' => ',']], - ['de_DE', ['decimalSymbol' => ',', 'groupSymbol' => '.']], - ['de_CH', ['decimalSymbol' => '.', 'groupSymbol' => '\'']], - ['uk_UA', ['decimalSymbol' => ',', 'groupSymbol' => ' ']] + ['en_US', 'USD', ['decimalSymbol' => '.', 'groupSymbol' => ',']], + ['de_DE', 'EUR', ['decimalSymbol' => ',', 'groupSymbol' => '.']], + ['de_CH', 'CHF', ['decimalSymbol' => '.', 'groupSymbol' => $swissGroupSymbol]], + ['uk_UA', 'UAH', ['decimalSymbol' => ',', 'groupSymbol' => ' ']] ]; } /** * - * @param mixed $value - * @param float $expected + * @param mixed $value + * @param float $expected * @dataProvider provideNumbers */ public function testGetNumber($value, $expected): void diff --git a/lib/web/mage/gallery/gallery.js b/lib/web/mage/gallery/gallery.js index 15c3d01cf2be3..2ed3f8b0d620c 100644 --- a/lib/web/mage/gallery/gallery.js +++ b/lib/web/mage/gallery/gallery.js @@ -141,7 +141,7 @@ define([ this.setupBreakpoints(); this.initFullscreenSettings(); - this.settings.$element.on('mouseup', '.fotorama__stage__frame', function () { + this.settings.$element.on('mousedown', '.fotorama__stage__frame', function () { if ( !$(this).parents('.fotorama__shadows--left, .fotorama__shadows--right').length && !$(this).hasClass('fotorama-video-container') diff --git a/lib/web/mage/validation.js b/lib/web/mage/validation.js index dfa35473176b9..a57191fd4aff4 100644 --- a/lib/web/mage/validation.js +++ b/lib/web/mage/validation.js @@ -1431,10 +1431,14 @@ ], 'validate-per-page-value-list': [ function (v) { - var isValid = !$.mage.isEmpty(v), + var isValid = true, values = v.split(','), i; + if ($.mage.isEmpty(v)) { + return isValid; + } + for (i = 0; i < values.length; i++) { if (!/^[0-9]+$/.test(values[i])) { isValid = false; diff --git a/nginx.conf.sample b/nginx.conf.sample index ce3891627bc8c..aef22cc55fbbe 100644 --- a/nginx.conf.sample +++ b/nginx.conf.sample @@ -159,6 +159,11 @@ location /media/downloadable/ { location /media/import/ { deny all; } +location /errors/ { + location ~* \.xml$ { + deny all; + } +} # PHP entry point for main application location ~ ^/(index|get|static|errors/report|errors/404|errors/503|health_check)\.php$ { @@ -198,6 +203,6 @@ gzip_types gzip_vary on; # Banned locations (only reached if the earlier PHP entry point regexes don't match) -location ~* (\.php$|\.htaccess$|\.git) { +location ~* (\.php$|\.phtml$|\.htaccess$|\.git) { deny all; } diff --git a/pub/errors/.htaccess b/pub/errors/.htaccess index 3692dd439e2ff..a7b9cbda05893 100644 --- a/pub/errors/.htaccess +++ b/pub/errors/.htaccess @@ -1,4 +1,7 @@ Options None +<FilesMatch "\.(xml|phtml)$"> + Deny from all +</FilesMatch> <IfModule mod_rewrite.c> RewriteEngine Off </IfModule>