diff --git a/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle.php b/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle.php index 41f11b3d529ee..62a2fa1c47e1e 100644 --- a/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle.php +++ b/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle.php @@ -188,6 +188,10 @@ public function getJsonConfig() $configValue = $preConfiguredValues->getData('bundle_option/' . $optionId); if ($configValue) { $defaultValues[$optionId] = $configValue; + $configQty = $preConfiguredValues->getData('bundle_option_qty/' . $optionId); + if ($configQty) { + $options[$optionId]['selections'][$configValue]['qty'] = $configQty; + } } } $position++; diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Eraser.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Eraser.php index 8182e6f07fab1..6762602aecaf1 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Eraser.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Eraser.php @@ -8,7 +8,12 @@ namespace Magento\Catalog\Model\Indexer\Product\Flat\Action; use Magento\Framework\App\ResourceConnection; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Store\Model\Store; +/** + * Flat item eraser. Used to clear items from the catalog flat table. + */ class Eraser { /** @@ -50,12 +55,7 @@ public function __construct( */ public function removeDeletedProducts(array &$ids, $storeId) { - $select = $this->connection->select()->from( - $this->productIndexerHelper->getTable('catalog_product_entity') - )->where( - 'entity_id IN(?)', - $ids - ); + $select = $this->getSelectForProducts($ids); $result = $this->connection->query($select); $existentProducts = []; @@ -69,6 +69,61 @@ public function removeDeletedProducts(array &$ids, $storeId) $this->deleteProductsFromStore($productsToDelete, $storeId); } + /** + * Remove products with "Disabled" status from the flat table(s). + * + * @param array $ids + * @param int $storeId + * @return void + */ + public function removeDisabledProducts(array &$ids, $storeId) + { + /* @var $statusAttribute \Magento\Eav\Model\Entity\Attribute */ + $statusAttribute = $this->productIndexerHelper->getAttribute('status'); + + $select = $this->getSelectForProducts($ids); + $select->joinLeft( + ['status_global_attr' => $statusAttribute->getBackendTable()], + ' status_global_attr.attribute_id = ' . (int)$statusAttribute->getAttributeId() + . ' AND status_global_attr.store_id = ' . Store::DEFAULT_STORE_ID, + [] + ); + $select->joinLeft( + ['status_attr' => $statusAttribute->getBackendTable()], + ' status_attr.attribute_id = ' . (int)$statusAttribute->getAttributeId() + . ' AND status_attr.store_id = ' . $storeId, + [] + ); + $select->where('IFNULL(status_attr.value, status_global_attr.value) = ?', Status::STATUS_DISABLED); + + $result = $this->connection->query($select); + + $disabledProducts = []; + foreach ($result->fetchAll() as $product) { + $disabledProducts[] = $product['entity_id']; + } + + if (!empty($disabledProducts)) { + $ids = array_diff($ids, $disabledProducts); + $this->deleteProductsFromStore($disabledProducts, $storeId); + } + } + + /** + * Get Select object for existed products. + * + * @param array $ids + * @return \Magento\Framework\DB\Select + */ + private function getSelectForProducts(array $ids) + { + $productTable = $this->productIndexerHelper->getTable('catalog_product_entity'); + $select = $this->connection->select()->from($productTable) + ->columns('entity_id') + ->where('entity_id IN(?)', $ids); + return $select; + } + /** * Delete products from flat table(s) * diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Row.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Row.php index 6d0727259d9db..d9bfc96b7817a 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Row.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Row.php @@ -90,6 +90,7 @@ public function execute($id = null) $tableExists = $this->_isFlatTableExists($store->getId()); if ($tableExists) { $this->flatItemEraser->removeDeletedProducts($ids, $store->getId()); + $this->flatItemEraser->removeDisabledProducts($ids, $store->getId()); } /* @var $status \Magento\Eav\Model\Entity\Attribute */ diff --git a/app/code/Magento/Catalog/Model/Product/Url.php b/app/code/Magento/Catalog/Model/Product/Url.php index c291dc33fedab..f3ac9f55d1aea 100644 --- a/app/code/Magento/Catalog/Model/Product/Url.php +++ b/app/code/Magento/Catalog/Model/Product/Url.php @@ -7,6 +7,7 @@ use Magento\UrlRewrite\Model\UrlFinderInterface; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; +use Magento\Framework\App\Config\ScopeConfigInterface; /** * Product Url model @@ -45,6 +46,11 @@ class Url extends \Magento\Framework\DataObject */ protected $urlFinder; + /** + * @var \Magento\Framework\App\Config\ScopeConfigInterface + */ + private $scopeConfig; + /** * @param \Magento\Framework\UrlFactory $urlFactory * @param \Magento\Store\Model\StoreManagerInterface $storeManager @@ -52,6 +58,7 @@ class Url extends \Magento\Framework\DataObject * @param \Magento\Framework\Session\SidResolverInterface $sidResolver * @param UrlFinderInterface $urlFinder * @param array $data + * @param ScopeConfigInterface|null $scopeConfig */ public function __construct( \Magento\Framework\UrlFactory $urlFactory, @@ -59,7 +66,8 @@ public function __construct( \Magento\Framework\Filter\FilterManager $filter, \Magento\Framework\Session\SidResolverInterface $sidResolver, UrlFinderInterface $urlFinder, - array $data = [] + array $data = [], + ScopeConfigInterface $scopeConfig = null ) { parent::__construct($data); $this->urlFactory = $urlFactory; @@ -67,16 +75,8 @@ public function __construct( $this->filter = $filter; $this->sidResolver = $sidResolver; $this->urlFinder = $urlFinder; - } - - /** - * Retrieve URL Instance - * - * @return \Magento\Framework\UrlInterface - */ - private function getUrlInstance() - { - return $this->urlFactory->create(); + $this->scopeConfig = $scopeConfig ?: + \Magento\Framework\App\ObjectManager::getInstance()->get(ScopeConfigInterface::class); } /** @@ -157,10 +157,19 @@ public function getUrl(\Magento\Catalog\Model\Product $product, $params = []) UrlRewrite::ENTITY_TYPE => \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator::ENTITY_TYPE, UrlRewrite::STORE_ID => $storeId, ]; + $useCategories = $this->scopeConfig->getValue( + \Magento\Catalog\Helper\Product::XML_PATH_PRODUCT_URL_USE_CATEGORY, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + if ($categoryId) { $filterData[UrlRewrite::METADATA]['category_id'] = $categoryId; + } elseif (!$useCategories) { + $filterData[UrlRewrite::METADATA]['category_id'] = ''; } + $rewrite = $this->urlFinder->findOneByData($filterData); + if ($rewrite) { $requestPath = $rewrite->getRequestPath(); $product->setRequestPath($requestPath); @@ -194,6 +203,7 @@ public function getUrl(\Magento\Catalog\Model\Product $product, $params = []) $routeParams['_query'] = []; } - return $this->getUrlInstance()->setScope($storeId)->getUrl($routePath, $routeParams); + $url = $this->urlFactory->create()->setScope($storeId); + return $url->getUrl($routePath, $routeParams); } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index fdd98442150ae..0d62d120f80e0 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -1423,8 +1423,13 @@ protected function _addUrlRewrite() ['cu' => $this->getTable('catalog_url_rewrite_product_category')], 'u.url_rewrite_id=cu.url_rewrite_id' )->where('cu.category_id IN (?)', $this->_urlRewriteCategory); + } else { + $select->joinLeft( + ['cu' => $this->getTable('catalog_url_rewrite_product_category')], + 'u.url_rewrite_id=cu.url_rewrite_id' + )->where('cu.url_rewrite_id IS NULL'); } - + // more priority is data with category id $urlRewrites = []; diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml index a138c9bbac073..51199300206a9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml @@ -23,6 +23,7 @@ + diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/EraserTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/EraserTest.php index cc6f5d84ef001..c04428eadef0d 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/EraserTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/EraserTest.php @@ -54,6 +54,7 @@ public function testRemoveDeletedProducts() $productsToDeleteIds = [1, 2]; $select = $this->createMock(\Magento\Framework\DB\Select::class); $select->expects($this->once())->method('from')->with('catalog_product_entity')->will($this->returnSelf()); + $select->expects($this->once())->method('columns')->with('entity_id')->will($this->returnSelf()); $select->expects($this->once())->method('where')->with('entity_id IN(?)', $productsToDeleteIds) ->will($this->returnSelf()); $products = [['entity_id' => 2]]; diff --git a/app/code/Magento/Catalog/etc/adminhtml/system.xml b/app/code/Magento/Catalog/etc/adminhtml/system.xml index 71a799fd22427..d6c98b92596fd 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/system.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/system.xml @@ -83,8 +83,9 @@ Magento\Catalog\Model\Indexer\Product\Flat\System\Config\Mode Magento\Config\Model\Config\Source\Yesno - + + Applies to category pages Magento\Catalog\Model\Config\Source\ListSort diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontShippmentFromActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontShippmentFromActionGroup.xml new file mode 100644 index 0000000000000..354ad6d2b44ba --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontShippmentFromActionGroup.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml index 494a365ffd507..ff7f8995c0681 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml @@ -31,6 +31,7 @@ + diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/ZeroSubtotalOrdersWithProcessingStatusTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/ZeroSubtotalOrdersWithProcessingStatusTest.xml new file mode 100644 index 0000000000000..aaedaedb7236c --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/ZeroSubtotalOrdersWithProcessingStatusTest.xml @@ -0,0 +1,100 @@ + + + + + + + + + + <description value="Created order should be in Processing status"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-94178"/> + <group value="checkout"/> + </annotations> + <before> + <createData entity="SimpleSubCategory" stepKey="simplecategory"/> + <createData entity="SimpleProduct" stepKey="simpleproduct"> + <requiredEntity createDataKey="simplecategory"/> + </createData> + <createData entity="PaymentMethodsSettingConfig" stepKey="paymentMethodsSettingConfig"/> + <createData entity="FreeShippingMethodsSettingConfig" stepKey="freeShippingMethodsSettingConfig"/> + <!--Go to Admin page--> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + + <after> + <actionGroup ref="DeleteCartPriceRuleByName" stepKey="deleteSalesRule"> + <argument name="ruleName" value="{{ApiSalesRule.name}}"/> + </actionGroup> + <createData entity="DefaultShippingMethodsConfig" stepKey="defaultShippingMethodsConfig"/> + <createData entity="DisableFreeShippingConfig" stepKey="disableFreeShippingConfig"/> + <createData entity="DisablePaymentMethodsSettingConfig" stepKey="disablePaymentMethodsSettingConfig"/> + <actionGroup ref="logout" stepKey="logout"/> + <deleteData createDataKey="simpleproduct" stepKey="deleteProduct"/> + <deleteData createDataKey="simplecategory" stepKey="deleteCategory"/> + </after> + + <!--Open MARKETING > Cart Price Rules--> + <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="amOnCartPriceList"/> + <waitForPageLoad stepKey="waitForRulesPage"/> + + <!--Add New Rule--> + <click selector="{{AdminCartPriceRulesSection.addNewRuleButton}}" stepKey="clickAddNewRule"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{ApiSalesRule.name}}" stepKey="fillRuleName"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="Main Website" stepKey="selectWebsite"/> + <actionGroup ref="selectNotLoggedInCustomerGroup" stepKey="chooseNotLoggedInCustomerGroup"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.coupon}}" userInput="Specific Coupon" stepKey="selectCouponType"/> + <fillField selector="{{AdminCartPriceRulesFormSection.couponCode}}" userInput="{{_defaultCoupon.code}}" stepKey="fillCouponCode"/> + <fillField selector="{{AdminCartPriceRulesFormSection.userPerCoupon}}" userInput="99" stepKey="fillUserPerCoupon"/> + <click selector="{{AdminCartPriceRulesFormSection.actionsHeader}}" stepKey="clickToExpandActions"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.apply}}" userInput="Percent of product price discount" stepKey="selectActionType"/> + <fillField selector="{{AdminCartPriceRulesFormSection.discountAmount}}" userInput="100" stepKey="fillDiscountAmount"/> + <click selector="{{AdminCartPriceRulesFormSection.save}}" stepKey="clickSaveButton"/> + <see selector="{{AdminCartPriceRulesSection.messages}}" userInput="You saved the rule." stepKey="seeSuccessMessage"/> + + <!--Proceed to store front and place an order with free shipping using created coupon--> + <!--Add product to card--> + <actionGroup ref="AddSimpleProductToCart" stepKey="AddProductToCard"> + <argument name="product" value="$$simpleproduct$$"/> + </actionGroup> + + <!--Proceed to shipment--> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickToOpenCard"/> + <click selector="{{StorefrontMinicartSection.goToCheckout}}" stepKey="clickToProceedToCheckout"/> + <waitForPageLoad stepKey="waitForTheFormIsOpened"/> + + <!--Fill shipping form--> + <actionGroup ref="ShipmentFormFreeShippingActionGroup" stepKey="shipmentFormFreeShippingActionGroup"/> + + <click selector="{{DiscountSection.DiscountTab}}" stepKey="clickToAddDiscount"/> + <fillField selector="{{DiscountSection.DiscountInput}}" userInput="{{_defaultCoupon.code}}" stepKey="TypeDiscountCode"/> + <click selector="{{DiscountSection.ApplyCodeBtn}}" stepKey="clickToApplyDiscount"/> + <waitForPageLoad stepKey="WaitForDiscountToBeAdded"/> + <see userInput="Your coupon was successfully applied." stepKey="verifyText"/> + + <waitForElement selector="{{CheckoutPaymentSection.placeOrder}}" time="30" stepKey="waitForPlaceOrderButton"/> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber}}" stepKey="grabOrderNumber"/> + + <!--Proceed to Admin panel > SALES > Orders. Created order should be in Processing status--> + <amOnPage url="/admin/sales/order/" stepKey="navigateToSalesOrderPage"/> + <waitForPageLoad stepKey="waitForSalesOrderPageLoaded"/> + + <!-- Open Order --> + <actionGroup ref="filterOrderGridById" stepKey="filterOrderGridById"> + <argument name="orderId" value="$grabOrderNumber"/> + </actionGroup> + <click selector="{{AdminOrdersGridSection.firstRow}}" stepKey="clickOrderRow"/> + <waitForPageLoad stepKey="waitForCreatedOrderPageOpened"/> + + <!--Verify that Created order is in Processing status--> + <see selector="{{AdminShipmentOrderInformationSection.orderStatus}}" userInput="Processing" stepKey="seeShipmentOrderStatus"/> + </test> +</tests> diff --git a/app/code/Magento/Customer/Helper/Address.php b/app/code/Magento/Customer/Helper/Address.php index c74c62dc6d98c..cfc76753fa10a 100644 --- a/app/code/Magento/Customer/Helper/Address.php +++ b/app/code/Magento/Customer/Helper/Address.php @@ -127,6 +127,8 @@ public function getBookUrl() } /** + * Retrieve edit url. + * * @return void */ public function getEditUrl() @@ -134,6 +136,8 @@ public function getEditUrl() } /** + * Retrieve delete url. + * * @return void */ public function getDeleteUrl() @@ -141,6 +145,8 @@ public function getDeleteUrl() } /** + * Retrieve create url. + * * @return void */ public function getCreateUrl() @@ -148,6 +154,8 @@ public function getCreateUrl() } /** + * Retrieve block renderer. + * * @param string $renderer * @return \Magento\Framework\View\Element\BlockInterface */ @@ -204,6 +212,8 @@ public function getStreetLines($store = null) } /** + * Retrieve address format. + * * @param string $code * @return Format|string */ @@ -391,4 +401,23 @@ public function isAttributeVisible($code) } return false; } + + /** + * Retrieve attribute required + * + * @param string $code + * @return bool + * @throws NoSuchEntityException + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function isAttributeRequired($code) + { + $attributeMetadata = $this->_addressMetadataService->getAttributeMetadata($code); + + if ($attributeMetadata) { + return $attributeMetadata->isRequired(); + } + + return false; + } } diff --git a/app/code/Magento/Customer/Test/Unit/Helper/AddressTest.php b/app/code/Magento/Customer/Test/Unit/Helper/AddressTest.php index 74af4ec57c77f..fd06f778efe45 100644 --- a/app/code/Magento/Customer/Test/Unit/Helper/AddressTest.php +++ b/app/code/Magento/Customer/Test/Unit/Helper/AddressTest.php @@ -414,4 +414,43 @@ public function isAttributeVisibleDataProvider() ['invalid_code', false] ]; } + + /** + * Test is required filed by attribute code + * + * @param string $attributeCode + * @param bool $isMetadataExists + * @dataProvider isAttributeRequiredDataProvider + * @covers \Magento\Customer\Helper\Address::isAttributeRequired() + * @return void + */ + public function testIsAttributeRequired($attributeCode, $isMetadataExists) + { + $attributeMetadata = null; + if ($isMetadataExists) { + $attributeMetadata = $this->getMockBuilder(\Magento\Customer\Api\Data\AttributeMetadataInterface::class) + ->getMockForAbstractClass(); + $attributeMetadata->expects($this->once()) + ->method('isRequired') + ->willReturn(true); + } + $this->addressMetadataService->expects($this->once()) + ->method('getAttributeMetadata') + ->with($attributeCode) + ->willReturn($attributeMetadata); + $this->assertEquals($isMetadataExists, $this->helper->isAttributeRequired($attributeCode)); + } + + /** + * Data provider for test testIsAttributeRequire + * + * @return array + */ + public function isAttributeRequiredDataProvider() + { + return [ + ['fax', true], + ['invalid_code', false] + ]; + } } diff --git a/app/code/Magento/Customer/view/base/ui_component/customer_form.xml b/app/code/Magento/Customer/view/base/ui_component/customer_form.xml index 4de6644b948fb..d2a9b3f44624d 100644 --- a/app/code/Magento/Customer/view/base/ui_component/customer_form.xml +++ b/app/code/Magento/Customer/view/base/ui_component/customer_form.xml @@ -486,9 +486,6 @@ </item> </argument> <settings> - <validation> - <rule name="required-entry" xsi:type="boolean">true</rule> - </validation> <dataType>text</dataType> </settings> </field> diff --git a/app/code/Magento/Newsletter/Controller/Subscriber/Unsubscribe.php b/app/code/Magento/Newsletter/Controller/Subscriber/Unsubscribe.php index efc469e15deaa..88fa128162700 100644 --- a/app/code/Magento/Newsletter/Controller/Subscriber/Unsubscribe.php +++ b/app/code/Magento/Newsletter/Controller/Subscriber/Unsubscribe.php @@ -1,16 +1,21 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Newsletter\Controller\Subscriber; -class Unsubscribe extends \Magento\Newsletter\Controller\Subscriber +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +/** + * Controller for unsubscribing customers. + */ +class Unsubscribe extends \Magento\Newsletter\Controller\Subscriber implements HttpGetActionInterface { /** - * Unsubscribe newsletter - * @return void + * Unsubscribe newsletter. + * + * @return \Magento\Backend\Model\View\Result\Redirect */ public function execute() { @@ -27,6 +32,9 @@ public function execute() $this->messageManager->addException($e, __('Something went wrong while unsubscribing you.')); } } - $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl()); + /** @var \Magento\Backend\Model\View\Result\Redirect $redirect */ + $redirect = $this->resultFactory->create(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT); + $redirectUrl = $this->_redirect->getRedirectUrl(); + return $redirect->setUrl($redirectUrl); } } diff --git a/app/code/Magento/Payment/etc/config.xml b/app/code/Magento/Payment/etc/config.xml index 9fe859c96a941..663734fb066c7 100644 --- a/app/code/Magento/Payment/etc/config.xml +++ b/app/code/Magento/Payment/etc/config.xml @@ -13,6 +13,7 @@ <model>Magento\Payment\Model\Method\Free</model> <order_status>pending</order_status> <title>No Payment Information Required + authorize 0 1 diff --git a/app/code/Magento/Reports/Block/Adminhtml/Sales/Tax/Grid.php b/app/code/Magento/Reports/Block/Adminhtml/Sales/Tax/Grid.php index 79deb27423be5..f65a2b964fb38 100644 --- a/app/code/Magento/Reports/Block/Adminhtml/Sales/Tax/Grid.php +++ b/app/code/Magento/Reports/Block/Adminhtml/Sales/Tax/Grid.php @@ -53,7 +53,8 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc + * * @codeCoverageIgnore */ protected function _construct() @@ -64,7 +65,7 @@ protected function _construct() } /** - * {@inheritdoc} + * @inheritdoc */ public function getResourceCollectionName() { @@ -74,7 +75,7 @@ public function getResourceCollectionName() } /** - * {@inheritdoc} + * @inheritdoc */ protected function _prepareColumns() { @@ -123,7 +124,6 @@ protected function _prepareColumns() [ 'header' => __('Orders'), 'index' => 'orders_count', - 'total' => 'sum', 'type' => 'number', 'sortable' => false, 'header_css_class' => 'col-qty', diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/View/History.php b/app/code/Magento/Sales/Block/Adminhtml/Order/View/History.php index 1912655a9292d..10b80b6f4e527 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/View/History.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/View/History.php @@ -88,7 +88,8 @@ public function getStatuses() */ public function canSendCommentEmail() { - return $this->_salesData->canSendOrderCommentEmail($this->getOrder()->getStore()->getId()); + return $this->_salesData->canSendOrderCommentEmail($this->getOrder()->getStore()->getId()) + && $this->_authorization->isAllowed('Magento_Sales::email'); } /** diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/DiscountSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/DiscountSection.xml index cbab097c5291b..e14bfb554b3f6 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Section/DiscountSection.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Section/DiscountSection.xml @@ -8,8 +8,9 @@
- + +
diff --git a/app/code/Magento/Store/Test/Mftf/Data/StorePaymentMethodsData.xml b/app/code/Magento/Store/Test/Mftf/Data/StorePaymentMethodsData.xml new file mode 100644 index 0000000000000..912399142fa61 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Data/StorePaymentMethodsData.xml @@ -0,0 +1,30 @@ + + + + + active + orderStatus + + + 1 + + + processing + + + + zeroSubEnable + zeroSubOrderStatus + + + 1 + + + 1 + + diff --git a/app/code/Magento/Store/Test/Mftf/Data/StoreShippingMethodsData.xml b/app/code/Magento/Store/Test/Mftf/Data/StoreShippingMethodsData.xml new file mode 100644 index 0000000000000..6e3e7ce7eb0d3 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Data/StoreShippingMethodsData.xml @@ -0,0 +1,30 @@ + + + + + active + + + 1 + + + + + DefaultFreeShipping + + + 0 + + + + disableFreeShipping + + + 1 + + diff --git a/app/code/Magento/Store/Test/Mftf/Metadata/store_payment_methods-meta.xml b/app/code/Magento/Store/Test/Mftf/Metadata/store_payment_methods-meta.xml new file mode 100644 index 0000000000000..cbad7265cbbd6 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Metadata/store_payment_methods-meta.xml @@ -0,0 +1,46 @@ + + + + + + + + + + string + + + string + + + + + + + + + + + + + integer + + + + + integer + + + + + + + + + diff --git a/app/code/Magento/Store/Test/Mftf/Metadata/store_shipping_methods-meta.xml b/app/code/Magento/Store/Test/Mftf/Metadata/store_shipping_methods-meta.xml new file mode 100644 index 0000000000000..83288ecfdaf71 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Metadata/store_shipping_methods-meta.xml @@ -0,0 +1,38 @@ + + + + + + + + + + string + + + + + + + + + + + + + integer + + + + + + + + + diff --git a/app/code/Magento/Tax/Model/ResourceModel/Report/Tax/Createdat.php b/app/code/Magento/Tax/Model/ResourceModel/Report/Tax/Createdat.php index ebf699db552d6..60cb6fe2898ae 100644 --- a/app/code/Magento/Tax/Model/ResourceModel/Report/Tax/Createdat.php +++ b/app/code/Magento/Tax/Model/ResourceModel/Report/Tax/Createdat.php @@ -11,6 +11,9 @@ */ namespace Magento\Tax\Model\ResourceModel\Report\Tax; +/** + * Class for tax report resource model with aggregation by created at + */ class Createdat extends \Magento\Reports\Model\ResourceModel\Report\AbstractReport { /** @@ -84,7 +87,7 @@ protected function _aggregateByOrder($aggregationField, $from, $to) 'order_status' => 'e.status', 'percent' => 'MAX(tax.' . $connection->quoteIdentifier('percent') . ')', 'orders_count' => 'COUNT(DISTINCT e.entity_id)', - 'tax_base_amount_sum' => 'SUM(tax.base_amount * e.base_to_global_rate)', + 'tax_base_amount_sum' => 'SUM(tax.base_real_amount * e.base_to_global_rate)', ]; $select = $connection->select()->from( diff --git a/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminTaxActionGroup.xml b/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminTaxActionGroup.xml index 1a95bf0282b40..aaea515aa9017 100644 --- a/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminTaxActionGroup.xml +++ b/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminTaxActionGroup.xml @@ -115,4 +115,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Tax/Test/Mftf/Data/TaxCodeData.xml b/app/code/Magento/Tax/Test/Mftf/Data/TaxCodeData.xml index 42fd01357375e..36929ef2e3fa1 100644 --- a/app/code/Magento/Tax/Test/Mftf/Data/TaxCodeData.xml +++ b/app/code/Magento/Tax/Test/Mftf/Data/TaxCodeData.xml @@ -26,4 +26,16 @@ * 0 + + Texas + United States + 78729 + 7.25 + + + Texas + United States + 78729 + 0.125 + diff --git a/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxReportsSection.xml b/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxReportsSection.xml new file mode 100644 index 0000000000000..80101687e173e --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxReportsSection.xml @@ -0,0 +1,19 @@ + + + + +
+ + + + + + +
+
diff --git a/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRulesSection.xml b/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRulesSection.xml index 9727c649d7e66..6c258f2de18e7 100644 --- a/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRulesSection.xml +++ b/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRulesSection.xml @@ -19,5 +19,14 @@ + + + + + + + + + diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxReportGridTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxReportGridTest.xml new file mode 100644 index 0000000000000..68fe8087c4fcd --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxReportGridTest.xml @@ -0,0 +1,222 @@ + + + + + + + + + + <description value="Tax Report Grid displays Tax amount in rows 'Total' and 'Subtotal' is a sum of all tax amounts"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-94338"/> + <group value="Tax"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Go to tax rule page --> + <actionGroup ref="addNewTaxRuleActionGroup" stepKey="addFirstTaxRuleActionGroup"/> + <fillField stepKey="fillRuleName" selector="{{AdminTaxRulesSection.ruleName}}" userInput="TaxRule1"/> + + <!-- Add NY and CA tax rules --> + <actionGroup ref="addNewTaxRateNoZip" stepKey="addNYTaxRate"> + <argument name="taxCode" value="SimpleTaxWithZipCode"/> + </actionGroup> + + <actionGroup ref="addProductTaxClass" stepKey="addProductTaxClass"> + <argument name="prodTaxClassName" value="TaxClasses1"/> + </actionGroup> + + <click stepKey="disableDefaultProdTaxClass" selector="{{AdminTaxRulesSection.defaultTaxClass}}"/> + <waitForPageLoad stepKey="waitForTaxRulePage"/> + <click stepKey="clickSave" selector="{{AdminTaxRulesSection.saveRule}}"/> + <waitForPageLoad stepKey="waitForNewTaxRuleCreated"/> + + <!-- Go to tax rule page to create second Tax Rule--> + <actionGroup ref="addNewTaxRuleActionGroup" stepKey="addSecondTaxRuleActionGroup"/> + <fillField stepKey="fillSecondRuleName" selector="{{AdminTaxRulesSection.ruleName}}" userInput="TaxRule2"/> + + <actionGroup ref="addNewTaxRateNoZip" stepKey="addCATaxRate"> + <argument name="taxCode" value="SimpleSecondTaxWithZipCode"/> + </actionGroup> + + <actionGroup ref="addProductTaxClass" stepKey="addSecondProductTaxClass"> + <argument name="prodTaxClassName" value="TaxClasses2"/> + </actionGroup> + + <click stepKey="disableSecondProdTaxClass" selector="{{AdminTaxRulesSection.defaultTaxClass}}"/> + <waitForPageLoad stepKey="waitForTaxRulePage2"/> + <click stepKey="clickSaveBtn" selector="{{AdminTaxRulesSection.saveRule}}"/> + <waitForPageLoad stepKey="waitForSecondTaxRuleCreated"/> + + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="firstProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <createData entity="_defaultProduct" stepKey="secondProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + + <!--Open Created products. In Tax Class select new created Product Tax classes.--> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="goToProductIndex"/> + <waitForPageLoad stepKey="wait1"/> + <actionGroup ref="resetProductGridToDefaultView" stepKey="resetProductGrid"/> + <actionGroup ref="filterProductGridBySku" stepKey="filterProductGridBySku"> + <argument name="product" value="$$firstProduct$$"/> + </actionGroup> + <actionGroup ref="openProducForEditByClickingRowXColumnYInProductGrid" stepKey="openFirstProductForEdit"/> + <selectOption selector="{{AdminProductFormSection.productTaxClass}}" stepKey="selectTexClassForFirstProduct" userInput="TaxClasses1"/> + <!-- Save the second product --> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveFirstProduct"/> + <waitForPageLoad stepKey="waitForFirstProductSaved"/> + + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="againGoToProductIndex"/> + <waitForPageLoad stepKey="wait2"/> + <actionGroup ref="resetProductGridToDefaultView" stepKey="resetSecondProductGrid"/> + <actionGroup ref="filterProductGridBySku" stepKey="filterSecondProductGridBySku"> + <argument name="product" value="$$secondProduct$$"/> + </actionGroup> + <actionGroup ref="openProducForEditByClickingRowXColumnYInProductGrid" stepKey="openSecondProductForEdit"/> + <selectOption selector="{{AdminProductFormSection.productTaxClass}}" stepKey="selectTexClassForSecondProduct" userInput="TaxClasses2"/> + <!-- Save the second product --> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveSecondProduct"/> + <waitForPageLoad stepKey="waitForSecondProductSaved"/> + + <!--Create an order with these 2 products in that zip code.--> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="navigateToOrderIndexPage"/> + <waitForPageLoad stepKey="waitForIndexPageLoad"/> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="Orders" stepKey="seeIndexPageTitle"/> + <click selector="{{AdminOrdersGridSection.createNewOrder}}" stepKey="clickCreateNewOrder"/> + <click selector="{{AdminOrderFormActionSection.CreateNewCustomer}}" stepKey="clickCreateCustomer"/> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="Create New Order" stepKey="seeNewOrderPageTitle"/> + <waitForPageLoad stepKey="waitForPage" time="60"/> + <!--Check if order can be submitted without the required fields including email address--> + <scrollToTopOfPage stepKey="scrollToTopOfOrderFormPage" after="seeNewOrderPageTitle"/> + <actionGroup ref="addSimpleProductToOrder" stepKey="addFirstProductToOrder" after="scrollToTopOfOrderFormPage"> + <argument name="product" value="$$firstProduct$$"/> + </actionGroup> + <actionGroup ref="addSimpleProductToOrder" stepKey="addSecondProductToOrder" after="addFirstProductToOrder"> + <argument name="product" value="$$secondProduct$$"/> + </actionGroup> + + <!--Fill customer group and customer email--> + <selectOption selector="{{AdminOrderFormAccountSection.group}}" userInput="{{GeneralCustomerGroup.code}}" stepKey="selectCustomerGroup" after="addSecondProductToOrder"/> + <fillField selector="{{AdminOrderFormAccountSection.email}}" userInput="{{Simple_US_Customer.email}}" stepKey="fillCustomerEmail" after="selectCustomerGroup"/> + + <!--Fill customer address information--> + <actionGroup ref="fillOrderCustomerInformation" stepKey="fillCustomerAddress" after="fillCustomerEmail"> + <argument name="customer" value="Simple_US_Customer"/> + <argument name="address" value="US_Address_TX"/> + </actionGroup> + + <!-- Select shipping --> + <actionGroup ref="orderSelectFlatRateShipping" stepKey="selectFlatRateShipping" after="fillCustomerAddress"/> + + <!--Submit Order and verify information--> + <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="clickSubmitOrder" after="selectFlatRateShipping"/> + <seeInCurrentUrl url="{{AdminOrderDetailsPage.url}}" stepKey="seeViewOrderPage" after="clickSubmitOrder"/> + <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="You created the order." stepKey="seeSuccessMessage" after="seeViewOrderPage"/> + + <!--Create Invoice and Shipment for this Order.--> + <click selector="{{AdminOrderDetailsMainActionsSection.invoice}}" stepKey="clickInvoiceButton"/> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="New Invoice" stepKey="seeNewInvoiceInPageTitle" after="clickInvoiceButton"/> + <waitForPageLoad stepKey="waitForInvoicePageOpened"/> + + <click selector="{{AdminInvoiceMainActionsSection.submitInvoice}}" stepKey="clickSubmitInvoice"/> + <waitForPageLoad stepKey="waitForInvoiceSaved"/> + + <click selector="{{AdminOrderDetailsMainActionsSection.ship}}" stepKey="clickShipAction" after="waitForInvoiceSaved"/> + <seeInCurrentUrl url="{{AdminShipmentNewPage.url}}" stepKey="seeOrderShipmentUrl" after="clickShipAction"/> + <!--Submit Shipment--> + <click selector="{{AdminShipmentMainActionsSection.submitShipment}}" stepKey="clickSubmitShipment" after="seeOrderShipmentUrl"/> + <waitForPageLoad stepKey="waitForShipmentSaved"/> + + <!--Go to "Reports" -> "Sales" -> "Tax"--> + <amOnPage url="/admin/reports/report_sales/tax/" stepKey="navigateToReportsTaxPage"/> + <waitForPageLoad stepKey="waitForReportsTaxPageLoad"/> + + <!--click "here" to refresh last day's statistics --> + <click stepKey="clickRefrashStatisticsHere" selector="{{AdminTaxReportsSection.refreshStatistics}}"/> + <waitForPageLoad stepKey="waitForRefresh"/> + + <!--Select Dates--> + <fillField selector="{{AdminTaxReportsSection.fromDate}}" userInput="05/16/2018" stepKey="fillDateFrom"/> + <click selector="{{AdminTaxReportsSection.toDate}}" stepKey="clickDateTo"/> + <click selector="{{AdminTaxReportsSection.goTodayButton}}" stepKey="clickGoTodayDate"/> + <!--Click "Show report" in the upper right corner.--> + <click selector="{{AdminTaxReportsSection.showReportButton}}" stepKey="clickShowReportButton"/> + <waitForPageLoad time="60" stepKey="waitForReload"/> + <!--Tax Report Grid displays Tax amount in rows. "Total" and "Subtotal" is a sum of all tax amounts--> + <grabTextFrom selector="{{AdminTaxReportsSection.taxRuleAmount('Texas-0.125')}}" stepKey="amountOfFirstTaxRate"/> + <grabTextFrom selector="{{AdminTaxReportsSection.taxRuleAmount('Texas-7.25')}}" stepKey="amountOfSecondTaxRate"/> + <grabTextFrom selector="{{AdminTaxReportsSection.taxRuleAmount('Subtotal')}}" stepKey="amountOfSubtotalTaxRate"/> + <assertEquals stepKey="assertSubtotalFirstField"> + <expectedResult type="string">$0.15</expectedResult> + <actualResult type="variable">amountOfFirstTaxRate</actualResult> + </assertEquals> + + <assertEquals stepKey="assertSubtotalSecondField"> + <expectedResult type="string">$8.92</expectedResult> + <actualResult type="variable">amountOfSecondTaxRate</actualResult> + </assertEquals> + + <assertEquals stepKey="assertSubtotalField"> + <expectedResult type="string">$9.07</expectedResult> + <actualResult type="variable">amountOfSubtotalTaxRate</actualResult> + </assertEquals> + + <after> + <!-- Go to the tax rule page and delete the row we created--> + <amOnPage url="{{AdminTaxRuleGridPage.url}}" stepKey="goToTaxRulesPage"/> + <waitForPageLoad stepKey="waitForRulesPage"/> + + <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deleteRule"> + <argument name="name" value="TaxRule1"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.taxIdentifierSearch}}"/> + </actionGroup> + + <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deleteSecondRule"> + <argument name="name" value="TaxRule2"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.taxIdentifierSearch}}"/> + </actionGroup> + + <!-- Go to the tax rate page --> + <amOnPage url="{{AdminTaxRateGridPage.url}}" stepKey="goToTaxRatesPage"/> + <waitForPageLoad stepKey="waitForRatesPage"/> + + <!-- Delete the two tax rates that were created --> + <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deleteNYRate"> + <argument name="name" value="{{SimpleTaxWithZipCode.state}}-{{SimpleTaxWithZipCode.rate}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.taxIdentifierSearch}}"/> + </actionGroup> + + <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deleteCARate"> + <argument name="name" value="{{SimpleSecondTaxWithZipCode.state}}-{{SimpleSecondTaxWithZipCode.rate}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.taxIdentifierSearch}}"/> + </actionGroup> + + <deleteData createDataKey="firstProduct" stepKey="deleteFirstProduct"/> + <deleteData createDataKey="secondProduct" stepKey="deleteSecondProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <actionGroup ref="deleteProductTaxClass" stepKey="deleteFirstProductTaxClass"> + <argument name="taxClassName" value="TaxClasses1"/> + </actionGroup> + + <actionGroup ref="deleteProductTaxClass" stepKey="deleteSecondProductTaxClass"> + <argument name="taxClassName" value="TaxClasses2"/> + </actionGroup> + + <actionGroup ref="logout" stepKey="logout"/> + </after> + </test> +</tests> diff --git a/lib/internal/Magento/Framework/MessageQueue/Test/Unit/Consumer/Config/XsdTest.php b/lib/internal/Magento/Framework/MessageQueue/Test/Unit/Consumer/Config/XsdTest.php index 2e0120ad7ea82..448a8a3db7407 100644 --- a/lib/internal/Magento/Framework/MessageQueue/Test/Unit/Consumer/Config/XsdTest.php +++ b/lib/internal/Magento/Framework/MessageQueue/Test/Unit/Consumer/Config/XsdTest.php @@ -10,15 +10,26 @@ class XsdTest extends \PHPUnit\Framework\TestCase /** * @var string */ - protected $_schemaFile; + private $schemaFile; + /** + * @var string + */ + private $schemaQueueFile; + + /** + * Set up. + * + * @return void + */ protected function setUp() { if (!function_exists('libxml_set_external_entity_loader')) { $this->markTestSkipped('Skipped on HHVM. Will be fixed in MAGETWO-45033'); } $urnResolver = new \Magento\Framework\Config\Dom\UrnResolver(); - $this->_schemaFile = $urnResolver->getRealPath('urn:magento:framework-message-queue:etc/consumer.xsd'); + $this->schemaFile = $urnResolver->getRealPath('urn:magento:framework-message-queue:etc/consumer.xsd'); + $this->schemaQueueFile = $urnResolver->getRealPath('urn:magento:framework-message-queue:etc/queue.xsd'); } /** @@ -29,13 +40,13 @@ protected function setUp() public function testExemplarXml($fixtureXml, array $expectedErrors) { $validationState = $this->createMock(\Magento\Framework\Config\ValidationStateInterface::class); - $validationState->expects($this->any()) + $validationState->expects($this->atLeastOnce()) ->method('isValidationRequired') ->willReturn(true); $messageFormat = '%message%'; $dom = new \Magento\Framework\Config\Dom($fixtureXml, $validationState, [], null, null, $messageFormat); $actualErrors = []; - $actualResult = $dom->validate($this->_schemaFile, $actualErrors); + $actualResult = $dom->validate($this->schemaFile, $actualErrors); $this->assertEquals(empty($expectedErrors), $actualResult, "Validation result is invalid."); $this->assertEquals($expectedErrors, $actualErrors, "Validation errors does not match."); } @@ -125,4 +136,106 @@ public function exemplarXmlDataProvider() ]; // @codingStandardsIgnoreEnd } + + /** + * @param string $fixtureXml + * @param array $expectedErrors + * @dataProvider exemplarQueueXmlDataProvider + */ + public function testExemplarQueueXml($fixtureXml, array $expectedErrors) + { + $validationState = $this->createMock(\Magento\Framework\Config\ValidationStateInterface::class); + $validationState->expects($this->atLeastOnce()) + ->method('isValidationRequired') + ->willReturn(true); + $messageFormat = '%message%'; + $dom = new \Magento\Framework\Config\Dom($fixtureXml, $validationState, [], null, null, $messageFormat); + $actualErrors = []; + $actualResult = $dom->validate($this->schemaQueueFile, $actualErrors); + $this->assertEquals(empty($expectedErrors), $actualResult, "Validation result is invalid."); + $this->assertEquals($expectedErrors, $actualErrors, "Validation errors does not match."); + } + + /** + * @return array + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function exemplarQueueXmlDataProvider() + { + // @codingStandardsIgnoreStart + return [ + 'valid' => [ + '<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework-message-queue:etc/queue.xsd"> + <broker topic="asd" > + <queue name="queue1" consumer="consumer1" handler="handlerClassOne1::handlerMethod1" consumerInstance="consumerClass1" maxMessages="5"/> + <queue name="queue2" consumer="consumer2" handler="handlerClassOne2::handlerMethod2" consumerInstance="consumerClass2" maxMessages="5"/> + </broker> + </config>', + [], + ], + 'invalid handler format' => [ + '<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework-message-queue:etc/queue.xsd"> + <broker topic="asd" > + <queue name="queue1" consumer="consumer1" handler="handlerClass_One1::handlerMethod1" consumerInstance="consumerClass1" maxMessages="5"/> + <queue name="queue2" consumer="consumer2" handler="handlerClassOne2::handler_Method2" consumerInstance="consumerClass2" maxMessages="5"/> + </broker> + </config>', + [ + "Element 'queue', attribute 'handler': [facet 'pattern'] The value 'handlerClass_One1::handlerMethod1' is not accepted by the pattern '[a-zA-Z0-9\\\\]+::[a-zA-Z0-9]+'.", + "Element 'queue', attribute 'handler': 'handlerClass_One1::handlerMethod1' is not a valid value of the atomic type 'handlerType'.", + "Element 'queue', attribute 'handler': [facet 'pattern'] The value 'handlerClassOne2::handler_Method2' is not accepted by the pattern '[a-zA-Z0-9\\\\]+::[a-zA-Z0-9]+'.", + "Element 'queue', attribute 'handler': 'handlerClassOne2::handler_Method2' is not a valid value of the atomic type 'handlerType'.", + ], + ], + 'invalid instance format' => [ + '<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework-message-queue:etc/queue.xsd"> + <broker topic="asd" > + <queue name="queue1" consumer="consumer1" handler="handlerClassOne1::handlerMethod1" consumerInstance="consumer_Class1" maxMessages="5"/> + <queue name="queue2" consumer="consumer2" handler="handlerClassOne2::handlerMethod2" consumerInstance="consumerClass_2" maxMessages="5"/> + </broker> + </config>', + [ + "Element 'queue', attribute 'consumerInstance': [facet 'pattern'] The value 'consumer_Class1' is not accepted by the pattern '[a-zA-Z0-9\\\\]+'.", + "Element 'queue', attribute 'consumerInstance': 'consumer_Class1' is not a valid value of the atomic type 'instanceType'.", + "Element 'queue', attribute 'consumerInstance': [facet 'pattern'] The value 'consumerClass_2' is not accepted by the pattern '[a-zA-Z0-9\\\\]+'.", + "Element 'queue', attribute 'consumerInstance': 'consumerClass_2' is not a valid value of the atomic type 'instanceType'.", + ], + ], + 'invalid maxMessages format' => [ + '<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework-message-queue:etc/queue.xsd"> + <broker topic="asd" > + <queue name="queue1" consumer="consumer1" handler="handlerClassOne1::handlerMethod1" consumerInstance="consumerClass1" maxMessages="ABC"/> + <queue name="queue2" consumer="consumer2" handler="handlerClassOne2::handlerMethod2" consumerInstance="consumerClass2" maxMessages="5"/> + </broker> + </config>', + [ + "Element 'queue', attribute 'maxMessages': 'ABC' is not a valid value of the atomic type 'xs:integer'.", + ], + ], + 'unexpected element' => [ + '<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework-message-queue:etc/queue.xsd"> + <broker topic="asd" > + <queue name="queue1" consumer="consumer1" handler="handlerClassOne1::handlerMethod1" consumerInstance="consumerClass1" maxMessages="2"/> + <queue name="queue2" consumer="consumer2" handler="handlerClassOne2::handlerMethod2" consumerInstance="consumerClass2" maxMessages="5"/> + <unexpected name="queue2"/> + </broker> + </config>', + [ + "Element 'unexpected': This element is not expected. Expected is ( queue ).", + ], + ], + 'unexpected attribute' => [ + '<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework-message-queue:etc/queue.xsd"> + <broker topic="asd" > + <queue name="queue1" consumer="consumer1" handler="handlerClassOne1::handlerMethod1" consumerInstance="consumerClass1" maxMessages="2"/> + <queue name="queue2" consumer="consumer2" handler="handlerClassOne2::handlerMethod2" consumerInstance="consumerClass2" maxMessages="5" unexpected="unexpected"/> + </broker> + </config>', + [ + "Element 'queue', attribute 'unexpected': The attribute 'unexpected' is not allowed.", + ], + ], + ]; + // @codingStandardsIgnoreEnd + } } diff --git a/lib/internal/Magento/Framework/MessageQueue/etc/queue_base.xsd b/lib/internal/Magento/Framework/MessageQueue/etc/queue_base.xsd index 4b326d637f274..bede197ec34de 100644 --- a/lib/internal/Magento/Framework/MessageQueue/etc/queue_base.xsd +++ b/lib/internal/Magento/Framework/MessageQueue/etc/queue_base.xsd @@ -41,7 +41,7 @@ </xs:documentation> </xs:annotation> <xs:restriction base="xs:string"> - <xs:pattern value="[a-zA-Z\\]+" /> + <xs:pattern value="[a-zA-Z0-9\\]+" /> <xs:minLength value="4" /> </xs:restriction> </xs:simpleType> @@ -53,7 +53,7 @@ </xs:documentation> </xs:annotation> <xs:restriction base="xs:string"> - <xs:pattern value="[a-zA-Z\\]+::[a-zA-Z]+" /> + <xs:pattern value="[a-zA-Z0-9\\]+::[a-zA-Z0-9]+" /> <xs:minLength value="5" /> </xs:restriction> </xs:simpleType>